import { formatHeaderUnit, formatHeaderYear } from './headerYearUtils.js'; import { getDataRowId } from './rowUtils.js'; // Достаёт вложенное поле из объекта строки таблицы export const getValueByPath = (row, path) => path.split('.').reduce((value, key) => value?.[key], row); const matchesValidationScope = (scope, { formType, sheetName }) => (!scope.formType || scope.formType === formType) && (!scope.sheetName || scope.sheetName === sheetName); // Пустой includedScopes означает «работает везде»; исключения приоритетнее. export const isValidationRuleEnabled = (rule, context = {}) => { const includedScopes = rule.includedScopes || []; const excludedScopes = rule.excludedScopes || []; if (excludedScopes.some((scope) => matchesValidationScope(scope, context))) return false; if (includedScopes.length && !includedScopes.some((scope) => matchesValidationScope(scope, context))) return false; return true; }; // Строит пути заголовков колонок export const buildColumnHeaderPaths = (columns = [], path = [], map = {}) => { for (const column of columns) { const nextPath = column.header ? [...path, column.header] : path; if (column.columns?.length) { buildColumnHeaderPaths(column.columns, nextPath, map); } else if (column.accessorKey) { map[column.accessorKey] = nextPath; } } return map; }; // AURORA-1893 (возврат 06.11): заголовки колонок в конфигах содержат плейсхолдеры // {year}/{year:short}/{unit} — подставляем их так же, как TableHead, чтобы в «Контроле // ошибок» и в тосте EditCell не было «План {year} ({unit})». export const formatHeaderPaths = (headerPaths, budgetYear, denomination) => { const formatted = {}; for (const [columnId, path] of Object.entries(headerPaths || {})) { formatted[columnId] = path.map((segment) => formatHeaderUnit(formatHeaderYear(segment, budgetYear), denomination)); } return formatted; }; // AURORA-1893 (возврат 06.11): ключ строки для ошибки и перехода к ней. У строк ввода // это line id, а у агрегатных строк (ROOT/GROUP/ITEM/SUB_ITEM) data.id нет — раньше ключ // у всех агрегатов одной колонки был null, они схлопывались в одну запись и переход к ним // не срабатывал. sort_order — глобальный ROW_NUMBER листа, уникален для каждой строки. export const getValidationRowKey = (row) => { const lineId = getDataRowId(row?.data); if (lineId !== null && lineId !== undefined) return lineId; if (row?.sort_order !== undefined && row?.sort_order !== null) return `sort:${row.sort_order}`; return null; }; // AURORA-1893: человекочитаемая подпись строки для меню плашки ошибок — // название статьи/ВСП, если есть, иначе номер строки (колонка «№»). const getRowLabel = (row) => { const data = row?.data; if (data?.header?.name) return data.header.name; if (data?.name) return data.name; if (row?.sort_order !== undefined && row?.sort_order !== null) return String(row.sort_order); return String(getDataRowId(data) ?? ''); }; // Обходит строки и собирает ошибки по правилу валидации — по записи на каждую // ошибочную ЯЧЕЙКУ (не одна запись на колонку, как было раньше), чтобы плашка // могла показать счётчик и перейти к конкретной строке. export const collectInvalidErrors = (rows, rule, errors, seen) => { for (const row of rows) { if (!rule.appliesToRow || rule.appliesToRow(row)) { for (const columnKey of rule.errorColumnKeys || rule.columnKeys) { if (!rule.isInvalid(getValueByPath(row, columnKey), row, columnKey)) continue; const lineId = getDataRowId(row.data); const rowKey = getValidationRowKey(row); const errorId = `${rule.id}:${columnKey}:${rowKey}`; if (seen.has(errorId)) continue; seen.add(errorId); errors.push({ id: errorId, ruleId: rule.id, columnId: columnKey, lineId, rowKey, rowLabel: getRowLabel(row), message: rule.message, }); } } collectInvalidErrors(row.subRows || [], rule, errors, seen); } }; // AURORA-1893: группирует ошибки по «правило + колонка» (как раньше отображалось // в плашке) и прикладывает к каждой группе список конкретных ячеек со счётчиком. export const groupValidationErrors = (errors) => { const groupsByKey = new Map(); for (const error of errors) { const key = `${error.ruleId}:${error.columnId}`; let group = groupsByKey.get(key); if (!group) { group = { id: key, ruleId: error.ruleId, columnId: error.columnId, message: error.message, cells: [], count: 0, }; groupsByKey.set(key, group); } group.cells.push(error); group.count += 1; } return [...groupsByKey.values()]; };