AURORA-1984: проекты ПД без программы (+1979, 1980, 1989) #306
@ -30,7 +30,7 @@ const CreateProgramModalComponent = ({ isOpen, onClose, onCreate, title = '', is
|
||||
onClose();
|
||||
};
|
||||
|
||||
const isFormValid = name !== '';
|
||||
const isFormValid = name.trim() !== '';
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ import { useValidationRules } from './hooks/useValidationRules';
|
||||
import { getColumnVirtualizerOptions, getOrderedVisibleColumns } from './utils/columnLayoutUtils';
|
||||
import { isCellEditable } from './utils/cellEditability';
|
||||
import { findNextEditableColumnId, findNextEditableRowId } from './utils/cellNavigation';
|
||||
import { canAddRowForHeader, getDeleteTarget } from './utils/rowUtils';
|
||||
import { canAddRowForHeader, getDeleteTarget, getProjectAddTarget } from './utils/rowUtils';
|
||||
import { clearLegacyFormulaKeys } from './utils/localStorageUtils';
|
||||
import { formatHeaderPaths, getValidationRowKey } from './utils/validationUtils';
|
||||
|
||||
@ -931,9 +931,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
|
||||
const handleAddProjectRow = useCallback(
|
||||
(name) => {
|
||||
const row = getSelectedRow();
|
||||
if (!row) return;
|
||||
contextAddProject({ name, program_id: row.original.data.header.program_id });
|
||||
contextAddProject({ name: name.trim(), ...getProjectAddTarget(getSelectedRow()?.original) });
|
||||
},
|
||||
[contextAddProject, getSelectedRow],
|
||||
);
|
||||
@ -1015,17 +1013,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
||||
toast.error('Добавление строк недоступно: нет доступных для редактирования столбцов');
|
||||
return;
|
||||
}
|
||||
const row = getSelectedRow();
|
||||
if (!row) {
|
||||
toast.error('Выделите строку для вставки');
|
||||
return;
|
||||
}
|
||||
if (row.original.row_type !== 'GROUP') {
|
||||
toast.error('Выделите строку программы');
|
||||
return;
|
||||
}
|
||||
modalsRef.current?.open('project');
|
||||
}, [canModifyRows, getSelectedRow]);
|
||||
}, [canModifyRows]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
deleteRow,
|
||||
insertCell,
|
||||
insertProjectRow,
|
||||
insertStandaloneProjectRow,
|
||||
updateCells,
|
||||
} from '../utils/cellUtils';
|
||||
import { buildRowHierarchy, getParentId, isVspNewRow } from '../utils/rowUtils';
|
||||
@ -150,9 +151,14 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
||||
return;
|
||||
}
|
||||
setData((prevData) => {
|
||||
const updateData = updateCells(prevData, updatedCells);
|
||||
const newData = insertProjectRow(updateData, newRow, program_id);
|
||||
return newData;
|
||||
let base = structuredClone(prevData);
|
||||
const rootRow = updates.find((row) => row[0] === 'ROOT');
|
||||
if (base[0]?.row_type !== 'ROOT' && rootRow) {
|
||||
base = addRootRow(base, rootRow);
|
||||
}
|
||||
const updateData = updateCells(base, alignNodeRows(base, updatedCells));
|
||||
if (program_id != null) return insertProjectRow(updateData, newRow, program_id);
|
||||
return insertStandaloneProjectRow(updateData, newRow);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -284,6 +284,30 @@ export function insertProjectRow(data, newRow, program_id) {
|
||||
return updatedRows;
|
||||
}
|
||||
|
||||
// AURORA-1984: самостоятельный проект (без программы) — под первый ROOT или на
|
||||
// верхний уровень, перед первой строкой списка с большим sort_order (исторические
|
||||
// INPUT идут после проектов). Номера строк с sort_order >= нового сдвигаются.
|
||||
export function insertStandaloneProjectRow(data, newRow) {
|
||||
const updatedRows = structuredClone(data);
|
||||
const [row_type, depth, sort_order, dataRow] = newRow;
|
||||
const newRowObject = { row_type, depth, sort_order, subRows: [], data: dataRow };
|
||||
|
||||
const shift = (rows) => {
|
||||
for (const row of rows) {
|
||||
if (row.sort_order >= sort_order) row.sort_order += 1;
|
||||
if (Array.isArray(row.subRows)) shift(row.subRows);
|
||||
}
|
||||
};
|
||||
shift(updatedRows);
|
||||
|
||||
const root = updatedRows.find((row) => row.row_type === 'ROOT');
|
||||
if (root && !Array.isArray(root.subRows)) root.subRows = [];
|
||||
const target = root ? root.subRows : updatedRows;
|
||||
const index = target.findIndex((row) => row.sort_order > sort_order);
|
||||
target.splice(index === -1 ? target.length : index, 0, newRowObject);
|
||||
return updatedRows;
|
||||
}
|
||||
|
||||
export function deleteRow(data, lineId) {
|
||||
if (lineId == null) return data;
|
||||
return deleteRowWhere(data, (row) => getDataRowId(row.data) === lineId);
|
||||
|
||||
@ -1,6 +1,15 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { test } from 'node:test';
|
||||
import { alignNodeRows, deleteProgramRow, deleteProjectRow, deleteRow, updateCells } from './cellUtils.js';
|
||||
import {
|
||||
addRootRow,
|
||||
alignNodeRows,
|
||||
deleteProgramRow,
|
||||
deleteProjectRow,
|
||||
deleteRow,
|
||||
insertCell,
|
||||
insertStandaloneProjectRow,
|
||||
updateCells,
|
||||
} from './cellUtils.js';
|
||||
|
||||
// Эталон: прежняя реализация updateCells (до AURORA-1838).
|
||||
function oldUpdateCells(data, newRows) {
|
||||
@ -385,3 +394,49 @@ test('AURORA-1978: alignNodeRows — ROOT по section_code, строки без
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
const node = (rowType, sortOrder, header = {}, subRows = []) => ({
|
||||
row_type: rowType,
|
||||
depth: { ROOT: 0, GROUP: 1, ITEM: 2, INPUT: 3 }[rowType],
|
||||
sort_order: sortOrder,
|
||||
subRows,
|
||||
data: { header },
|
||||
});
|
||||
const newItem = (sortOrder) => ['ITEM', 2, sortOrder, { header: { project_id: 77, name: 'Н' } }];
|
||||
|
||||
test('AURORA-1984: самостоятельный проект — под ROOT, после соседей', () => {
|
||||
const result = insertStandaloneProjectRow([node('ROOT', 1, {}, [node('GROUP', 2)])], newItem(3));
|
||||
assert.deepEqual(
|
||||
result[0].subRows.map((r) => [r.row_type, r.sort_order]),
|
||||
[
|
||||
['GROUP', 2],
|
||||
['ITEM', 3],
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('AURORA-1984: самостоятельный проект — на верхний уровень без ROOT', () => {
|
||||
const result = insertStandaloneProjectRow([node('GROUP', 1)], newItem(2));
|
||||
assert.deepEqual(
|
||||
result.map((r) => r.row_type),
|
||||
['GROUP', 'ITEM'],
|
||||
);
|
||||
});
|
||||
|
||||
test('AURORA-1984: самостоятельный проект — перед историческим расходом, номера сдвигаются', () => {
|
||||
const result = insertStandaloneProjectRow([node('ROOT', 1, {}, [node('GROUP', 2), node('INPUT', 3)])], newItem(3));
|
||||
assert.deepEqual(
|
||||
result[0].subRows.map((r) => [r.row_type, r.sort_order]),
|
||||
[
|
||||
['GROUP', 2],
|
||||
['ITEM', 3],
|
||||
['INPUT', 4],
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('AURORA-1984: insertCell после addRootRow — первый расход самостоятельного проекта', () => {
|
||||
const withRoot = addRootRow([node('ITEM', 1, { project_id: 21 })], ['ROOT', 0, 1, { header: { expense_item_id: 900 } }]);
|
||||
const result = insertCell(withRoot, ['INPUT', 3, 3, { line_id: 5, header: { project_id: 21 } }], 21);
|
||||
assert.equal(result[0].subRows[0].subRows[0].data.line_id, 5);
|
||||
});
|
||||
|
||||
@ -69,6 +69,15 @@ export function getDeleteTarget(rowData, { isForm4 = false } = {}) {
|
||||
return id == null ? null : { kind: 'row', id };
|
||||
}
|
||||
|
||||
// AURORA-1984: цель добавления проекта по выделенной строке. Программа задана
|
||||
// только если у GROUP/ITEM/INPUT header.program_id != null; иначе проект
|
||||
// самостоятельный. rowData — row.original.
|
||||
export function getProjectAddTarget(rowData) {
|
||||
const programId = rowData?.data?.header?.program_id;
|
||||
const typed = ['GROUP', 'ITEM', 'INPUT'].includes(rowData?.row_type);
|
||||
return typed && programId != null ? { program_id: programId } : {};
|
||||
}
|
||||
|
||||
export function isVspRow(row) {
|
||||
if (row?.original?.data?.id) {
|
||||
return true;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { buildRowHierarchy, canAddRowForHeader, getDataRowId, getDeleteTarget } from './rowUtils.js';
|
||||
import { buildRowHierarchy, canAddRowForHeader, getDataRowId, getDeleteTarget, getProjectAddTarget } from './rowUtils.js';
|
||||
|
||||
test('row hierarchy uses row types when depth skips a level and keeps INPUT rows as siblings', () => {
|
||||
const rows = [
|
||||
@ -87,3 +87,13 @@ test('AURORA-1978: getDeleteTarget — строка бюджета и прочи
|
||||
assert.equal(getDeleteTarget(form4Row('ROOT', { section_code: 'S' }), { isForm4: true }), null);
|
||||
assert.equal(getDeleteTarget(null, { isForm4: true }), null);
|
||||
});
|
||||
|
||||
test('AURORA-1984: getProjectAddTarget — цель по выделенной строке', () => {
|
||||
assert.deepEqual(getProjectAddTarget(undefined), {});
|
||||
assert.deepEqual(getProjectAddTarget(form4Row('ROOT', { section_code: 'CAP' })), {});
|
||||
assert.deepEqual(getProjectAddTarget(form4Row('GROUP', { program_id: 11 })), { program_id: 11 });
|
||||
assert.deepEqual(getProjectAddTarget(form4Row('ITEM', { program_id: 11, project_id: 21 })), { program_id: 11 });
|
||||
assert.deepEqual(getProjectAddTarget(form4Row('ITEM', { program_id: null, project_id: 21 })), {});
|
||||
assert.deepEqual(getProjectAddTarget(form4Row('ITEM', { project_id: 21 })), {});
|
||||
assert.deepEqual(getProjectAddTarget(form4Row('INPUT', { program_id: 11, project_id: 21 })), { program_id: 11 });
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user