isComputedColumn/isCellEditable принимают formType; transfer_econ вычисляемый только на FORM_2, на FORM_4 остаётся редактируемым. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
6.0 KiB
JavaScript
113 lines
6.0 KiB
JavaScript
// AURORA-1890: редактируемость ячейки — чистая функция, вынесенная из
|
||
// tableColumns.jsx. Используется tableColumns.jsx (isEditable внутри
|
||
// getTableColumns) и будет использоваться AURORA-1891 — сигнатуру без
|
||
// необходимости не менять.
|
||
//
|
||
// isComputedColumn повторяет часть серверной проверки из
|
||
// v3._apply_form_cell и v3._apply_form3_cell
|
||
// (api/alembic/versions/sql/0023_apply_cell_empty_string.sql; функция
|
||
// переопределяется CREATE OR REPLACE в более новых миграциях, но список
|
||
// computed_field/normative_field исключений не менялся): какие колонки
|
||
// сервер отвергает при попытке правки через _apply_form_cell/_apply_form3_cell,
|
||
// те же колонки фронт не должен показывать как редактируемые — даже
|
||
// администратору, у которого isEditable иначе был бы всегда true на
|
||
// строках INPUT.
|
||
//
|
||
// Это фронтовый список, зашитый в коде (а не синхронизированный с БД
|
||
// автоматически) — умышленно, см. AURORA-1890. Расхождения чинятся отдельным
|
||
// PR, а не здесь.
|
||
|
||
// 0023:416-420 — IF v_field IN (...) THEN RAISE EXCEPTION 'computed_field: %'
|
||
// применяется к полям scope q1..q4, плюс FORM_3 (_apply_form3_cell, 0023:515)
|
||
// называет те же по смыслу поля иначе (quarter_actual вместо actual_quarter,
|
||
// total_corr — отдельное суммарное поле без аналога в FORM_1/2/4).
|
||
const QUARTER_COMPUTED_FIELDS = new Set([
|
||
'corrected_plan',
|
||
'new_plan',
|
||
'actual_quarter',
|
||
'booking',
|
||
'rem_booking',
|
||
'rem_actual',
|
||
'residual_after_booking',
|
||
'residual_after_actual',
|
||
'economy',
|
||
'total',
|
||
// FORM_3 / PROJECT CURRENT_EXPENSES (_apply_form3_cell, 0023:515-517)
|
||
'quarter_actual',
|
||
'total_corr',
|
||
]);
|
||
|
||
// 0023:200-219 — header.* редактируется через v3.budget_line, остальные
|
||
// поля header.* (year/section/item_id/num_group/vsp_address) — computed_field.
|
||
const HEADER_EDITABLE_FIELDS = new Set(['name', 'internal_order', 'justification', 'vsp_id']);
|
||
|
||
// 0023:154-155 — атрибуты ВСП на сателлитах AHR_RENT/AHR_UTILITY/AHR_SECURITY
|
||
// правятся через справочник ВСП, не через эту ячейку.
|
||
const VSP_ATTRIBUTE_FIELDS = new Set(['address', 'object_type', 'rented_area']);
|
||
|
||
// 0023:115-127 — лист лимитов AHR_LIMIT: amount_qN вычисляется из
|
||
// qty × limit × period_factor, limit_with_vat/limit_without_vat — нормативный
|
||
// справочник (правится админом отдельно, не через ячейку формы).
|
||
const LIMIT_SHEET_COMPUTED_FIELDS = new Set(['amount_q1', 'amount_q2', 'amount_q3', 'amount_q4', 'limit_with_vat', 'limit_without_vat']);
|
||
|
||
const QUARTER_SCOPE_PATTERN = /^q[1-4]$/;
|
||
const FACT_QUARTER_SCOPE_PATTERN = /^fact_q[1-4]$/;
|
||
|
||
const DATA_PREFIX = 'data.';
|
||
|
||
/**
|
||
* Повторяет серверный список computed_field/normative_field исключений
|
||
* v3._apply_form_cell / v3._apply_form3_cell для набора колонок, который
|
||
* знает фронт (FORM_1/2/4, PROJECT, сателлиты AHR_LIMIT/RENT/UTILITY/SECURITY,
|
||
* OTCH9F). columnId — это React Table column.id в формате accessorKey, т.е.
|
||
* всегда с префиксом "data." для полей строки (например "data.q1.booking").
|
||
*/
|
||
export const isComputedColumn = (columnId, formType) => {
|
||
if (typeof columnId !== 'string' || !columnId.startsWith(DATA_PREFIX)) return false;
|
||
|
||
const parts = columnId.slice(DATA_PREFIX.length).split('.');
|
||
const [scope, field] = parts;
|
||
|
||
// 0023:221-223 (FORM_1/2/4), 0023:515 (FORM_3) — approved.*/totals.* всегда вычисляемые.
|
||
if (scope === 'approved' || scope === 'totals') return true;
|
||
|
||
// 0023:200-219 — header.* кроме разрешённого списка.
|
||
if (scope === 'header') return !HEADER_EDITABLE_FIELDS.has(field);
|
||
|
||
if (parts.length === 2) {
|
||
// *.year — plan.year, seq_dfip.year, seq_ssp.year, reserve.year и т.п.
|
||
if (field === 'year') return true;
|
||
|
||
// qN.<field> из списка вычисляемых полей квартала.
|
||
if (QUARTER_SCOPE_PATTERN.test(scope) && QUARTER_COMPUTED_FIELDS.has(field)) return true;
|
||
|
||
// AURORA-1938: на FORM_2 («Смета РФ») перенос в фонд экономии считается на сервере
|
||
// (миграция 0063; _apply_form_cell отвергает запись для FORM_2). На FORM_4
|
||
// («Проектная деятельность») transfer_econ — обычное редактируемое поле.
|
||
if (formType === 'FORM_2' && QUARTER_SCOPE_PATTERN.test(scope) && field === 'transfer_econ') return true;
|
||
|
||
// fact_qN.total — сателлиты AHR_RENT/AHR_UTILITY/AHR_SECURITY.
|
||
if (FACT_QUARTER_SCOPE_PATTERN.test(scope) && field === 'total') return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
if (parts.length === 1) {
|
||
if (VSP_ATTRIBUTE_FIELDS.has(scope)) return true;
|
||
if (LIMIT_SHEET_COMPUTED_FIELDS.has(scope)) return true;
|
||
}
|
||
|
||
return false;
|
||
};
|
||
|
||
/**
|
||
* isCellEditable({ rowType, columnId, isAdmin, stageColumns, formType })
|
||
*
|
||
* rowType === 'INPUT' && !isComputedColumn(columnId, formType) && (isAdmin || stageColumns.has(columnId))
|
||
*
|
||
* stageColumns — Set с ключами вида "data.<column>" (columnsCurStage из
|
||
* useRealtimeData, отражающий окно текущего этапа пользователя).
|
||
*/
|
||
export const isCellEditable = ({ rowType, columnId, isAdmin, stageColumns, formType }) =>
|
||
rowType === 'INPUT' && !isComputedColumn(columnId, formType) && (isAdmin || stageColumns.has(columnId));
|