const ROW_TYPE_LEVELS = { ROOT: 0, GROUP: 1, ITEM: 2, SUB_ITEM: 3, INPUT: 4, }; export function buildRowHierarchy(items, mapItem = (item) => item) { const result = []; const stack = []; for (const [index, item] of items.entries()) { const currentLevel = ROW_TYPE_LEVELS[item.row_type] ?? 999; while (stack.length > 0 && (ROW_TYPE_LEVELS[stack.at(-1).row_type] ?? 999) >= currentLevel) { stack.pop(); } const parent = stack.at(-1); const newNode = { ...mapItem(item, index), subRows: [] }; if (parent) parent.subRows.push(newNode); else result.push(newNode); if (item.row_type !== 'INPUT') stack.push(newNode); } return result; } export function getDataRowId(data) { return data?.id || data?.line_id || null; } export function getRowId(row) { return getDataRowId(row.original.data); } // AURORA-1978: что удаляет кнопка «Удалить строку» для выделенной строки. // На форме 4 у строк программы (GROUP) и проекта (ITEM) нет line_id — их // удаляют отдельные события program_deleted / project_deleted по id из header. // Непустые программу/проект сервер не удалит (v3.del_form4_program / // v3.del_form4_project), поэтому причину показываем сразу, без запроса. // rowData — row.original (row_type, data, subRows). export function getDeleteTarget(rowData, { isForm4 = false } = {}) { if (!rowData) return null; const header = rowData.data?.header; const hasChildren = Array.isArray(rowData.subRows) && rowData.subRows.length > 0; if (isForm4 && rowData.row_type === 'GROUP' && header?.program_id != null) { return { kind: 'program', id: header.program_id, name: header.name ?? '', blockedReason: hasChildren ? 'Нельзя удалить программу: в ней есть проекты или расходы' : null, }; } if (isForm4 && rowData.row_type === 'ITEM' && header?.project_id != null) { return { kind: 'project', id: header.project_id, name: header.name ?? '', blockedReason: hasChildren ? 'Нельзя удалить проект: в нём есть расходы' : null, }; } const id = getDataRowId(rowData.data); return id == null ? null : { kind: 'row', id }; } export function isVspRow(row) { if (row?.original?.data?.id) { return true; } return false; } export function isVspNewRow(rowData) { if (!rowData) return false; const newRow = rowData[3]; return !!newRow.id; } export function getParentId(data) { return data?.project_id || data?.expense_item_id || null; } // AURORA-1887: лист AHR отдаёт header.can_add_row=false для статей // аренды/коммуналки/охраны (у записи сателлита нет понятия "сверх лимита" — // ручная строка там не имеет смысла). На остальных листах и для остальных // статей (включая лимитные — AURORA-1907 "сверх лимита" не трогаем) поле // либо true, либо вовсе отсутствует/null — по умолчанию добавление строки // разрешено. export function canAddRowForHeader(header) { return header?.can_add_row !== false; }