Рассмотрим хранение данных контекста формы, как одно из применений Произвольного типа.
При работе с данными на форме, в случае, когда запросы на получение данных выполняются с сервера, а взаимодействие с полученными данными на клиенте/сервере (например, ставится задача предоставить возможность пользователю взаимодействовать с данными с ограниченным доступом), может возникнуть потребность сократить количество запросов на получение данных и/или хранить данные произвольного формата и не множить контекст формы.
Для решения данной задачи возможно использовать Произвольный тип.
Например, необходимо сформировать отчёт по работе пользователей. Решение сводится к запросу на получение и обработку данных на сервере, формированию таблицы и применению фильтров на клиенте.
Оперативные данные хранятся в формате JSON и записываются в свойство типа Произвольный тип.
Для работы с оперативными данными реализован класс FormCache с заданным набором полей и методом для конвертации данных в JSON.
Код:
/**
* Данные по категории
*/
type EnumDataItem = {
item: ApplicationItemRef<any, any> | UserGroupItemRef;
enum_item: TEnum<string>;
}
/**
* Кэш формы
*/
class FormCache {
/**
* Менеджеры
*
* @remarks На клиент в кэше будет передаваться информация по полученным пользователям.
* При наличии требований конфиденциальности возможно оставить только RefItem, и на фильтрации каждый раз получать пользователей по RefItem
* Либо оставлять только необходимые данные в json
*/
managers: UserItem[];
/**
* Данные по категории Роль
*/
role_enum_data: EnumDataItem[];
constructor() {
this.managers = [];
this.role_enum_data = [];
}
json() {
return {
managers: this.managers.map(item => ({
id: item.id,
code: item.code,
namespace: item.namespace,
data: (item as any).json()
})),
role_enum_data: this.role_enum_data.map(data_item => ({
item: {
id: data_item.item.id,
code: data_item.item.code,
namespace: data_item.item.namespace
},
enum_item: {
code: data_item.enum_item.code,
name: data_item.enum_item.name
}
}))
}
}
}
/**
* Размер пакета промисов (оптимальное значение)
*/
const promises_package_size = 20;
Получение данных для фильтрации по группам пользователей и подчинённым менеджерам с записью в form_cache.
Код:
/**
* Подготовить данные перед заполнением
*/
async function getDataReady(): Promise<void> {
// Группы компании
const user_group_codes = ['018e9932-69e4-7e20-86fd-cdfb34f17eab', '018e3277-23ff-7029-9dea-704587a999c9'];
const company_groups = await System.userGroups.search()
.where((f, g) => g.and(
f.__deletedAt.eq(null),
f.namespace.eq('_clients._companies'),
f.code.in(user_group_codes)
))
.size(100)
.all();
if (company_groups.length < 0) {
throw new Error(`company_groups is empty at ${getDataReady.name}`);
}
const role_enum_data: EnumDataItem[] = [];
for (let company_group of company_groups) {
const enum_item: TEnum<string> = {
code: company_group.id,
name: company_group.data.__name
};
role_enum_data.push({
item: company_group,
enum_item: enum_item
});
Context.fields.role_enum.data.variants.push(enum_item);
}
const company_groups_users = await System.users.search()
.where((f, g) => g.and(
f.__deletedAt.eq(null),
f.__status.eq(UserStatus.Active),
g.or(...company_groups.map(group => f.groupIds.has(group)))
))
.size(10000)
.all();
if (company_groups_users.length < 0) {
throw new Error(`company_groups_users is empty at ${getDataReady.name}`);
}
const current_user = await System.users.getCurrentUser();
// Элементы оргструктуры текущего пользователя
let current_user_child_orgstructure_items: OrganisationStructureItem[] = [];
if (current_user.data.osIds && current_user.data.osIds.length > 0) {
current_user_child_orgstructure_items = await System.organisationStructure.search()
.where((f, g) => g.and(
f.__deletedAt.eq(null),
f.parent.in(current_user.data.osIds!.map(item => item.id))
))
.size(1000)
.all();
}
const orgstructure_items: OrganisationStructureItem[] = [];
// Асинхронное получение поддеревьев по подчиненным элементам оргструктуры текущего пользователя
await Promise.all(
current_user_child_orgstructure_items.map(async item => orgstructure_items.push(...await allLevelSubItems(item)))
);
// Пересечение пользователей групп и пользователей по подчиненным должностям текущего пользователя, включая текущего пользователя
const users_intersection = company_groups_users.filter(user =>
user.id === current_user.id ||
orgstructure_items.some(item => user.data.osIds?.some(os_item => os_item.id === item.id))
);
// Запись в кэш
const form_cache = new FormCache();
form_cache.managers = users_intersection;
form_cache.role_enum_data = role_enum_data;
Context.data.form_cache = form_cache.json();
Context.fields.managers.data.setFilter((f, c, g) => g.and(
f.__id.in(form_cache.managers.map(item => item.id))
));
}
/**
* Получение элементов дерева
*
* @param item Элемент оргструктуры
*
* @returns Элементы дерева оргструктуры
*/
async function allLevelSubItems(item: OrganisationStructureItem): Promise<OrganisationStructureItem[]> {
let items = [item];
const child_items = item.getChildren();
for (const child_item of child_items) {
items = items.concat(await allLevelSubItems(child_item));
}
return items;
}
Формирование таблицы с заданной фильтрацией.
Код:
/**
* Заполнить таблицу с данными
*/
async function writeDataTable(): Promise<void> {
if (!Context.data.form_cache) {
throw new Error(`Context.data.form_cache is falsy at ${writeDataTable.name}`);
}
const form_cache: FormCache = Context.data.form_cache;
if (!form_cache || !form_cache.managers) {
throw new Error(`form_cache is falsy at ${writeDataTable.name}`);
}
const data_table = Context.data.data_table!;
// Очистка таблицы
for (let i = data_table.length - 1; i >= 0; i--) {
data_table.delete(i);
}
// Заполнение таблицы
const managers_promises = form_cache.managers.map(async (user) => {
// Фильтр на Менеджеров
if (Context.data.managers && Context.data.managers.length > 0 && !Context.data.managers.some(item => item.id == user.id)) {
return;
}
// Фильтр на Роль
if (Context.data.role_enum && Context.data.role_enum.length > 0 &&
!Context.data.role_enum.some(
item => user.data.groupIds?.some(group_id => (group_id as any) === form_cache.role_enum_data.find(item_data => item_data.enum_item.code === item.code)?.item.id)
)) {
return;
}
const [deals_count, contacts_count] = await Promise.all([
Context.fields.deals.app.search()
.where((f, g) => g.and(
f.__deletedAt.eq(null),
f.__createdBy.eq(user),
})
.count(),
Context.fields.contacts.app.search()
.where((f, g) => g.and(
f.__deletedAt.eq(null),
f.__createdBy.eq(user),
})
.count()
]);
const table_row = data_table.insert();
table_row.manager = user;
table_row.deals_amount = deals_count;
table_row.contacts_amount = contacts_count;
});
// Ожидание промисов по пакетам в соответствии с заданным размером пакета
for (let i = 0; i < managers_promises.length; i += promises_package_size) {
await Promise.all(managers_promises.slice(i, i + promises_package_size));
}
Context.data.data_table = data_table;
}