Создание файла класса, работа с кодом
Прежде всего, как уже говорилось выше, после создания класса в 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')
}
Ключевые аспекты
- Документирующий коментарий
- Декоратор @OpenApi(), если в хотите чтобы метод автоматически был собран в документацию
- Описание входных параметров в дженерике для IAPIRequest.
- Описание выходных параметров в дженерике IAPIResponse
- Возможность получить переданные параметры из params. (rParams содержит системные параметры метода)
- Возможность получить информацию из запроса, о сессии, пользователе, его сокетах и т.д.
- Вызов других методов, этого или того же класса через внутренее api.
- Обязательно проверяйте на ошибки
if (res.code) return res
- Обязательно проверяйте на ошибки
- Возврат ошибки (MyError или UserError) или успеха UserOk (с указанием объекта в соответсвии с выходными параметрами)
Для ознакомления приведу пример метода, участвующего в формировании этой страницы документации:
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})
}