...

Менеджер элементов

Тема в разделе "Примеры решений и дополнительных модулей", создана пользователем Valentin Lysenko, 17 июн 2025 в 23:46.

?

Пользовался виджетом?

  1. Да

    Голосов: 0
    0%
  2. Нет

    Голосов: 0
    0%
  3. Ещё нет, но выглядит полезно

    Голосов: 2
    100%
  1. Valentin Lysenko

    Valentin Lysenko Активный участник

    Описание функционала:
    Виджет позволяет найти определённый элемент приложения в указанном разделе.
    Поиск происходит по всем элементам приложения и запущенным процессам.
    Присутствует функционал замены элемента приложения на новый(указанный).
    Присутствуте функционал проверки правильности ввода.
    Виджет содержит 4 вкладки:
    • Атрибут в приложениях - отображает список всех приложений, в которых находится атрибут
    • Данные по элементам - отображает таблицу всех элементов, где находится указанный элемент
    • Атрибут в процессах - отображает список всех шаблонов процессов, в которых находится атрибут
    • Данные по процессам - отображает таблицу всех процессов, где находится указанный элемент
    Предыстория: в справочнике ELMA появились дубли элементов с одинаковым названием. Пользователи системы использовали эти дубли в других приложениях и процессах.
    Задача: консолидировать элементы и убрать дубликаты в элементах раздела и запущенных
    процессах.

    Как выглядит виджет:

    upload_2025-6-17_18-4-36.png

    Во вложениях файлы с кодом, модуль отдельнои модуль в составе решения.
    Содержимое вкладок описал ниже в комментариях из-за лимита символов.

    Вложения:

    Последнее редактирование: 18 июн 2025 в 15:51
  2. Valentin Lysenko

    Valentin Lysenko Активный участник

    Предварительная настройка:

    Необходимо указать токен, с которым будет формироваться запрос к API

    [​IMG]

    Также следует в виджете добавить зависимость, где находится элемент и в каком разделе его исакать. Если элемент находится в том же разделе, то зависимость будет одна.


    [​IMG]

    При импорте виджета на страницу прописать указанные разделы и код приложения, элемент которого мы ищем.

    [​IMG]
    Последнее редактирование: 18 июн 2025 в 15:41
  3. Valentin Lysenko

    Valentin Lysenko Активный участник

    Вкладка 1: "Атрибут в приложениях"

    На этой вкладке отображаются все приложения внутри раздела, где может использоваться искомый элемент.
    Принцип работы:
    1. Получение данных
      • Запрашиваем список всех приложений через API.
    2. Обработка приложений
      • Для каждого приложения получаем список его переменных.
    3. Поиск переменной
      • Анализируем переменные на совпадение с искомой.
      • Если переменная является таблицей → выполняем поиск по её колонкам.
    4. Визуализация результата
      • Формируем структуру данных, указывая:
        • Множественность (Single: true/false)
        • Местоположение (SYS_COLLECTION / TABLE)

    Спойлер: Формируем структуру данных по приложениям
    Код:
    
    async function getApps2(): Promise<void> {
        const 
    ns_data await getNSData()
        
    //@ts-ignore
        
    const app_keys ns_data.map(obj => obj.code)

        const 
    apps_listAppList[] = [];

        for (const 
    key of app_keys) {
            const 
    fieldsAppField[] = [];
            const 
    app_data await getAppData(key);
            const 
    app_fields app_data.fields

            
    // @ts-ignore
            
    for (const field of app_fields) {
                if (
    field.type === 'TABLE' && field.data?.fields) {
                    
    // Фильтруем колонки таблицы, оставляя только те, что соответствуют search_app_code
                    
    const matchedColumns field.data.fields.filter(
                        (
    colany) => col.data?.code === Context.data.search_app_code

                    
    );
         
                    if (
    matchedColumns.length 0) {
                        
    // Создаем копию поля таблицы, но оставляем только нужные колонки
                        
    const tableFieldCopy = {
                            ...
    field,
                            
    data: {
                                ...
    field.data,
                                
    fieldsmatchedColumns
                            
    }
                        };
                        
    fields.push(tableFieldCopy);
                    }
                } else if (
    field.data?.code === Context.data.search_app_code) {
                    
    // Простое поле (не таблица), соответствующее search_app_code
                    
    fields.push(field);
                }
            }

            if (
    fields.length 0) {
                
    apps_list.push({
                    
    app_namens_data.find((appany) => app.code == key).name,
                    
    app_codekey,
                    
    fieldsfields
                
    });
            }
        }

        
    Context.data.apps_and_fields apps_list;
        
    console.log(Context.data.apps_and_fields);

    }
    /**Получаем список всех приложений раздела */
    async function getNSData(): Promise<any> {
        const 
    token Context.data.token
        
    const myHeaders = {
            
    Authorization: `Bearer ${token}`,
        }
        const 
    requestOptions = {
            
    method"GET",
            
    headersmyHeaders,
            
    redirect"follow"
            
    };

        const 
    response await fetch(`${Context.data.baseUrl}/pub/v1/scheme/namespaces/${Context.data.ns_code}/apps`, requestOptions)
        
    let json await response.json()
        
    let result json.result.result
        
    return result
    }
    /**Получаем данные по приложению */
    async function getAppData(app_codestring): Promise<any> {
        const 
    token Context.data.token
        
    const myHeaders = {
            
    Authorization: `Bearer ${token}`,
        }
        const 
    requestOptions = {
            
    method"GET",
            
    headersmyHeaders,
            
    redirect"follow"
            
    };

        const 
    response await fetch(`${Context.data.baseUrl}/pub/v1/scheme/namespaces/${Context.data.ns_code}/apps/${app_code}`, requestOptions)
        
    let json await response.json()
        
    let result json.application
        
    return result
    }
    Спойлер: Отображаем структуру данных по приложениям
    Код:
    
    <div id='apps_and_fields'>
        <% if (
    Context.data.apps_and_fields && Context.data.apps_and_fields.length 0) { %>
            <
    div class="section-header">
                <
    h2>Поля приложений</h2>
                <
    div class="search-info">
                    
    Фильтр: <%= Context.data.search_ns_code %> / <%= Context.data.search_app_code %>
                </
    div>
            </
    div>

            <
    div class="apps-container">
                <% for (const 
    app of Context.data.apps_and_fields) { %>
                    <
    div class="app-card">
                        <
    div class="app-header" onclick="toggleFields(this)">
                            <
    div class="app-title">
                                <
    span class="app-icon"></span>
                                <
    span class="app-name"><%= app.app_name %> (<%= app.app_code %>)</span>
                                <
    span class="field-count"><%= app.fields.length %> поле(й)</span>
                                <
    span class="toggle-icon"></span>
                            </
    div>
                        </
    div>
             
                        <
    div class="fields-container">
                            <% for (const 
    field of app.fields) { %>
                                <
    div class="field-card <%= field.type.toLowerCase() %>">
                                    <
    div class="field-header">
                                        <
    span class="field-type <%= field.type.toLowerCase() %>">
                                            <%= 
    field.type %>
                                        </
    span>
                                        <
    span class="field-code"><%= field.view.name %> (<%= field.code %>)</span>
                                        <% if (
    field.single) { %>
                                            <
    span class="single-tag">SINGLEtrue</span>
                                        <% } %>
                                        <% if (
    field.single == false) { %>
                                            <
    span class="multiple-tag">SINGLEfalse</span>
                                        <% } %>
                                    </
    div>
                         
                                    <% if (
    field.type === 'TABLE' && field.data?.fields) { %>
                                        <
    div class="table-columns-section">
                                            <
    div class="columns-header">Колонки таблицы:</div>
                                            <
    div class="columns-container">
                                                <% for (const 
    col of field.data.fields) { %>
                                                    <
    div class="column-card <%= col.single ? 'single' : 'multiple' %>">
                                                        <
    span class="column-name"><%= col.view.name %> (<%= col.code %>)</span>
                                                        <% if (
    col.single) { %>
                                                            <
    span class="single-tag">SINGLEtrue</span>
                                                        <% } %>
                                                        <% if (
    col.single == false) { %>
                                                            <
    span class="multiple-tag">SINGLEfalse</span>
                                                        <% } %>
                                                    </
    div>
                                                <% } %>
                                            </
    div>
                                        </
    div>
                                    <% } %>
                                </
    div>
                            <% } %>
                        </
    div>
                    </
    div>
                <% } %>
            </
    div>
        <% } else { %>
            <
    div class="no-data">
                <
    div class="no-data-icon"></div>
                    <
    p>Не найдено процессов для<br/>
                    <
    strong><%= Context.data.search_ns_code %> / <%= Context.data.search_app_code %></strong></p>
                    <
    button class="btn-refresh" onclick="<%=Scripts%>.getApps2()">
                        
    Получить структуру
                    
    </button>
                </
    div>
            </
    div>
        <% } %>
    </
    div>


    <
    script>
    function 
    toggleFields(headerElement) {
        const 
    appCard headerElement.closest('.app-card');
        const 
    fieldsContainer appCard.querySelector('.fields-container');
        const 
    toggleIcon headerElement.querySelector('.toggle-icon');

        
    fieldsContainer.classList.toggle('collapsed');
        
    headerElement.classList.toggle('collapsed');

        
    // Обновляем иконку
        
    if (fieldsContainer.classList.contains('collapsed')) {
            
    toggleIcon.textContent '▶';
        } else {
            
    toggleIcon.textContent '▼';
        }
    }

    // По умолчанию можно свернуть все блоки при загрузке
    document.addEventListener('DOMContentLoaded', function() {
        const 
    appHeaders document.querySelectorAll('.app-header');
        
    appHeaders.forEach(header => {
            
    // Для примера - сворачиваем все блоки при загрузке
            // toggleFields(header);

            // Или оставляем развернутыми по умолчанию
        
    });
    });
    </script>

    Нажимаем кнопку "Получить структуру"

    [​IMG]

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

    [​IMG]
    Последнее редактирование: 18 июн 2025 в 17:55
  4. Valentin Lysenko

    Valentin Lysenko Активный участник

    Вкладка 2: "Данные по элементам"

    На этой вкладе отображаются все места, где находится искомый элемент.
    Для корректной работы необходимо указать id искомого элемента или выбрать его через поле "old element". После указания элемента(id) нажать на кнопку "Получить элементы".

    Спойлер: Кнопка получения элементов + анимация загрузки
    Код:
    
    <% if (Context.data.show_app_loader) { %>
        <
    div class="status-container">
            <
    div class="loading-state">
                <
    div class="loading-indicator">
                    <
    svg width="80" height="80" viewBox="0 0 100 100">
                        <
    circle cx="50" cy="50" r="40" stroke="#f0f2f5" stroke-width="8" fill="none"/>
                        <
    path class="loading-path" d="M50 10 A40 40 0 0 1 90 50" stroke="#4e73df" stroke-width="8"
                            
    fill="none" stroke-linecap="round">
                            <
    animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50"
                                            
    dur="1.5s" repeatCount="indefinite"/>
                        </
    path>
                        <
    circle cx="50" cy="50" r="6" fill="#4e73df"/>
                        <
    circle cx="50" cy="10" r="3" fill="#4e73df">
                            <
    animate attributeName="opacity" values="0.3;1;0.3" dur="1.5s" repeatCount="indefinite"/>
                        </
    circle>
                    </
    svg>
                </
    div>
                <
    class="loading-text">Загрузка данных...</p>
            </
    div>
        </
    div>
    <% } %>

    <% if (
    Context.data.old_element_id && !Context.data.data_by_element && !Context.data.show_app_loader) { %>
        <
    div class="status-container">
            <
    div class="empty-state">
                <
    div class="empty-icon"></div>
                <
    class="empty-message">Нет доступных элементов</p>
                <
    button class="status-btn status-btn--primary" onclick="<%=Scripts%>.showReverseFromApps2()">
                    <
    span class="status-btn__icon"></span>
                    
    Получить элементы
                
    </button>
            </
    div>
        </
    div>
    <% } else if (!
    Context.data.old_element_id && !Context.data.show_app_loader) { %>
        <
    div class="status-container">
            <
    div class="prompt-state">
                <
    div class="prompt-icon">✏️</div>
                <
    class="prompt-message">Введите id или выберите элемент</p>
            </
    div>
        </
    div>
    <% } %>

    Принцип работы:
    1. Обработка записей ведётся по полученной структуре.
    2. Поиск всех элементов, содержащих искомый элемент, с учётом:
      • Возможности нахождения элемента в таблице.
      • Производительности (при большом количестве элементов время обработки увеличивается).
    3. Исключение полей:
      Чтобы исключить определённые поля, добавьте в условие проверку:
      Код:
      
      && !field.view?.name?.startsWith('old')
      
      

    Спойлер: Код поиска элемента
    Код:
    
    async function showReverseFromApps2(): Promise<void> {
        
    Context.data.data_by_element undefined
        Context
    .data.show_app_loader true
        
    if (!Context.data.apps_and_fields){
            
    await getApps2()
        }
        try {
            
    // Получаем пользователя
            
    const element await getElement();
            if (!
    element) {
                
    console.error("element not found");
                
    Context.data.show_app_loader false
                Context
    .data.data_by_element undefined
                
    return;
            }
      
            const 
    reversedResultReversedResultItem[] = [];
      

            
    // Обрабатываем все приложения
            
    for (const app of Context.data.apps_and_fields) {
                
    await processAppFields(appelementreversedResult);
            }
            const 
    element_tableElementTable = {idContext.data.old_element_id!, app_listreversedResult }
            
    Context.data.data_by_element element_table //reversedResult;
            
    console.log(Context.data.data_by_element);
        } catch (
    error) {
            
    console.error("Error in showReverseFromApps:"error);
        }

        
    //Скрываем загрузчик и контейнер
        // container.style.display = "none"
        
    Context.data.show_app_loader false

        
    // Вспомогательные функции
        
    async function getElement() {
            
    //@ts-ignore
            
    return await Imports![Context.data.search_ns_code!]!.app[Context.data.search_app_code!].search()
                .
    where((emp: { __id: { eq: (arg0any) => any } }) => emp.__id.eq(Context.data.old_element_id!))
                .
    first();
        }

        
    async function processAppFields(appanyelementanyreversedResultReversedResultItem[]) {
            for (const 
    field of app.fields) {
                try {
                    if (
    field.type === "TABLE" && !field.view?.name?.startsWith('old')) {
                        
    await processTableField(appfieldelementreversedResult);
                    } else
                    {
                        
    await processRegularField(appfieldelementreversedResult);
                    }
                } catch (
    error) {
                    
    console.error(`Error processing field ${field.code} in app ${app.app_code}:`, error);
                }
            }
        }

        
    async function processTableField(appanyfieldanyelementanyreversedResultReversedResultItem[]) {
            const 
    tableColumns field.data.fields.map((columnany) => ({
                
    column_namecolumn.name,
                
    column_codecolumn.code,
                
    singlecolumn.single true false
            
    }));

            const 
    employeeColumns field.data.fields.filter((colany) =>
                
    col.data?.code === Context.data.search_app_code!
            );

            if (
    employeeColumns.length === 0) return;
                        
    //@ts-ignore
                
    const appSearch Imports![Context.data.ns_code!]!.app[app.app_code].search().where((elemg) => g.and(
                
    elem.__deletedAt.eq(null),
                
    //@ts-ignore
                
    elem[field.code].neq(null)
            ))
            
    //@ts-ignore
            
    let elements await getAllElements(appSearch//Используем именно этот вариант, поскольку далее идёт фильтрация

            
    for (const column of employeeColumns) {
                const 
    filteredElements elements.filter((elemany) =>
                
    elem.data[field.code]?.some((rowany) =>
                    
    column.single
                    
    row[column.code]?.id === element.id
                    
    row[column.code]?.some((empany) => emp.id === element.id)
                ));

                
    addElementsToResult(filteredElementsapp.app_codeapp.app_namefieldtableColumnsreversedResult);
            }
        }

        
    async function processRegularField(appanyfieldanyelementanyreversedResultReversedResultItem[]) {

                                
    //@ts-ignore
            
    const appSearch Imports![Context.data.ns_code!]!.app[app.app_code].search().where((elemg) => g.and(
                
    elem.__deletedAt.eq(null),
                
    //@ts-ignore
                
    field.single
                        
    elem[field.code].link(element)
                        : 
    elem[field.code].has(element),
                ))
            
    //@ts-ignore
            
    let elements await getAllElements(appSearch//Используем именно этот вариант, поскольку далее идёт фильтрация
            
    addElementsToResult(elementsapp.app_codeapp.app_namefieldundefinedreversedResult);
        }

        function 
    addElementsToResult(
            
    elementsany[],
            
    appCodestring,
            
    appNamestring,
            
    fieldany,
            
    tableColumnsTableColumn[] | undefined,
            
    reversedResultReversedResultItem[]
            ) {
            for (const 
    element of elements) {
                
    let existingElement reversedResult.find(el => el.id === element.id);
          
                if (!
    existingElement) {
                    
    existingElement = {
                        
    idelement.id,
                        
    nameelement.data.__name,
                        
    elementelement,
                        
    app_codeappCode,
                        
    app_nameappName,
                        
    fields: []
                    };
                    
    reversedResult.push(existingElement);
                }
          
                if (!
    existingElement.fields.some(=>
                    
    f.field_code === field.code && f.field_type === field.type
                
    ))
                {
                    
    existingElement.fields.push({
                        
    singlefield.single,
                        
    field_namefield.view.name,
                        
    field_codefield.code,
                        
    field_typefield.type,
                        ...(
    tableColumns && { table_columnstableColumns })
                    });
                }
            }
        }

        
    async function getAllElements(appSearchany ): Promise<any> {
            
    let numberOfFilteredElements await appSearch.count();
            
    let rangeElements = {
                
    from0,
                
    tonumberOfFilteredElements,
                *[
    Symbol.iterator]() {
                    for (
    let value this.fromvalue <= this.to;) {
                        yield 
    appSearch.from(value).size(5000).all();
                        
    value value 5000;
                    }
                }
            }
            const 
    itemsPromises = [...rangeElements];
            const 
    result await Promise.all(itemsPromises);
            const 
    revenueCache result.reduce((acc: [], val: []) => acc.concat(val), []);

            return 
    revenueCache
        
    }
    }
    [​IMG]

    Результат поиска отобразится в таблице

    Спойлер: Код виджета таблицы
    Код:
    
    <div class="element-view">
        <% if (
    Context.data.data_by_element) { %>
            <
    div style='display: flex; justify-content:space-between'>
                <
    span class="element-title">Данные по элементам</span>
                <
    button class="btn-refresh" onclick="<%=Scripts%>.showReverseFromApps2()">
                    
    ↻ Получить элементы приложений
                
    </button>
            </
    div>
        <% } %>
        <% if (
    Context.data.data_by_element && Context.data.data_by_element.app_list.length == ) { %>
            <
    spanДанных не найдено </span>
        <% } %>
        <
    div id='element_data'>
            <% if (
    Context.data.data_by_element && Context.data.data_by_element.app_list) { %>
                <
    table class='element-table'>
                    <
    tr>
                        <
    th width='15%' class="element-th">Элемент</th>
                        <
    th width='65%' class="element-th">Приложение и поля</th>
                        <% if (
    Context.data.data_by_element && Context.data.old_element_id == Context.data.data_by_element.id && Context.data.new_element_id) { %>
                            <
    th width='20%' class="element-th">Действие</th>
                        <% } %>
                    </
    tr>
                    <% for (const 
    elementData of Context.data.data_by_element.app_list) { %>
                        <
    tr>
                            <
    td class="element-td">
                                <
    a href="/(p:item/<%=Context.data.ns_code%>/<%= elementData.app_code %>/<%= elementData.id %>)"
                                   
    target="_blank"
                                   
    class="element-link">
                                    <%= 
    elementData.name %>
                                </
    a>
                            </
    td>
                            <
    td class="element-td">
                                <
    div class="app-info">
                                    
    Приложение: <%=elementData.app_name%> (<%= elementData.app_code %>)
                                </
    div>
                                <
    div class="fields-container">
                                    <% for (const 
    field of elementData.fields) { %>
                                        <
    div class="field-item">
                                            <
    div class="field-header">
                                                <
    span class="field-code">
                                                    <%= 
    field.field_name %> (<%= field.field_code %>)
                                                </
    span>
                                                <
    span class="field-type"><%= field.field_type %></span>
                                                <% if (
    field.field_type !== 'TABLE') { %>
                                                        <% if (
    field.single) { %>
                                                            <
    span class="single-tag field-type"">SINGLE: true</span>
                                                        <% } %>
                                                        <% if (field.single == false) { %>
                                                            <span class="
    multiple-tag field-type"">SINGLEfalse</span>
                                                        <% } %>
                                                    <!-- <
    span class="field-type"> (single: <%= field.single %>)</span> -->
                                                <% } %>
                                            </
    div>
                                            <% if (
    field.field_type === 'TABLE' && field.table_columns) { %>
                                                <
    div class="columns-container">
                                                    <% for (const 
    column of field.table_columns) { %>
                                                        <
    div class="column-tag">
                                                            <
    span class="field-code">
                                                            <%= 
    column.column_name %> (<%= column.column_code %>)
                                                            </
    span>
                                                            <% if (
    column.single) { %>
                                                                <
    span class="single-tag column-type"">SINGLE: true</span>
                                                            <% } %>
                                                            <% if (column.single == false) { %>
                                                                <span class="
    multiple-tag column-type"">SINGLEfalse</span>
                                                            <% } %>
                                                            <!-- <
    span class="column-type">(single: <%= column.single true false %>)</span> -->
                                                        </
    div>
                                                    <% } %>
                                                </
    div>
                                            <% } %>
                                        </
    div>
                                    <% } %>
                                </
    div>
                            </
    td>
                            <% if (
    Context.data.data_by_element && Context.data.old_element_id == Context.data.data_by_element.id && Context.data.new_element_id) { %>
                                <
    td class="element-td">
                                    <
    div class="action-buttons">
                                        <
    button id='replace-<%= elementData.id %>' class="btn-replace" onclick="<%=Scripts%>.replaceItem('<%= elementData.id %>', false)">
                                            
    Заменить
                                        
    </button>
                                        <
    button id='cancel-<%= elementData.id %>' class="btn-cancel" style="display: none"  onclick="<%=Scripts%>.replaceItem('<%= elementData.id %>', true)">
                                            
    Отменить
                                        
    </button>
                                    </
    div>
                                </
    td>
                            <% } %>
                        </
    tr>
                    <% } %>
                </
    table>
            <% } %>
        </
    div>
    </
    div>

    [​IMG]
    Последнее редактирование: 18 июн 2025 в 17:47
  5. Valentin Lysenko

    Valentin Lysenko Активный участник

    Функционал замены элемента в приложении.

    Если необходимо заменить элемент на другой, то в поле "new element id" необходимо ввести id новго элемента или выбрать элемент в поле ниже.
    После ввода будет доступна кнопка замены элемента. После нажатия на кнопку замены элемент меняется во всех найденных полях, а кнопка "Заменить" заменяется на кнопку "Отменить", которая производит обратную замену.

    [​IMG] [​IMG]

    Спойлер: Функционал замены элемента
    Код:
    
    async function replaceItem(element_id stringis_reverseboolean): Promise<void> {
            
    //@ts-ignore
        
    let old_element await Imports![Context.data.search_ns_code]!.app[Context.data.search_app_code!].search()
        
    //@ts-ignore
            
    .where(emp => emp.__id.eq(Context.data.old_element_id!))
            .
    first();
        
    //@ts-ignore
        
    let new_element await Imports![Context.data.search_ns_code]!.app[Context.data.search_app_code!].search()
        
    //@ts-ignore
            
    .where(emp => emp.__id.eq(Context.data.new_element_id!))
            .
    first();
        if( !
    old_element || !new_element){
            
    console.log('Один или оба элементов не найдены')
            return
        }

        if (
    is_reverse){
            [
    old_elementnew_element] = [new_elementold_element];
        }

        
    let replace_button document.querySelector(`#replace-${element_id}`)
        
    let cancel_button document.querySelector(`#cancel-${element_id}`)
            
    console.log(element_id)
        
    //@ts-ignore    
        
    let elem Context.data.data_by_element.app_list.find(el => el.id == element_id)

        for (
    let field of elem.fields) {
            try {
                if (
    field.field_type === "TABLE") {
                    if (
    elem.element.data[field.field_code]) {
                        for (
    let row of elem.element.data[field.field_code]) {
                            for (
    let column of field.table_columns!) {
                                if (
    row.hasOwnProperty(column.column_code)) {
                                    if (Array.
    isArray(row[column.column_code])) {
                                        const 
    index row[column.column_code].findIndex(
                                            (
    cell_itemany) => cell_item?.id == old_element.id
                                        
    );
                                        if (
    index !== -1) {
                                            
    row[column.column_code][index] = new_element;
                                        }
                                    } else if (
    row[column.column_code]?.id === old_element.id) {
                                        
    row[column.column_code] = new_element;
                                    }
                                }
                            }
                        }
                    }
                } else {
                    if (Array.
    isArray(elem.element.data[field.field_code])) {
                        const 
    index elem.element.data[field.field_code].findIndex(
                            (
    cell_itemany) => cell_item?.id == old_element.id
                        
    );
                        if (
    index !== -1) {
                            
    elem.element.data[field.field_code][index] = new_element;
                        }
                    } else if (
    elem.element.data[field.field_code]?.id === old_element.id) {
                        
    elem.element.data[field.field_code] = new_element;
                    }
                }
            } catch (
    error) {
                
    console.log += `Error updating field ${field.field_code} in element ${elem.id}${error} `;
            }
        }
        
    await elem.element.save();  
        if (
    is_reverse){
            
    cancel_button.style.display 'none'
            
    replace_button.style.display 'block'
        
    }
        else{
            
    replace_button.style.display 'none'
            
    cancel_button.style.display 'block'
        
    }
    }
    Последнее редактирование: 18 июн 2025 в 14:42
  6. Valentin Lysenko

    Valentin Lysenko Активный участник

    Вкладка 3: "Атрибут в процессах"

    Здесь отображаются все процессы внутри раздела, где может использоваться искомый элемент.
    Принцип работы
    1. Обработка процессов
      • Сначала обрабатываем процессы раздела
      • Затем обрабатываем процессы приложений
    2. Анализ переменных
      • Для каждого процесса получаем список его переменных
      • Среди переменных находим искомую
      • Если переменная является таблицей → выполняем углубленный поиск по колонкам
    3. Формирование результата
      • Объединяем:
        • Процессы приложений
        • Процессы раздела
      • Отрисовываем итоговую структуру, указывая:
        • Множественность (Single: true/false)
        • Местоположение (SYS_COLLECTION / TABLE)
    Спойлер: Структура процессов
    Код:
    
    async function getProcesses2(): Promise<void> {
        
    // Деструктуризация с сохранением ссылок на оригинальные данные
        
    const { ns_codesearch_app_codesearch_ns_code } = Context.data;

        if (!
    ns_code) {
            
    console.error('Namespace code is required');
            return;
        }

        
    // Мемоизация часто используемых данных
        
    const targetAppFilter = (dataany) =>
            
    data?.code === search_app_code && data?.namespace === search_ns_code;

        
    // Универсальная функция обработки процессов
        
    const processMapper = (processProcess): ExtendedProcess null => {
            if (!
    process.context) return null;
            
    //@ts-ignore
            
    const fields Object.values(process.context).flatMap((fieldany) => {
                if (
    field.type === 'TABLE' && field.data?.fields) {
                    const 
    matchedColumns field.data.fields.filter((colany) => targetAppFilter(col.data));
                    return 
    matchedColumns.length ? [{
                        ...
    field,
                        
    data: { ...field.datafieldsmatchedColumns }
                    }] : [];
                }
                return 
    targetAppFilter(field.data) ? [field] : [];
            });

            return 
    fields.length ? {
                ...
    process,
                
    idprocess.__id,
                
    filtered_contextfields
            
    } : null;
        };

        
    // Обработка namespace процессов
        //@ts-ignore
        
    const namespaceProcesses Object.values(Imports![ns_code]!.processes)
            .
    map(processMapper)
            .
    filter(Boolean) as ExtendedProcess[];

        const 
    namespaceListProcessList = {
            
    app_name"",
            
    app_code"",
            
    processesnamespaceProcesses
        
    };

        
    // Обработка app процессов
        
    const nsData await getNSData();
        
    //@ts-ignore
        
    const appsList nsData.map(({ code }) => {
                
    //@ts-ignore
                
    const app Imports![ns_code].app[code];
                
    //@ts-ignore
                
    const processes Object.values(app.processes)
                    .
    map(processMapper)
                    .
    filter(Boolean) as ExtendedProcess[];

                return 
    processes.length ? {
                    
    app_nameapp.name,
                    
    app_codecode,
                    
    processes
                
    } : null;
            })
            .
    filter(Boolean) as ProcessList[];

        
    // Собираем результат без мутации Context.data до последнего момента
        
    const result = [...appsListnamespaceList].filter(item => item.processes.length 0);

        
    // Единственное присваивание в Context.data
        
    Context.data.processes result;
    }
    /**Получаем список всех приложений раздела */
    async function getNSData(): Promise<any> {
        const 
    token Context.data.token
        
    const myHeaders = {
            
    Authorization: `Bearer ${token}`,
        }
        const 
    requestOptions = {
            
    method"GET",
            
    headersmyHeaders,
            
    redirect"follow"
            
    };

        const 
    response await fetch(`${Context.data.baseUrl}/pub/v1/scheme/namespaces/${Context.data.ns_code}/apps`, requestOptions)
        
    let json await response.json()
        
    let result json.result.result
        
    return result
    }
    Спойлер: Виджет отображения
    Код:
    
    <div id='processes_list'>
        <% if (
    Context.data.processes && Context.data.processes.length 0) { %>
            <
    div class="processes-header">
                <
    h2>Доступные процессы</h2>
                <
    div class="search-info">
                    
    Фильтр: <%= Context.data.search_ns_code %> / <%= Context.data.search_app_code %>
                </
    div>
            </
    div>
       
            <
    table class='processes-table'>
                <
    thead>
                    <
    tr>
                        <
    th width="25%">Приложение</th>
                        <
    th width="75%">Процессы и поля</th>
                    </
    tr>
                </
    thead>
                <
    tbody>
                    <% for (const [
    appIndexappof Context.data.processes.entries()) { %>
                        <
    tr class="app-row">
                            <
    td class="app-code">
                                <
    div class="app-name"> <%= app.app_name %> (<%= app.app_code %>)</div>
                                <
    div class="process-count"><%= app.processes.length %> процесса(ов)</div>
                            </
    td>
                            <
    td class="processes-container">
                                <
    div class="processes-accordion">
                                    <% for (const [
    processIndexprocessof app.processes.entries()) { %>
                                        <% const 
    uniqueId = `app-${appIndex}-process-${processIndex}`; %>
                                        <
    div class="process-item">
                                            <
    div class="process-header" onclick="<%=Scripts%>.toggleProcessDetails('<%= uniqueId %>')">
                                                <
    span class="process-name"><%= process.__name %></span>
                                                <
    span class="process-code"><%= process.code %></span>
                                                <
    span class="toggle-icon" id="icon-<%= uniqueId %>"></span>
                                            </
    div>
                                       
                                            <
    div class="process-details" id="details-<%= uniqueId %>">
                                                <% if (
    process.filtered_context && process.filtered_context.length 0) { %>
                                                    <
    div class="fields-section">
                                                        <
    div class="section-title">Поля процесса:</div>
                                                        <
    div class="fields-grid">
                                                            <% for (const 
    field of process.filtered_context) { %>
                                                                <
    div class="field-item">
                                                                    <
    div class="<%= field.type.toLowerCase() %>">
                                                                        <
    div class="field-type"><%= field.type %></div>
                                                                        <% if (
    field.type === 'TABLE') { %>
                                                                            <
    div class="field-info">
                                                                                <%= 
    field.view.name %> (<%= field.code %>)
                                                                            </
    div>
                                                                            <
    div class="columns-header"Колонки таблицы:</div>
                                                                            <
    div class="columns-container">
                                                                                <% for (const 
    col of field.data.fields) { %>
                                                                                    <
    div class="column-card <%= col.single ? 'single' : 'multiple' %>"">
                                                                                        <span class="
    field-code"><%= col.view.name %> (<%= col.code %>)</span>
                                                                                        <% if (col.single) { %>
                                                                                            <span class="
    single-tag">SINGLE: true</span>
                                                                                        <% } %>
                                                                                        <% if (col.single == false) { %>
                                                                                            <span class="
    multiple-tag">SINGLE: false</span>
                                                                                        <% } %>
                                                                                    </div>
                                                                                <% } %>
                                                                            </div>
                                                                        <% } else { %>
                                                                            <div class="
    field-info">
                                                                                <span class="
    field-code"><%= field.view.name %> (<%= field.code %>)</span>
                                                                                <% if (field.single) { %>
                                                                                    <span class="
    single-tag">SINGLE: true</span>
                                                                                <% } %>
                                                                                <% if (field.single == false) { %>
                                                                                    <span class="
    multiple-tag">SINGLE: false</span>
                                                                                <% } %>
                                                                            </div>
                                                                        <% } %>
                                                                    </div>
                                                                </div>
                                                            <% } %>
                                                        </div>
                                                    </div>
                                                <% } else { %>
                                                    <div class="
    no-fields">Нет подходящих полей</div>
                                                <% } %>
                                            </div>
                                        </div>
                                    <% } %>
                                </div>
                            </td>
                        </tr>
                    <% } %>
                </tbody>
            </table>
        <% } else { %>
            <div class="
    no-data">
                <div class="
    no-data-icon">⎈</div>
                <p>Не найдено процессов для<br/>
                   <strong><%= Context.data.search_ns_code %> / <%= Context.data.search_app_code %></strong></p>
                <button class="
    btn-refresh" onclick="<%=Scripts%>.getProcesses2()">
                    Показать структуру
                </button>
            </div>
        <% } %>
    </div>

    <script>


    function selectProcess(appCode) {
        // Ваша логика выбора процесса
        console.log('Выбрано приложение:', appCode);
        // Дополнительная обработка...
    }
    </script>

    [​IMG]
    Последнее редактирование: 18 июн 2025 в 17:55
  7. Valentin Lysenko

    Valentin Lysenko Активный участник

    Вкладка 4: "Данные по процессам"

    На этой вкладе отображаются все процессы, где находится искомый элемент.
    Для корректной работы необходимо указать id искомого элемента или выбрать его через поле "old element". После указания элемента(id) нажать на кнопку "Получить процессы".
    Принцип работы: По полученной выше структуре ведём обработку записей. Ищем все запущенные процессы, где присутствует искомый элемент. Шаблоны и экземпляры процессов получаем по API. При поиске учитывается возможность нахождения в таблице. Лимит процессов одного шаблона - 10000.
    Список запущенных процессов получаем через id шаблона. Id шаблона получаем через любой существующий процесс, зная его код. Код получаем из цикла.
    Спойлер: Кнопка получения процессов + анимация загрузки
    <% if (Context.data.show_process_loader) { %>
    <div class="status-container">
    <div class="loading-state">
    <div class="loading-indicator">
    <svg width="80" height="80" viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="40" stroke="#f0f2f5" stroke-width="8" fill="none"/>
    <path class="loading-path" d="M50 10 A40 40 0 0 1 90 50" stroke="#4e73df" stroke-width="8"
    fill="none" stroke-linecap="round">
    <animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50"
    dur="1.5s" repeatCount="indefinite"/>
    </path>
    <circle cx="50" cy="50" r="6" fill="#4e73df"/>
    <circle cx="50" cy="10" r="3" fill="#4e73df">
    <animate attributeName="opacity" values="0.3;1;0.3" dur="1.5s" repeatCount="indefinite"/>
    </circle>
    </svg>
    </div>
    <p class="loading-text">Загрузка данных...</p>
    </div>
    </div>
    <% } %>

    <% if (Context.data.old_element_id && !Context.data.processes_data && !Context.data.show_process_loader) { %>
    <div class="status-container">
    <div class="empty-state">
    <div class="empty-icon">⎈</div>
    <p class="empty-message">Нет доступных процессов</p>
    <button class="status-btn status-btn--primary" onclick="<%=Scripts%>.showElementInProcess()">
    <span class="status-btn__icon">↻</span>
    Получить процессы
    </button>
    </div>
    </div>
    <% } else if (!Context.data.old_element_id && !Context.data.show_process_loader) { %>
    <div class="status-container">
    <div class="prompt-state">
    <div class="prompt-icon">✏️</div>
    <p class="prompt-message">Введите id или выберите элемент</p>
    </div>
    </div>
    <% } %>
    Спойлер: Код поиска процессов
    Код:
    
    async function showElementInProcess(): Promise<void> {
        if (!
    Context.data.processes){
            
    await getProcesses2()
            if (!
    Context.data.processes) {
                
    console.error("Failed to load processes");
                return;
            }
        }
        
    Context.data.processes_data undefined;
        
    Context.data.show_process_loader true;
        
    let reversedResultReversedResultItemProcess[] = [];
        
    //@ts-ignore
        
    let user await Imports![Context.data.search_ns_code!]!.app[Context.data.search_app_code!].search()
            .
    where((emp: { __id: { eq: (arg0any) => any; }; }) => emp.__id.eq(Context.data.old_element_id!))
            .
    first();

        if (!
    user) {
            
    console.error("User not found");
            return;
        }

        
    // // Проходим по всем приложениям из apps_and_fields
        
    for (let app of Context.data.processes) {
            
    // У каждого приложения заходим в процессы
            
    for (let process of app.processes) {
                
    //Получаем id шаблона процесса для запроса экземпляров по API
                
    let template_id await getProcessTemplateId(Context.data.ns_code!, process.codeapp.app_code)
        
                if (!
    template_id) {
                    continue
                }
                
    //Для каждого поля процесса, где был найдено значение Context.data.search_app получаем экземпляры.
                
    for  (let field of process.filtered_context){
                    try {
                        
    let result await getProcessItems(template_idfield.code);
                        
    let elements result.result
                        
    // Обработка табличных полей
                        
    if (field.type === "TABLE") {
                            
    // Получаем информацию о колонках таблицы
                            
    let tableColumns field.data.fields.map((columnany) => ({
                                
    column_namecolumn.view.name,
                                
    column_codecolumn.code,
                                
    singlecolumn.single true false
                            
    }));
                            
    // Ищем колонки, связанные с employees
                            
    let employeeColumns field.data.fields.filter((colany) => col.data.code === Context.data.search_app_code!);
                            if (
    elements && elements.length 0){
                                
    // Фильтруем элементы по всем релевантным колонкам сразу
                                
    elements elements.filter((elemany) => {
                                    const 
    fieldValue elem[field.code];
                                    if (!
    fieldValue?.rows?.length) return false;
                            
                                    return 
    fieldValue.rows.some((rowany) => {
                                        return 
    employeeColumns.some((columnany) => {
                                            const 
    cellValue row[column.code];
                                            return Array.
    isArray(cellValue) && cellValue.some(id => id === user.id);
                                        });
                                    });
                                });
                            }
                            if (
    elements && elements.length 0){
                                
    // Добавляем найденные элементы в результат
                                
    for (let element of elements) {
                                    
    let existingElement reversedResult.find(el => el.id === element.__id);
                                    if (!
    existingElement) {
                                        
    existingElement createReversedResultElement(elementappprocesstemplate_id);
                                        
    reversedResult.push(existingElement);
                                    }
                                    
    addFieldToElement(existingElementfieldtableColumns);
                                }
                            }
                    
                        }
                        
    // Обработка обычных полей (не табличных)
                        
    else {
                            if (
    elements && elements.length 0){
                                
    elements elements.filter((elemany) =>
                                        
    //учёт множественной или одиночного типа не применяется, поскольку в API всё отображается в массиве
                                        
    elem[field.code]?.some((elem_idstring) => elem_id === user!.id)                        
                                    );
                                
    // Добавляем найденные элементы в результат
                                
    for (let element of elements) {
                                    
    let existingElement reversedResult.find(el => el.id === element.__id);
                            
                                    if (!
    existingElement) {
                                        
    existingElement createReversedResultElement(elementappprocesstemplate_id);
                                        
    reversedResult.push(existingElement);
                                    }
                                    
    addFieldToElement(existingElementfield);                        
                                }
                            }
                        }
            
                    } catch (
    error) {
                        
    console.error(`Error processing field ${field.code} in app ${app.app_code}:`, error);
                    }
                }
            }
        }

        
    console.log(reversedResult);
        const 
    element_tableElementTableProcess = {idContext.data.old_element_id!, app_listreversedResult }
        
    Context.data.processes_data element_table
        Context
    .data.show_process_loader false

        
    /** Вспомогательная функция для создания элемента результата */
        
    function createReversedResultElement(elementanyappanyprocessanytemplate_idstring) {
            return {
                
    idelement.__id,
                
    nameelement.__name,
                
    elementelement,
                
    app_codeapp.app_code,
                
    // app_name: app.app_name,
                
    template_idtemplate_id,
                
    template_codeprocess.code,
                
    template_nameprocess.__name,
                
    template_app_nameapp.app_name,
                
    fields: []
            };
        }

        
    /** Вспомогательная функция для добавления поля к элементу */
        
    function addFieldToElement(elementanyfieldanytableColumns?: any) {
            if (!
    element.fields.some((fany) => f.field_code === field.code)) {
                
    element.fields.push({
                    
    singlefield.single,
                    
    field_namefield.view.name,
                    
    field_codefield.code,
                    
    field_typefield.type,
                    ...(
    tableColumns && { table_columnstableColumns })
                });
            }
        }

        
    /**Получаем список элементов по template_id и фильтру */
        
    async function getProcessItems(template_id:stringfield_code:string): Promise<any>{
            const 
    token Context.data.token
            
    const myHeaders = {
                
    Authorization: `Bearer ${token}`,
            }
            const 
    body = {
                
    "active"true,
                
    "filter": {
                        
    "tf": {
                            
    "__state""wait"
                        
    }
                    },
                    
    "sortExpressions": [
                        {
                            
    "ascending"false
                        
    }
                    ],
                
    "from"0,
                
    //Здесь ограничение по колчеству запущенных процессов - максимум 10000
                
    "size"10000
            
    }
            const 
    requestOptions = {
                
    method"POST",
                
    headersmyHeaders,
                
    redirect"follow",
                
    bodyJSON.stringify(body)
            };
            const 
    response await fetch(`${Context.data.baseUrl}/pub/v1/bpm/instance/bytemplateid/${template_id}/list`, requestOptions)
            
    // const response = await fetch(`https://dev.elma.a101.ru/pub/v1/bpm/instance/${instance_id}/get`, requestOptions)
            
    let json await response.json()
            return 
    json.result
        
    }

        
    /** Получаем id шаблона процесса через запущенный процесс, если знаем код шаблона */
        
    async function getProcessTemplateId(ns_codestringprocess_codestringapp_code?: string): Promise<string undefined> {
            try {
                
    // Выбираем нужную коллекцию процессов
                //@ts-ignore
                
    const processesCollection app_code Imports![ns_code]?.app[app_code]?.processes Imports![ns_code]?.processes;

                if (!
    processesCollection) {
                    
    console.error(`Process collection not found for ns_code: ${ns_code}` +
                                (
    app_code ? `, app_code: ${app_code}` : ''));
                    return 
    undefined;
                }

                
    // Получаем первый экземпляр процесса
                
    const process processesCollection[process_code];
                const 
    example await process._searchInstances().first();
                if (!
    example) return undefined;

                
    // Получаем данные экземпляра
                
    const instanceData await getProcessesInstance(example.__id);
                return 
    instanceData.data.__template?.id;
            } catch (
    error) {
                
    console.error('Error getting process template ID:'error);
                return 
    undefined;
            }
        }

    }

    /** Получаем экземпляр процесса */
    async function getProcessesInstance(instance_idstring): Promise<any> {
        const 
    token Context.data.token;
        const 
    response await fetch(
            `${
    Context.data.baseUrl}/pub/v1/bpm/instance/${instance_id}/get`,
            {
                
    method"GET",
                
    headers: { Authorization: `Bearer ${token}` }
            }
        );
        return 
    await response.json();
    }
    [​IMG]

    Результат поиска отобразиться в таблице

    Спойлер: Код виджета таблицы
    Код:
    
    <div class="element-view">
        <% if (
    Context.data.processes_data) { %>
        <
    div style='display: flex; justify-content:space-between'>
            <
    span class="element-title">Данные по процессам и элементам</span>
            <
    button class="btn-refresh" onclick="<%=Scripts%>.showElementInProcess()">
                
    ↻ Получить процессы
            
    </button>
        </
    div>
        <% } %>
        <% if (
    Context.data.processes_data && Context.data.processes_data.app_list.length == 0) { %>
            <
    spanДанных не найдено</span>
        <% } %>
        <
    div id="processes_data">
            <% if (
    Context.data.processes_data && Context.data.processes_data.app_list) { %>
                <
    table class="element-table">
                    <
    thead>
                        <
    tr>
                            <
    th width='25%' class="element-th">Элемент</th>
                            <
    th width='75%' class="element-th">Приложение и поля</th>
                            <% if (
    Context.data.processes_data && Context.data.old_element_id == Context.data.processes_data.id && Context.data.new_element_id) { %>
                                <
    th width='20%' class="element-th">Действие</th>
                            <% } %>
                        </
    tr>
                    </
    thead>
                    <
    tbody>
                        <% for (const 
    elementData of Context.data.processes_data.app_list) { %>
                            <
    tr>
                                <
    td class="element-td">
                                    <
    a href="/admin/monitor/<%= elementData.template_id %>(p:history/<%= elementData.id %>) "
                                       
    target="_blank"
                                       
    class="element-link">
                                        <%= 
    elementData.name %>
                                    </
    a>
                                </
    td>
                                <
    td class="element-td">
                                    <
    div class="fields-container">
                                        <
    div class="app-info">
                                            <
    span>Приложение: <%= elementData.template_app_name %> (<%= elementData.app_code %>) </span>
                                            <
    span>Процесс: <%= elementData.template_name %> (<%= elementData.template_code %>)</span>
                                            </
    div>
                                        <% for (const 
    field of elementData.fields) { %>
                                            <
    div class="field-item">
                                                <
    div class="field-header">
                                                    <
    span class="field-code">
                                                    <%= 
    field.field_name %> (<%= field.field_code %>)
                                                    </
    span>
                                                    <
    span class="field-type"><%= field.field_type %></span>
                                                    <% if (
    field.field_type !== 'table') { %>
                                                        <% if (
    field.single) { %>
                                                            <
    span class="single-tag field-type"">SINGLE: true</span>
                                                        <% } %>
                                                        <% if (field.single == false) { %>
                                                            <span class="
    multiple-tag field-type"">SINGLEfalse</span>
                                                        <% } %>
                                                    <% } %>
                                                </
    div>
                                                <% if (
    field.field_type === 'TABLE' && field.table_columns) { %>
                                                    <
    div class="columns-container">
                                                        <% for (const 
    column of field.table_columns) { %>
                                                            <
    div class="column-tag">
                                                                <%= 
    column.column_name %> (<%= column.column_code %>)
                                                                <% if (
    column.single) { %>
                                                                    <
    span class="single-tag column-type"">SINGLE: true</span>
                                                                <% } %>
                                                                <% if (column.single == false) { %>
                                                                    <span class="
    multiple-tag column-type"">SINGLEfalse</span>
                                                                <% } %>
                                                                <!-- <
    span class="column-type">(single: <%= column.single 'true' 'false' %>)</span> -->
                                                            </
    div>
                                                        <% } %>
                                                    </
    div>
                                                <% } %>
                                            </
    div>
                                        <% } %>
                                    </
    div>
                                </
    td>
                            </
    tr>
                        <% } %>
                    </
    tbody>
                </
    table>
            <% } %>
        </
    div>
    </
    div>

    [​IMG]
    Последнее редактирование: 18 июн 2025 в 15:41
  8. Valentin Lysenko

    Valentin Lysenko Активный участник

    Также есть функционал замены элемента в процессах (в том числе и в таблице) и написаны бизнес-процессы, которые производят замену элемента массово, в приложениях и процессах.