AURORA-1902: Смета РФ — выгрузка Операц/АХР/КВ своей раскладкой #259

Open
aep wants to merge 1 commits from feat/252-aurora-1902-form2-export-layout into test
Owner

Closes #252.

Проблема

Листы Операц/АХР/КВ формы «Смета РФ» (FORM_2) выгружались раскладкой формы 1 — из 163 колонок экрана совпадали по ключу только 83, 97 колонок выгрузки были всегда пустыми, заголовки групп — от формы 1. На экране также был лишний столбец «Адрес ВСП» (его нет в исходной форме РФ), и ВСП выгружался бы числовым id, а не номером как на экране.

Что сделано

  • FORM2_DEPTH_COLUMNS/FORM2_COLORS (export_mappings.py) — дерево колонок и цвета для Операц/АХР/КВ, сгенерированы из web/src/.../FORM_2/{OPER,AHR,CAP}.js. 162 листа — тот же порядок, что на экране, минус header.vsp_address. Каждая группа — чисто визуальная обёртка для Excel-заголовков ("key": None), каждый лист несёт полный путь к данным как явный "key" (например "booking.y2027.q1"), поэтому отображение колонок в несколько визуальных групп не влияет на то, откуда реально берётся значение.
  • Form2DepthRowNormalizer — в отличие от Form1DepthRowNormalizer, не поднимает header.* на верхний уровень (FORM_2 ожидает header.section_code, header.vsp_id и т.д., а не плоские section/vsp_id).
  • _create_xlsxwriter_sheet_writers: выделена отдельная ветка для FormTypeEnum.FORM_2 (раньше FORM_2 попадала в общий else вместе с FORM_1 — в этом и была причина бага). Сателлиты АХР (AHR_RENT/AHR_UTILITY/AHR_SECURITY, относятся к AURORA-1904) и STRUCTURE не трогал.
  • VSPRepository.get_reg_numbers_by_ids + ExportService._resolve_form2_vsp_numbers: один запрос резолвит все встретившиеся header.vsp_id (включая закрытые ВСП) в registration_number перед записью в Excel.
  • Убран столбец «Адрес ВСП» (E1) и его цвет из FORM_2/{OPER,AHR,CAP}.js (по умолчанию из тикета — см. Rulings), убрана мёртвая ссылка на него в Stages/constants.js.

Rulings (решения без обсуждения)

  1. «Лишний столбец» = «Адрес ВСП» (E1), по умолчанию, указанному в самом тикете (вопрос заказчику без ответа) — убрал его со всех трёх листов и из выгрузки.
  2. Дерево и цвета сгенерированы скриптом (Node, разбор реального OPER.js/AHR.js/CAP.js), а не вручную — так и предлагал тикет. Корректность порядка листьев проверена программно по алгоритму самого XlsxDepthSheetWriter._columns_dict_to_flat (не только моей копией, но и реальным классом — см. тесты), а идентичность колонок между OPER/AHR/CAP — сверкой списков ключей всех трёх файлов, а не на слово из тикета.
  3. Все группы в FORM2_DEPTH_COLUMNS — "key": None, все листья несут полный путь явным "key". Отступил от подхода FORM1_DEPTH_COLUMNS (где часть пути собирается из вложенности групп), потому что у FORM_2 несколько визуально различных групп (Корректировки/Согласование платежей/Фактические расходы и т.д.) пишут в один и тот же data.q{n}.* префикс — с «честной» вложенностью это дало бы коллизию имён у дочерних групп. Явный путь на каждом листе проще и надёжнее при ручной сверке.
  4. Цвета тела ячеек (FORM2_COLORS, "palette") взяты по инструкции тикета (greenColumn → "green" и т.д., переиспользуется существующий PALETTE) — это красит ЯЧЕЙКИ СТРОК по _set_palette/palette, а не фон заголовков групп (_header_format, который адресуется ПУТЁМ ПО СЫРЫМ КЛЮЧАМ СЛОВАРЯ, а не플 плоским ключом). Фон самих заголовков групп Excel я не настраивал — в FORM1_COLORS/FORM4_COLORS это тоже не было явной частью контракта, а в критерии приёмки тикета заголовки-цвета не упоминаются (только порядок, заголовки-текст и значения).
  5. VSP из закрытых ВСП тоже резолвится (фильтр только по is_deleted, не по is_active) — так просил тикет явно.

Тесты

  • api/tests/unit/test_export_form2_mapping.py (новый, 11 тестов): порядок листьев FORM2_DEPTH_COLUMNS через реальный XlsxDepthSheetWriter совпадает с 162 ключами экрана; у всех трёх листов есть цвет на каждый лист; render_column_years резолвит {year}/{year+1} в заголовках групп; _create_xlsxwriter_sheet_writers(..., FORM_2, ...) отдаёт раскладку FORM_2 (и явно НЕ совпадает с FORM_1 — _create_xlsxwriter_sheet_writers(..., FORM_1, ...) по-прежнему отдаёт FORM1_DEPTH_COLUMNS); Form2DepthRowNormalizer не поднимает header.*; _resolve_form2_vsp_numbers подменяет id на номер одним запросом и ничего не делает, если vsp_id нет вовсе.
  • pytest tests/unit — 69 passed, 2 failed, оба failed не связаны с этой правкой (test_export_service_query_params.py, экспорт SMETA/ignore_non_applicable_query_params — я его не трогал, те же 2 падения на test).
  • Интеграционные тесты и реальная выгрузка Excel — НЕ ЗАПУСКАЛИСЬ: в этом окружении нет Postgres. Структура колонок и подстановка ВСП проверены юнит-тестами (включая прогон через реальный XlsxDepthSheetWriter), но визуально файл .xlsx я не открывал и не видел — нужна ручная проверка на стенде с БД.
  • Frontend: node --test src/components/RealtimeTable/utils/*.test.js — 8/8. npm run build — без ошибок. Biome на изменённых файлах (OPER.js/AHR.js/CAP.js/Stages/constants.js) — то же число предсуществу��щих warning'ов, что в test (не добавил новых).

Ручная проверка (acceptance criteria, не автоматизировано)

  • Экспортировать Операц/АХР/КВ любой формы «Смета РФ» — колонки, их порядок и заголовки групп совпадают с экраном; значений формы 1 в выгрузке нет.
  • Значения в колонках выгрузки совпадают со значениями на экране.
  • Колонка «ВСП РФ» в выгрузке содержит номер ВСП (включая закрытые ВСП), а не числовой id.
  • На экране и в выгрузке нет столбца «Адрес ВСП» — если заказчик имел в виду другой столбец, его нужно будет вернуть.
  • Выгрузка формы 1 (Смета ГО) не изменилась.

Не в объёме

AURORA-1904 (AHR_LIMIT и сателлиты) — отдельная задача, не трогал AHR_RENT/AHR_UTILITY/AHR_SECURITY. AURORA-1900 (подписи «тыс. руб.» в заголовках).

🤖 Generated with Claude Code

Closes #252. ## Проблема Листы Операц/АХР/КВ формы «Смета РФ» (FORM_2) выгружались раскладкой формы 1 — из 163 колонок экрана совпадали по ключу только 83, 97 колонок выгрузки были всегда пустыми, заголовки групп — от формы 1. На экране также был лишний столбец «Адрес ВСП» (его нет в исходной форме РФ), и ВСП выгружался бы числовым id, а не номером как на экране. ## Что сделано - `FORM2_DEPTH_COLUMNS`/`FORM2_COLORS` (`export_mappings.py`) — дерево колонок и цвета для Операц/АХР/КВ, сгенерированы из `web/src/.../FORM_2/{OPER,AHR,CAP}.js`. 162 листа — тот же порядок, что на экране, минус `header.vsp_address`. Каждая группа — чисто визуальная обёртка для Excel-заголовков (`"key": None`), каждый лист несёт полный путь к данным как явный `"key"` (например `"booking.y2027.q1"`), поэтому отображение колонок в несколько визуальных групп не влияет на то, откуда реально берётся значение. - `Form2DepthRowNormalizer` — в отличие от `Form1DepthRowNormalizer`, не поднимает `header.*` на верхний уровень (FORM_2 ожидает `header.section_code`, `header.vsp_id` и т.д., а не плоские `section`/`vsp_id`). - `_create_xlsxwriter_sheet_writers`: выделена отдельная ветка для `FormTypeEnum.FORM_2` (раньше FORM_2 попадала в общий `else` вместе с FORM_1 — в этом и была причина бага). Сателлиты АХР (`AHR_RENT`/`AHR_UTILITY`/`AHR_SECURITY`, относятся к AURORA-1904) и `STRUCTURE` не трогал. - `VSPRepository.get_reg_numbers_by_ids` + `ExportService._resolve_form2_vsp_numbers`: один запрос резолвит все встретившиеся `header.vsp_id` (включая закрытые ВСП) в `registration_number` перед записью в Excel. - Убран столбец «Адрес ВСП» (E1) и его цвет из `FORM_2/{OPER,AHR,CAP}.js` (по умолчанию из тикета — см. Rulings), убрана мёртвая ссылка на него в `Stages/constants.js`. ## Rulings (решения без обсуждения) 1. **«Лишний столбец» = «Адрес ВСП» (E1)**, по умолчанию, указанному в самом тикете (вопрос заказчику без ответа) — убрал его со всех трёх листов и из выгрузки. 2. **Дерево и цвета сгенерированы скриптом** (Node, разбор реального `OPER.js`/`AHR.js`/`CAP.js`), а не вручную — так и предлагал тикет. Корректность порядка листьев проверена программно по алгоритму самого `XlsxDepthSheetWriter._columns_dict_to_flat` (не только моей копией, но и реальным классом — см. тесты), а идентичность колонок между OPER/AHR/CAP — сверкой списков ключей всех трёх файлов, а не на слово из тикета. 3. **Все группы в FORM2_DEPTH_COLUMNS — `"key": None`, все листья несут полный путь явным `"key"`.** Отступил от подхода FORM1_DEPTH_COLUMNS (где часть пути собирается из вложенности групп), потому что у FORM_2 несколько визуально различных групп (Корректировки/Согласование платежей/Фактические расходы и т.д.) пишут в один и тот же `data.q{n}.*` префикс — с «честной» вложенностью это дало бы коллизию имён у дочерних групп. Явный путь на каждом листе проще и надёжнее при ручной сверке. 4. **Цвета тела ячеек (`FORM2_COLORS`, `"palette"`) взяты по инструкции тикета** (`greenColumn` → `"green"` и т.д., переиспользуется существующий `PALETTE`) — это красит ЯЧЕЙКИ СТРОК по `_set_palette`/`palette`, а не фон заголовков групп (`_header_format`, который адресуется ПУТЁМ ПО СЫРЫМ КЛЮЧАМ СЛОВАРЯ, а не플 плоским ключом). Фон самих заголовков групп Excel я не настраивал — в `FORM1_COLORS`/`FORM4_COLORS` это тоже не было явной частью контракта, а в критерии приёмки тикета заголовки-цвета не упоминаются (только порядок, заголовки-текст и значения). 5. **VSP из закрытых ВСП тоже резолвится** (фильтр только по `is_deleted`, не по `is_active`) — так просил тикет явно. ## Тесты - `api/tests/unit/test_export_form2_mapping.py` (новый, 11 тестов): порядок листьев `FORM2_DEPTH_COLUMNS` через реальный `XlsxDepthSheetWriter` совпадает с 162 ключами экрана; у всех трёх листов есть цвет на каждый лист; `render_column_years` резолвит `{year}`/`{year+1}` в заголовках групп; `_create_xlsxwriter_sheet_writers(..., FORM_2, ...)` отдаёт раскладку FORM_2 (и явно НЕ совпадает с FORM_1 — `_create_xlsxwriter_sheet_writers(..., FORM_1, ...)` по-прежнему отдаёт `FORM1_DEPTH_COLUMNS`); `Form2DepthRowNormalizer` не поднимает `header.*`; `_resolve_form2_vsp_numbers` подменяет id на номер одним запросом и ничего не делает, если `vsp_id` нет вовсе. - `pytest tests/unit` — 69 passed, 2 failed, оба failed не связаны с этой правкой (`test_export_service_query_params.py`, экспорт SMETA/`ignore_non_applicable_query_params` — я его не трогал, те же 2 падения на `test`). - **Интеграционные тесты и реальная выгрузка Excel — НЕ ЗАПУСКАЛИСЬ: в этом окружении нет Postgres.** Структура колонок и подстановка ВСП проверены юнит-тестами (включая прогон через реальный `XlsxDepthSheetWriter`), но визуально файл `.xlsx` я не открывал и не видел — нужна ручная проверка на стенде с БД. - Frontend: `node --test src/components/RealtimeTable/utils/*.test.js` — 8/8. `npm run build` — без ошибок. Biome на изменённых файлах (`OPER.js`/`AHR.js`/`CAP.js`/`Stages/constants.js`) — то же число предсуществу��щих warning'ов, что в `test` (не добавил новых). ## Ручная проверка (acceptance criteria, не автоматизировано) - [ ] Экспортировать Операц/АХР/КВ любой формы «Смета РФ» — колонки, их порядок и заголовки групп совпадают с экраном; значений формы 1 в выгрузке нет. - [ ] Значения в колонках выгрузки совпадают со значениями на экране. - [ ] Колонка «ВСП РФ» в выгрузке содержит номер ВСП (включая закрытые ВСП), а не числовой id. - [ ] На экране и в выгрузке нет столбца «Адрес ВСП» — если заказчик имел в виду другой столбец, его нужно будет вернуть. - [ ] Выгрузка формы 1 (Смета ГО) не изменилась. ## Не в объёме AURORA-1904 (AHR_LIMIT и сателлиты) — отдельная задача, не трогал `AHR_RENT`/`AHR_UTILITY`/`AHR_SECURITY`. AURORA-1900 (подписи «тыс. руб.» в заголовках). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 1 commit 2026-10-30 20:48:10 +03:00
Листы Операц/АХР/КВ формы «Смета РФ» (FORM_2) выгружались раскладкой формы 1
(FORM1_DEPTH_COLUMNS, 180 колонок формы 1) — из 163 колонок экрана совпадали
по ключу только 83, 97 колонок выгрузки были всегда пустыми (блоки формы 1,
которых нет в данных формы 2), заголовки групп — от формы 1. Также на экране
был лишний столбец «Адрес ВСП», которого нет в исходной форме РФ, и ВСП
выгружался бы числовым id вместо номера, который виден на экране.

- FORM2_DEPTH_COLUMNS/FORM2_COLORS (export_mappings.py) — сгенерированы из
  web/src/.../FORM_2/{OPER,AHR,CAP}.js (колонки идентичны у всех трёх листов,
  проверено программно; различаются только цвета). 162 листа дерева —
  байт-в-байт тот же порядок, что на экране, минус header.vsp_address.
  Каждая группа — чисто визуальная обёртка ("key": None), каждый лист несёт
  полный путь данных как "key" (например "booking.y2027.q1"), поэтому порядок
  вложенности групп для Excel-заголовков не завязан на реальный путь к данным.
- Form2DepthRowNormalizer (export_normalizers.py) — в отличие от
  Form1DepthRowNormalizer, не поднимает header.* на верхний уровень: FORM_2
  ожидает их как header.section_code, header.vsp_id и т.д.
- _create_xlsxwriter_sheet_writers: отдельная ветка для FormTypeEnum.FORM_2
  (раньше FORM_2 попадала в общий else вместе с FORM_1). Сателлиты АХР
  (AHR_RENT/UTILITY/SECURITY, AURORA-1904) и STRUCTURE не трогал.
- VSPRepository.get_reg_numbers_by_ids + ExportService._resolve_form2_vsp_numbers:
  один запрос резолвит все встретившиеся header.vsp_id (включая закрытые ВСП)
  в registration_number перед записью в Excel, как уже показывает экран.
- web/.../FORM_2/{OPER,AHR,CAP}.js: убран столбец «Адрес ВСП» (E1) и его цвет —
  по умолчанию, т.к. его нет в исходной форме РФ (см. Rulings в PR). Убрана
  мёртвая ссылка на него в Stages/constants.js.

Issue: #252 (AURORA-1902, эпик AURORA-1876, Высокий приоритет)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This pull request can be merged automatically.
You are not authorized to merge this pull request.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/252-aurora-1902-form2-export-layout:feat/252-aurora-1902-form2-export-layout
git checkout feat/252-aurora-1902-form2-export-layout
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RSHB/DFiP_Budget_planing#259
No description provided.