...

Использование произвольного типа

Тема в разделе "Примеры сценариев", создана пользователем kirillovykh, 28 май 2024.

  1. kirillovykh

    kirillovykh Участник

    Рассмотрим хранение данных контекста формы, как одно из применений Произвольного типа.

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

    Для решения данной задачи возможно использовать Произвольный тип.

    Например, необходимо сформировать отчёт по работе пользователей. Решение сводится к запросу на получение и обработку данных на сервере, формированию таблицы и применению фильтров на клиенте.

    Оперативные данные хранятся в формате JSON и записываются в свойство типа Произвольный тип.
    Для работы с оперативными данными реализован класс FormCache с заданным набором полей и методом для конвертации данных в JSON.
    Код:
    
    /**
    * Данные по категории
    */
    type EnumDataItem = {
        
    itemApplicationItemRef<anyany> | UserGroupItemRef;
        
    enum_itemTEnum<string>;
    }

    /**
    * Кэш формы
    */
    class FormCache {
        
    /**
         * Менеджеры
         *
         * @remarks На клиент в кэше будет передаваться информация по полученным пользователям.
         * При наличии требований конфиденциальности возможно оставить только RefItem, и на фильтрации каждый раз получать пользователей по RefItem
         * Либо оставлять только необходимые данные в json
         */
        
    managersUserItem[];
        
    /**
         * Данные по категории Роль
         */
        
    role_enum_dataEnumDataItem[];

        
    constructor() {
            
    this.managers = [];
            
    this.role_enum_data = [];
        }

        
    json() {
            return {
                
    managersthis.managers.map(item => ({
                    
    iditem.id,
                    
    codeitem.code,
                    namespace: 
    item.namespace,
                    
    data: (item as any).json()
                })),
                
    role_enum_datathis.role_enum_data.map(data_item => ({
                    
    item: {
                        
    iddata_item.item.id,
                        
    codedata_item.item.code,
                        namespace: 
    data_item.item.namespace
                    },
                    
    enum_item: {
                        
    codedata_item.enum_item.code,
                        
    namedata_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((fg) => 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_dataEnumDataItem[] = [];

        for (
    let company_group of company_groups) {
            const 
    enum_itemTEnum<string> = {
                
    codecompany_group.id,
                
    namecompany_group.data.__name
            
    };

            
    role_enum_data.push({
                
    itemcompany_group,
                
    enum_itemenum_item
            
    });

            
    Context.fields.role_enum.data.variants.push(enum_item);
        }

        const 
    company_groups_users await System.users.search()
            .
    where((fg) => 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_itemsOrganisationStructureItem[] = [];
        if (
    current_user.data.osIds && current_user.data.osIds.length 0) {
            
    current_user_child_orgstructure_items await System.organisationStructure.search()
                .
    where((fg) => g.and(
                    
    f.__deletedAt.eq(null),
                    
    f.parent.in(current_user.data.osIds!.map(item => item.id))
                ))
                .
    size(1000)
                .
    all();
        }

        const 
    orgstructure_itemsOrganisationStructureItem[] = [];

        
    // Асинхронное получение поддеревьев по подчиненным элементам оргструктуры текущего пользователя
        
    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((fcg) => g.and(
            
    f.__id.in(form_cache.managers.map(item => item.id))
        ));
    }

    /**
    * Получение элементов дерева
    *
    * @param item Элемент оргструктуры
    *
    * @returns Элементы дерева оргструктуры
    */
    async function allLevelSubItems(itemOrganisationStructureItem): 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_cacheFormCache 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>= 0i--) {
            
    data_table.delete(i);
        }

        
    // Заполнение таблицы
        
    const managers_promises form_cache.managers.map(async (user) => {
            
    // Фильтр на Менеджеров
            
    if (Context.data.managers && Context.data.managers.length && !Context.data.managers.some(item => item.id == user.id)) {
                return;
            }
            
    // Фильтр на Роль
            
    if (Context.data.role_enum && Context.data.role_enum.length &&
                !
    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_countcontacts_count] = await Promise.all([
                
    Context.fields.deals.app.search()
                    .
    where((fg) => g.and(                  
                        
    f.__deletedAt.eq(null),
                        
    f.__createdBy.eq(user),
                    })
                    .
    count(),
                
    Context.fields.contacts.app.search()
                    .
    where((fg) => 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 0managers_promises.length+= promises_package_size) {
            
    await Promise.all(managers_promises.slice(ipromises_package_size));
        }

        
    Context.data.data_table data_table;
    }