Версия:

Создание файла класса, работа с кодом

Прежде всего, как уже говорилось выше, после создания класса в tables.json и синхронизации его в Class_profile, класс становится полностью работоспособен. То есть у него уже есть все базовые методы, таблица в БД, свой пункт меню, который выведет в интерфейс таблицу (для ds_ создается в Dictionary_system, для d_ в Dictionary или Basic_data, для остальных в Temporary), а также его методы можно вызывать через внутреннее (и внешнее) api, как из методов на бэкенде, так и с клиента.

Также при синхронизации создается файл с описанием структур typescript, связанных с этим классом и в нем автоматически создается/обновляется интерфейс для полей этого класса, например для класса Crm_user: ICrm_userDataRow, который может пригодится для методов работающих с записями этого класса. Он располагается в classes/_structures/<class_name>.ts

Для работоспособности файл класса вам создавать не нужно, если вы не хотите дописать новые методы или переопределить базовые. Однако в текущей версии, вызов методов какого либо класса из других методов, других (или того же) классов, осуществляется через конструкцию вида:

res = await r.api(Deal, 'getById', {id: 1})

где “Deal” это сам класс импортируемый из файла класса. Это необходимо, чтобы typescript мог проверять параметры.

Таким образом, нам необходимо создать файл класса.

Чтобы создать файл класса, зайдите в интерфейсе системы в меню System->Classes, найдите нужный класс и в контекстном меню по этой строчке выберите “Создать файл класса.”

Файл создастся в classes/<Class_name>.ts. Его вручную необходимо добавить в git.

Созданный класс вы можете импортировать для использования в синтаксисе внутреннего API res = await r.api(<Class_name>, ‘<methodName>’, {id : 1}) Но не создавать его инстанс вручную для вызова его методов, так как для этого используется именно внутреннее api. см. базовые концепции.

В нем же вы можете писать свои методы, или переопределять базовые.

Написание своих методов

При создании файла класса он копируется из шаблона, в котором помимо необходимого кода имеются примеры методов. Их можно брать за основу.

Вот пример оттуда:

/**
     * Description of the "example" method
     * Описание метода "example".
     */
    // @OpenApi()
    async example(r: IAPIRequest<{checkSomething:boolean}>): Promise<IAPIResponse<{name:string, age?:number}>> {

        const rParams: IAPIParams = r.params
        const params: IAPIQueryParams = r.data.params
        const checkSomething = params.checkSomething;

        const usr = r.client._session.usr

        let res: IAPIResponse // Может быть использована многократно

        // res = await r.api(Crm_user, 'add', {
        //     firstname: 'John',
        //     lastname: 'Doe',
        //     email: 'john.d@example.com'
        // })
        // if (res.code) return res
        // console.log('Added successfully. ID:', res.data.id)



        // return new UserOk('Все ок', {name: usr.userData.fio})
        return new UserOk('Все ок', {name: usr.userData.fio, age: usr.userData.age})



        // return await new Promise(cb=>{})
        return new MyError('notImplemented')
    }

Ключевые аспекты

Для ознакомления приведу пример метода, участвующего в формировании этой страницы документации:
async getContentByUrl(r: IAPIRequest<{
        url: string,
        query?: {
            v?: string
            lang?: string
        }
    }>): Promise<IAPIResponse<{content: string}>> {
        const params = r.data.params
        const lang = (params.query?.lang || defaultSystemLang).toUpperCase()
        const version = params.query?.v
        const url = params.url

        let res: IAPIResponse
        const languages: {
            sysname: string,
            name: string,
            selected: string
        }[] = []

        // Получим элемент документации по пути
        res = await r.api(Documentation_item, 'getByUrl', {url, lang})
        const getByUrlData = res.data as IGetByUrlRes

        // Получим documentationId и список документаций с таким alias, для формирования списка язков
        const documentationAlias: string = getByUrlData.row?.documentation_alias
            || getByUrlData.suggestions?.[0]?.documentation_alias || url.split('/')[0] || null

        let currentDocumentationId: number = getByUrlData.row?.documentation_id || null

        if (documentationAlias) {
            res = await r.api(Documentation, 'get', {
                where: [
                    whereEq('alias', documentationAlias)
                ],
                columns: ['id', 'language_sysname', 'language'],
            })
            if (res.code) return
            res.data.rows.forEach(one=>{
                languages.push({
                    sysname: one.language_sysname,
                    name: one.language,
                    selected: one.language_sysname === lang ? 'selected' : ''
                })
            })
            if (!currentDocumentationId) {
                // Выберем из результатов по языку
                currentDocumentationId = res.data.rows
                    .find(one => one.language_sysname === lang)?.id || null
            }
        }

        // Получим HTML меню
        let menuHtml = ''
        if (currentDocumentationId) {
            res = await r.api(Documentation, 'getById', {
                id: currentDocumentationId,
                columns: ['menu_html'],
            })
            if (res.code) return
            menuHtml = res.data.rows[0].menu_html
        }

        if (!getByUrlData.row) {

            let suggestions = getByUrlData.suggestions.map(suggestion => ({
                url: `/docs/${suggestion.url}?lang=${suggestion.documentation_lang_sysname.toLowerCase()}`,
                name: `${suggestion.name} (${suggestion.documentation_lang})`,
            }))

            let title: string = ''
            let suggestionsTitle: string = ''

            if (!suggestions.length) {
                // Сформируем список предложений из элементов документации первого уровня
                res = await r.api(Documentation_item, 'get', {
                    where: [
                        whereEq('documentation_id', currentDocumentationId),
                        whereEq('is_active', true),
                        whereIsNull('parent_id')
                    ],
                    columns: ['id', 'name', 'url', 'position'],
                    sort: {
                        columns: ['position', 'name'],
                        directions: ['ASC', 'ASC']
                    },
                })
                if (res.code) return
                suggestions = res.data.rows.map(suggestion => ({
                    url: `/docs/${suggestion.url}`,
                    name: `${suggestion.name}`,
                }))
                title = ''
                suggestionsTitle = await langFn('Выберите раздел', lang)

            } else {
                title = await langFn('Документ не найден', lang)
                suggestionsTitle = await langFn('Возможно вы искали', lang)
            }

            // Отправим страницу "Документ не найден" и предложения
            const templatePathNF = path.join(process.cwd(), 'templates', 'documentation', 'tpl_document_not_found.html');
            let templateNF = '';
            try {
                templateNF = fs.readFileSync(templatePathNF, 'utf-8');
            } catch (e) {
                return new MyError('Ошибка загрузки шаблона', {tpl: 'tpl_document_not_found'});
            }



            const viewNF = {
                title,
                suggestionsTitle,
                toMainText: await langFn('Вернуться на главную страницу документации', lang),
                docsListText: await langFn('К списку документаций', lang),
                docsListUrl: '/docs',
                currentLang: lang.toLowerCase(),
                languages,
                sidebarHtml: menuHtml,
                currentUrl: `/docs/${url}`,
                suggestions
            };

            const finalHtmlNF = Mustache.render(templateNF, viewNF);
            return new UserOk('ok', {content: finalHtmlNF})
        }

        // Получим текущий документ, если этой версии нет, то запросим прочие версии этого же документа,
        // и оформим их как список предложений

        const p = {
            where: [
                // whereEq('id', getByUrlData.row.latest_document_id),
                whereEq('documentation_item_id', getByUrlData.row.id),
                whereEq('is_active', true)
            ],
            columns: ['id', 'content', 'is_active', 'type_sysname', 'version'],
        }
        if (version) {
            p.where.push(whereEq('version', version))
        } else {
            // Если версия не указана, то вернем текущую версию
            p.where.push(whereEq('id', getByUrlData.row.latest_document_id))
        }
        res = await r.api(Document, 'get', p)
        if (res.code) return
        const currentDocument = res.data.rows[0]

        if (!currentDocument) {
            // Получим другие версии если есть
            const otherVersions: {
                id: number,
                version: string,
                url: string,
                is_active: boolean,
                type_sysname: string,
            }[] = []
            res = await r.api(Document, 'get', {
                where: [
                    whereEq('documentation_item_id', getByUrlData.row.id),
                    whereEq('is_active', true)
                ]
            })
            if (res.code) return
            res.data.rows.forEach(one=>{
                otherVersions.push({
                    id: one.id,
                    version: one.version,
                    url: one.url,
                    is_active: one.is_active,
                    type_sysname: one.type_sysname,
                })
            })

            // Отправим страницу "Документ не найден" и предложения (другие версии)
            const templatePathNF = path.join(process.cwd(), 'templates', 'documentation', 'tpl_document_not_found.html');
            let templateNF = '';
            try {
                templateNF = fs.readFileSync(templatePathNF, 'utf-8');
            } catch (e) {
                return new MyError('Ошибка загрузки шаблона', {tpl: 'tpl_document_not_found'});
            }

            const viewNF = {
                title: await langFn('Версия документа не найдена', lang),
                suggestionsTitle: await langFn('Другие доступные версии', lang),
                toMainText: await langFn('Вернуться на главную страницу документации', lang),
                docsListText: await langFn('К списку документаций', lang),
                docsListUrl: '/docs',
                currentLang: lang.toLowerCase(),
                languages,
                sidebarHtml: menuHtml,
                currentUrl: `/docs/${url}`,
                suggestions: otherVersions.map(v => ({
                    url: `/docs/${url}?lang=${lang.toLowerCase()}&v=${v.version}`,
                    name: `Версия ${v.version}`,
                }))
            };

            const finalHtmlNF = Mustache.render(templateNF, viewNF);
            return new UserOk('ok', {content: finalHtmlNF})
        }

        // Если найден, то вернем его, а также запросим список других версий для формирования выпадающего списка
        const allVersionsRes = await r.api(Document, 'get', {
            where: [
                whereEq('documentation_item_id', getByUrlData.row.id),
                whereEq('is_active', true)
            ],
            columns: ['id', 'version', 'priority'],
            sort: {
                columns: ['priority', 'version'],
                directions: ['DESC', 'ASC']
            }
        })
        const versions = (allVersionsRes.data?.rows || []).map((v: any) => ({
            v: v.version,
            selected: v.version === (currentDocument.version) ? 'selected' : ''
        }))

        const childLinks = []

        // Получим дочерние элементы
        {
            const res = await r.api(Documentation_item, 'get', {
                where: [
                    whereEq('documentation_id', currentDocumentationId),
                    whereEq('is_active', true),
                    whereEq('parent_id', getByUrlData.row.id),
                ],
                columns: ['id', 'name', 'url'],
                sort: {
                    columns: ['position', 'name'],
                    directions: ['ASC', 'ASC']
                }
            })
            if (!res.code) {
                res.data.rows.forEach(one=>{
                    childLinks.push({
                        url: `/docs/${one.url}`,
                        name: one.name,
                    })
                })
            }

        }

        const md = new MarkdownIt();
        const contentHtml = md.render(currentDocument.content || '');

        // Путь к шаблону
        const templatePath = path.join(process.cwd(), 'templates', 'documentation', 'tpl_documentation_item.html');
        let template = '';
        try {
            template = fs.readFileSync(templatePath, 'utf-8');
        } catch (e) {
            return new MyError('Ошибка загрузки шаблона');
        }

        const view = {
            // title: 'Документация: ' + params.url,
            title: '',
            docsListText: await langFn('К списку документаций', lang),
            docsListUrl: '/docs',
            sidebarHtml: menuHtml,
            currentUrl: `/docs/${url}`,
            content: contentHtml,
            currentLang: lang.toLowerCase(),
            languages,
            versions,
            childLinksTitle: childLinks?.length ? `<h5>${await langFn('Больше информации в разделах', lang)}:</h5>` : '',
            childLinks
        };

        const finalHtml = Mustache.render(template, view);

        return new UserOk('ok', {content: finalHtml})
    }