From 4675b461d8889ffafc5743476420dc253cdcddb6 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 3 Nov 2026 14:38:27 +0400 Subject: [PATCH] =?UTF-8?q?fix(table):=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BF=D1=80=D0=BE=D0=BF=D1=83=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=B8=D1=81=D1=85=D0=BE=D0=B4=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=87=D0=BD=D1=8F=20AURORA-1892=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D1=81=D0=B2=D0=B5=D1=80=D0=BA=D0=B5=2002.11.202?= =?UTF-8?q?6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit validation.js: добавлены колонки, пропущенные в первой реализации (разделы 5/6.1 сверки) — approved.*, collegial.approved, ckk.*, contract_detail.* (включая amount_foreign), corrected_plan, pay_ho/pay_rf, booking, rem_actual/ residual_after_actual, qN.total, new_plan, data.qN.economy. Отдельно добавлен transfer_econ формы 2 — явное исключение из общего префикса transfer_*, так как перечень Салмина прямо требует контроль минуса для этих колонок (раздел 6.3, противоречие с Word). Тест validationUtils.test.js обновлён под это исключение и дополнен проверкой полноты перечня и отсутствия дублей с remaining-booking. FORM_1/OPER.js, AHR.js, CAP.js: перестановка колонок Q4 — «Комментарий» теперь перед «Скорректированный план 4 кв.» во всех пяти таблицах (раздел 8 сверки). Не включено (требует отдельного решения заказчика, раздел 12.2 сверки): контроль месячного факта/СПОД, контроль факта расшифровочных листов сверх плановых колонок, область действия правила для формы 4. Co-Authored-By: Claude Sonnet 5 --- .../RealtimeTable/constants/FORM_1/AHR.js | 24 ++--- .../RealtimeTable/constants/FORM_1/CAP.js | 16 ++-- .../RealtimeTable/constants/FORM_1/OPER.js | 16 ++-- .../RealtimeTable/constants/validation.js | 94 ++++++++++++++++++- .../utils/validationUtils.test.js | 54 ++++++++++- 5 files changed, 174 insertions(+), 30 deletions(-) diff --git a/web/src/components/RealtimeTable/constants/FORM_1/AHR.js b/web/src/components/RealtimeTable/constants/FORM_1/AHR.js index 37aeb0f..f5ab8eb 100644 --- a/web/src/components/RealtimeTable/constants/FORM_1/AHR.js +++ b/web/src/components/RealtimeTable/constants/FORM_1/AHR.js @@ -3568,14 +3568,14 @@ export const config = { }, }, { - header: 'Скорректированный план 4 кв. {year:short}', - accessorKey: '4_kvartal_2026_skorr_plan_4_kv_26', + header: 'Комментарий', + accessorKey: '4_kvartal_2024_kommentariy', columns: [ { - header: 'Сумма, тыс. руб. (без НДС)', - accessorKey: 'data.q4.corrected_plan', - columnLetter: 'GH', - size: 250, + header: 'Сумма, тыс. руб. (с учётом НДС)', + accessorKey: 'data.q4.adj_comment', + columnLetter: 'GG', + size: 300, filterFn: 'contains', }, ], @@ -3587,14 +3587,14 @@ export const config = { }, }, { - header: 'Комментарий', - accessorKey: '4_kvartal_2024_kommentariy', + header: 'Скорректированный план 4 кв. {year:short}', + accessorKey: '4_kvartal_2026_skorr_plan_4_kv_26', columns: [ { - header: 'Сумма, тыс. руб. (с учётом НДС)', - accessorKey: 'data.q4.adj_comment', - columnLetter: 'GG', - size: 300, + header: 'Сумма, тыс. руб. (без НДС)', + accessorKey: 'data.q4.corrected_plan', + columnLetter: 'GH', + size: 250, filterFn: 'contains', }, ], diff --git a/web/src/components/RealtimeTable/constants/FORM_1/CAP.js b/web/src/components/RealtimeTable/constants/FORM_1/CAP.js index e8bce1b..6731fa5 100644 --- a/web/src/components/RealtimeTable/constants/FORM_1/CAP.js +++ b/web/src/components/RealtimeTable/constants/FORM_1/CAP.js @@ -3582,13 +3582,13 @@ export const config = { }, }, { - header: 'Скорректированный план 4 кв. {year:short}', - accessorKey: '4_kvartal_2024_skorr_plan_4_kv_24', + header: 'Комментарий', + accessorKey: '4_kvartal_2024_kommentariy', columns: [ { header: 'Сумма, тыс. руб. (с учётом НДС)', - accessorKey: 'data.q4.corrected_plan', - columnLetter: 'GH', + accessorKey: 'data.q4.adj_comment', + columnLetter: 'GG', size: 300, filterFn: 'contains', }, @@ -3601,13 +3601,13 @@ export const config = { }, }, { - header: 'Комментарий', - accessorKey: '4_kvartal_2024_kommentariy', + header: 'Скорректированный план 4 кв. {year:short}', + accessorKey: '4_kvartal_2024_skorr_plan_4_kv_24', columns: [ { header: 'Сумма, тыс. руб. (с учётом НДС)', - accessorKey: 'data.q4.adj_comment', - columnLetter: 'GG', + accessorKey: 'data.q4.corrected_plan', + columnLetter: 'GH', size: 300, filterFn: 'contains', }, diff --git a/web/src/components/RealtimeTable/constants/FORM_1/OPER.js b/web/src/components/RealtimeTable/constants/FORM_1/OPER.js index 2d1378e..4e5f839 100644 --- a/web/src/components/RealtimeTable/constants/FORM_1/OPER.js +++ b/web/src/components/RealtimeTable/constants/FORM_1/OPER.js @@ -3566,13 +3566,13 @@ export const config = { }, }, { - header: 'Скорректированный план 4 кв. {year:short}', - accessorKey: '4_kvartal_2024_skorr_plan_4_kv_24', + header: 'Комментарий', + accessorKey: '4_kvartal_2024_kommentariy', columns: [ { header: 'Сумма, тыс. руб. (с учётом НДС)', - accessorKey: 'data.q4.corrected_plan', - columnLetter: 'GH', + accessorKey: 'data.q4.adj_comment', + columnLetter: 'GG', size: 300, filterFn: 'contains', }, @@ -3585,13 +3585,13 @@ export const config = { }, }, { - header: 'Комментарий', - accessorKey: '4_kvartal_2024_kommentariy', + header: 'Скорректированный план 4 кв. {year:short}', + accessorKey: '4_kvartal_2024_skorr_plan_4_kv_24', columns: [ { header: 'Сумма, тыс. руб. (с учётом НДС)', - accessorKey: 'data.q4.adj_comment', - columnLetter: 'GG', + accessorKey: 'data.q4.corrected_plan', + columnLetter: 'GH', size: 300, filterFn: 'contains', }, diff --git a/web/src/components/RealtimeTable/constants/validation.js b/web/src/components/RealtimeTable/constants/validation.js index 197f6f9..1e62c14 100644 --- a/web/src/components/RealtimeTable/constants/validation.js +++ b/web/src/components/RealtimeTable/constants/validation.js @@ -88,7 +88,10 @@ const bookingLessBalanceRuleScope = { // Перечень — из вложения Салмина М.П. к AURORA-1892 (AURORA-1892_Перечень_колонок.md), // уточнённого кодовыми именами колонок (см. рулинги в PR). Корректировки и переносы // (adj_*, base_correction, revision_*, seq_*, target_change, transfer_*) сюда не входят — -// там минус — нормальная операция. +// там минус — нормальная операция. Исключение — transfer_econ (перенос в фонд экономии, +// формы 2): перечень Салмина прямо требует контроль для этих колонок (см. сверку +// AURORA-1892 от 02.11.2026, раздел 6.3); общий префикс transfer_* на этот ключ не +// распространяется. export const nonNegativeColumnKeys = [ // Основные листы форм 1, 2, 4 (Операц/АХР/КВ): план по кварталам и за год. "data.plan.q1", @@ -165,6 +168,95 @@ export const nonNegativeColumnKeys = [ "data.amount_q4", "data.limit_with_vat", "data.limit_without_vat", + + // Доработка по сверке AURORA-1892 от 02.11.2026 (разделы 5, 6.1): колонки из + // исходного перечня Салмина, пропущенные при первой реализации. + // Платежи следующих лет и сумма других ССП. + "data.contract_summary.future_y1", + "data.contract_summary.future_y2", + "data.contract_summary.other_ssp", + + // Утверждённый план по кварталам и за год. + "data.approved.q1", + "data.approved.q2", + "data.approved.q3", + "data.approved.q4", + "data.approved.year", + + // Сумма коллегиального органа. + "data.collegial.approved", + + // Расходы ЦКК по кварталам и по сметам РФ. + "data.ckk.q1", + "data.ckk.q2", + "data.ckk.q3", + "data.ckk.q4", + "data.ckk.rf_schedule", + + // Расходы договора по кварталам, по сметам РФ и сумма в валюте. + "data.contract_detail.q1", + "data.contract_detail.q2", + "data.contract_detail.q3", + "data.contract_detail.q4", + "data.contract_detail.rf_schedule", + "data.contract_detail.amount_foreign", + + // Скорректированный план по кварталам. + "data.q1.corrected_plan", + "data.q2.corrected_plan", + "data.q3.corrected_plan", + "data.q4.corrected_plan", + + // Части платежа ГО и РФ. + "data.q1.pay_ho", + "data.q1.pay_rf", + "data.q2.pay_ho", + "data.q2.pay_rf", + "data.q3.pay_ho", + "data.q3.pay_rf", + "data.q4.pay_ho", + "data.q4.pay_rf", + + // Бронь квартала (отдельно от ввода брони на 2026/2027 годы выше). + "data.q1.booking", + "data.q2.booking", + "data.q3.booking", + "data.q4.booking", + + // Остаток после факта (формы 1 и 2 используют разные кодовые имена). + "data.q1.rem_actual", + "data.q2.rem_actual", + "data.q3.rem_actual", + "data.q4.rem_actual", + "data.q1.residual_after_actual", + "data.q2.residual_after_actual", + "data.q3.residual_after_actual", + "data.q4.residual_after_actual", + + // Перенос в фонд экономии формы 1 (data.economy — другой ключ, чем transfer_econ + // формы 2 ниже, и не входит в исключённый префикс transfer_*). + "data.q1.economy", + "data.q2.economy", + "data.q3.economy", + "data.q4.economy", + + // Перенос в фонд экономии формы 2 — исключение из transfer_*, см. комментарий + // к nonNegativeColumnKeys выше. + "data.q1.transfer_econ", + "data.q2.transfer_econ", + "data.q3.transfer_econ", + "data.q4.transfer_econ", + + // Всего при закрытии квартала. + "data.q1.total", + "data.q2.total", + "data.q3.total", + "data.q4.total", + + // Новый план на подпись (кварталов II–IV; для I квартала поле отсутствует). + "data.q2.new_plan", + "data.q3.new_plan", + "data.q4.new_plan", ]; const isNegativeNumber = (value) => diff --git a/web/src/components/RealtimeTable/utils/validationUtils.test.js b/web/src/components/RealtimeTable/utils/validationUtils.test.js index 6989933..0d9782c 100644 --- a/web/src/components/RealtimeTable/utils/validationUtils.test.js +++ b/web/src/components/RealtimeTable/utils/validationUtils.test.js @@ -51,11 +51,15 @@ test('a scoped rule is disabled outside its form/sheet scope', () => { assert.equal(isValidationRuleEnabled(rule, { formType: 'FORM_2', sheetName: 'AHR' }), false); }); -test('the shared nonNegativeColumnKeys list excludes correction and transfer columns', () => { +test('the shared nonNegativeColumnKeys list excludes correction and transfer columns, except the explicit transfer_econ carve-out', () => { const excludedPrefixes = ['adj_', 'base_correction', 'revision_', 'seq_', 'target_change', 'transfer_']; + // AURORA-1892, сверка 02.11.2026 (раздел 6.3): перечень Салмина прямо требует контроль + // минуса для transfer_econ, в отличие от остальных transfer_* — это намеренное исключение. + const allowedException = 'transfer_econ'; for (const key of nonNegativeColumnKeys) { const segment = key.split('.').pop(); + if (segment === allowedException) continue; for (const prefix of excludedPrefixes) { assert.equal( segment.startsWith(prefix), @@ -65,3 +69,51 @@ test('the shared nonNegativeColumnKeys list excludes correction and transfer col } } }); + +test('nonNegativeColumnKeys has no duplicate entries', () => { + assert.equal(nonNegativeColumnKeys.length, new Set(nonNegativeColumnKeys).size); +}); + +test('nonNegativeColumnKeys covers the gaps confirmed by the AURORA-1892 audit (02.11.2026)', () => { + const mustInclude = [ + // Разделы 5/6.1 сверки: обязательные показатели, пропущенные в первой реализации. + 'data.contract_summary.future_y1', + 'data.contract_summary.other_ssp', + 'data.approved.q1', + 'data.approved.year', + 'data.collegial.approved', + 'data.ckk.q1', + 'data.ckk.rf_schedule', + 'data.contract_detail.q1', + 'data.contract_detail.amount_foreign', + 'data.q4.corrected_plan', + 'data.q1.pay_ho', + 'data.q1.pay_rf', + 'data.q1.booking', + 'data.q1.rem_actual', + 'data.q1.residual_after_actual', + 'data.q4.total', + 'data.q2.new_plan', + // Раздел 6.3: исключение из transfer_* по прямому требованию перечня Салмина. + 'data.q1.transfer_econ', + 'data.q4.transfer_econ', + // Раздел 5: экономия формы 1 — другой ключ, чем transfer_econ, не затронут исключением. + 'data.q1.economy', + ]; + + for (const key of mustInclude) { + assert.ok(nonNegativeColumnKeys.includes(key), `${key} must be in nonNegativeColumnKeys`); + } + + // Д-статус из раздела 5/6.1: эти остатки уже покрыты правилом remaining-booking + // и не должны дублироваться здесь (иначе пользователь увидит две причины ошибки). + const mustExclude = [ + 'data.q1.rem_booking', + 'data.q4.rem_booking', + 'data.q1.residual_after_booking', + 'data.q4.residual_after_booking', + ]; + for (const key of mustExclude) { + assert.ok(!nonNegativeColumnKeys.includes(key), `${key} is already covered by remaining-booking and must not duplicate here`); + } +});