Версия:

Creating a class file, working with code

First of all, as mentioned above, after creating a class in tables.json and synchronizing it in Class_profile, the class becomes fully operational. That is, it already has all the basic methods, a table in the database, its own menu item that will display the table in the interface (for ds_ it is created in Dictionary_system, for d_ in Dictionary or Basic_data, for others in Temporary), and its methods can be called via internal (and external) api, both from methods on the backend and from the client.

Also, during synchronization, a file with a description of typescript structures associated with this class is created, and an interface for the fields of this class is automatically created/updated in it, for example, for the Crm_user class: ICrm_userDataRow, which may be useful for methods working with records of this class. It is located in classes/_structures/<class_name>.ts

You do not need to create a class file for it to work unless you want to add new methods or override base ones. However, in the current version, calling methods of any class from other methods of other (or the same) classes is carried out through a construction of the form:

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

where “Deal” is the class itself imported from the class file. This is necessary so that typescript can check the parameters.

Thus, we need to create a class file.

To create a class file, go to the system interface in the System->Classes menu, find the desired class and in the context menu for this line select “Create class file.”

The file will be created in classes/<Class_name>.ts. It must be manually added to git.

You can import the created class for use in the internal API syntax res = await r.api(<Class_name>, ‘<methodName>’, {id : 1}) But do not create its instance manually to call its methods, as the internal api is used for this. see basic concepts.

In it, you can write your own methods or override the basic ones.

Writing your own methods

When a class file is created, it is copied from a template which, in addition to the necessary code, contains examples of methods. They can be taken as a basis.

Here is an example from there:

/**
     * 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 // Can be used multiple times
        
        // 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('Everything is ok', {name: usr.userData.fio})
        return new UserOk('Everything is ok', {name: usr.userData.fio, age: usr.userData.age})



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

Key aspects

For reference, here is an example of a method involved in generating this documentation page:
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
        }[] = []

        // Get documentation item by path
        res = await r.api(Documentation_item, 'getByUrl', {url, lang})
        const getByUrlData = res.data as IGetByUrlRes

        // Get documentationId and list of documentations with such alias to form the language list
        const documentationAlias: string = getByUrlData.row?.documentation_alias
            || getByUrlData.suggestions?.[0]?.documentation_alias || url.split('/eso/')[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) {
                // Select from results by language
                currentDocumentationId = res.data.rows
                    .find(one => one.language_sysname === lang)?.id || null
            }
        }

        // Get menu 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) {
                // Form a list of suggestions from first-level documentation items
                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('Select section', lang)

            } else {
                title = await langFn('Document not found', lang)
                suggestionsTitle = await langFn('Perhaps you were looking for', lang)
            }

            // Send "Document not found" page and suggestions
            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('Error loading template', {tpl: 'tpl_document_not_found'});
            }



            const viewNF = {
                title,
                suggestionsTitle,
                toMainText: await langFn('Back to documentation home page', lang),
                docsListText: await langFn('To documentation list', 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})
        }

        // Get current document, if this version is not available, request other versions of the same document,
        // and format them as a list of suggestions

        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 {
            // If version is not specified, return current version
            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) {
            // Get other versions if any
            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,
                })
            })

            // Send "Document version not found" page and suggestions (other versions)
            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('Error loading template', {tpl: 'tpl_document_not_found'});
            }

            const viewNF = {
                title: await langFn('Document version not found', lang),
                suggestionsTitle: await langFn('Other available versions', lang),
                toMainText: await langFn('Back to documentation home page', lang),
                docsListText: await langFn('To documentation list', 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: `Version ${v.version}`,
                }))
            };

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

        // If found, return it, and also request a list of other versions to form the dropdown list
        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 = []

        // Get child elements
        {
            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 || '');

        // Template path
        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('Error loading template');
        }

        const view = {
            // title: 'Documentation: ' + params.url,
            title: '',
            docsListText: await langFn('To documentation list', lang),
            docsListUrl: '/docs',
            sidebarHtml: menuHtml,
            currentUrl: `/docs/${url}`,
            content: contentHtml,
            currentLang: lang.toLowerCase(),
            languages,
            versions,
            childLinksTitle: childLinks?.length ? `<h5>${await langFn('More information in sections', lang)}:</h5>` : '',
            childLinks
        };

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

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