«Удалить строку» на строке программы (GROUP) или проекта (ITEM) ничего не делала: у этих строк нет line_id, окно подтверждения не открывалось, а события program_deleted / project_deleted, которые сервер уже поддерживает, фронт не отправлял. - getDeleteTarget: по выделенной строке выбирает программу, проект или строку бюджета; для непустых программы/проекта сразу показывает причину. - RealtimeContext: deleteProgram / deleteProject и обработка ответов. - useRealtimeData: строка убирается из дерева, пересчёт ROOT/GROUP из ответа сопоставляется по section_code / program_id / project_id (alignNodeRows), а не по sort_order — сервер нумерует строки заново, и у листа без строк бюджета нет ROOT (найдено при проверке на стенде). - Окно подтверждения называет удаляемую программу/проект. - getWsErrorMessage срезает префикс "<class 'asyncpg...'>: " — иначе отказ сервера (программа с проектами у устаревшего клиента) показывался с ним. Refs #303 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
3.3 KiB
JavaScript
97 lines
3.3 KiB
JavaScript
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;
|
||
}
|