AURORA-1909: добавление записи на сателлите не захватывает ручную строку АХР #268

Open
aep wants to merge 3 commits from feat/265-aurora-1909-ahr-manual-row-capture into test
Owner

Closes #265.

Summary

  • v3.add_budget_line (ветка сателлитов AHR_RENT/AHR_UTILITY/AHR_SECURITY) искала любую существующую строку статьи (без ORDER BY, без исключения ручных и уже привязанных строк) и переиспользовала её как заготовку для новой записи. Если у статьи ещё не было строк в v3.form2_ahr_satellite_line_source (типичный случай — первая запись сателлита при наличии ручной строки той же статьи), sync_form2_ahr_satellite_line(s) переименовывал и перезаписывал план/факт чужой ручной строки, а затем блокировал её для правки.
  • Новая миграция 0051_form2_ahr_satellite_new_line пересоздаёт v3.add_budget_line: ветка сателлитов теперь всегда создаёт новую пустую строку-заготовку вместо поиска существующей. Sync переиспользует эту заготовку как строку именно этой записи (её ещё нет в form2_ahr_satellite_line_source), лишних строк не появляется.
  • Заодно удалён мёртвый код _ensure_form2_ahr_sync_line / _upsert_form2_ahr_sync_values (0031) — не было вызывающих мест.
  • Не затронуто (намеренно, отдельные задачи): лимитная часть (_ensure_form2_ahr_limit_sync_line, AURORA-1907) и статьи 314/24/779, зашитые в коде (AURORA-1887). Уже испорченные строки на существующих формах автоматически не восстанавливаются — вне рамок задачи.

Эта ветка стоит поверх #263 (AURORA-1906, колонка СПОД в сателлитах АХР), который ещё не смёржен в test. Решение человека — не ждать мерджа #263 и продолжать цепочку задач прямо на его ветке. Поэтому диф этого PR временно включает 2 коммита #263 (feat(form2): колонка «СПОД»..., fix(tests): реальный прогон...) — это ожидаемо; после мерджа #263 в test диф этого PR сократится сам до одного коммита с миграцией 0051 и тестами.

Test plan

  • Новый интеграционный файл api/tests/integration/test_form2_ahr_satellite_integration.py (9 тестов, создаёт свою форму FORM_2 через POST /api/v1/form/):
    • Параметризовано по AHR_RENT(314)/AHR_UTILITY(24)/AHR_SECURITY(779): ручная строка на листе AHR (план q1=500, имя «моя строка») создаётся раньше записи сателлита → после добавления записи на сателлите ручная строка не меняется (тот же line_id, имя, план), а запись получает свою отдельную строку.
    • То же, но проверяется, что ручная строка остаётся удаляемой (DELETE .../sheet/AHR/line/{id} → 200) после добавления записи сателлита.
    • Две записи подряд на пустой статье (без ручной строки) → две разные строки АХР (до AURORA-1887/сводной строки источника — ожидаемое поведение, не в рамках этой задачи).
  • TDD подтверждён: все 6 тестов первого и второго сценариев падают на миграции 0050 (без фикса) с тем же симптомом, что в тикете (ручная строка переименовывается/блокируется для удаления — ForeignKeyViolationError: Строку АХР, созданную из листа-сателлита..., нельзя удалить напрямую), и проходят после миграции 0051.
  • Полный прогон cd api && pytest (локальный выделенный Postgres 16, не делился с другими параллельными агентами — см. Rulings) на миграции 0051: 224 passed, 3 failed, 4 skipped (231 собрано). Те же 3 падения получены и на чистой миграции 0050 (без моей правки): 215 passed, 3 failed, 4 skipped (222 собрано) — т.е. все 3 падения pre-existing и не связаны с этой задачей:
    • tests/integration/test_db_functions_integration.py::test_backend_calls_form3_add_project_and_add_line_functions
    • tests/unit/test_export_service_query_params.py::test_export_full_form_ignores_non_applicable_params_for_other_sheets
    • tests/unit/test_export_service_query_params.py::test_export_form_payload_forwards_ignore_flag

Rulings I made

  • R-1: perf-pg (общий стенд на localhost:55432 из perf/README.md) оказался в момент работы на миграции 0053 — используется параллельно другими агентами этой же серии задач (AURORA-1876), и его схема несовместима с локальной цепочкой миграций этой ветки (head=0051). Поднял отдельный одноразовый контейнер Postgres 16 (порт 55440, perf/perf/perf), прогнал полный alembic upgrade head и тесты там, контейнер удалён после прогона. Общий стенд не трогал.
  • R-2: обнаружил, что чисто пустая БД + alembic upgrade head на PostgreSQL 15 падает на миграции 0012_del_project.sql (can_delete_project): subquery in FROM must have an alias — PL/pgSQL-компилятор PG15 строже проверяет тела функций при CREATE FUNCTION, чем PG16 (воспроизвёл минимальным примером на обеих версиях напрямую через psql). Это предсуществующий баг миграции 0012, не связанный с AURORA-1909 — на PG16 (как в perf/README.md) эта же миграция проходит без ошибок. Переключился на PG16 для прогона и зафиксировал находку здесь, чтобы не тратить раунды на диагностику при следующей задаче серии.
  • R-3: codegraph_explore по SQL-функциям (add_budget_line, sync_form2_ahr_satellite_line(s) и т.д.) не нашёл сами PL/pgSQL-функции (индекс не покрывает текстовые .sql-файлы с объектами БД) — вернул только связанный Python-код. Дальше читал SQL через Read/grep, как и предписано в задаче на случай пустого ответа.
  • R-4: тело v3.add_budget_line в этой ветке взято из 0034_ahr_rf_sort_order.sql:525-732 без изменений кроме ветки сателлитов — проверил, что миграция 0050_form2_ahr_spod_column.sql (уже в этой ветке, PR #263) не переопределяет add_budget_line (переопределяет только v_form2_view, _apply_form_cell, _upsert_form2_ahr_detail_sync_values, sync_form2_ahr_satellite_line(s), v_svod2_go), поэтому брать тело было безопасно взять как в 0034 "как есть".
Closes #265. ## Summary - `v3.add_budget_line` (ветка сателлитов `AHR_RENT`/`AHR_UTILITY`/`AHR_SECURITY`) искала любую существующую строку статьи (без `ORDER BY`, без исключения ручных и уже привязанных строк) и переиспользовала её как заготовку для новой записи. Если у статьи ещё не было строк в `v3.form2_ahr_satellite_line_source` (типичный случай — первая запись сателлита при наличии ручной строки той же статьи), `sync_form2_ahr_satellite_line(s)` переименовывал и перезаписывал план/факт чужой ручной строки, а затем блокировал её для правки. - Новая миграция `0051_form2_ahr_satellite_new_line` пересоздаёт `v3.add_budget_line`: ветка сателлитов теперь **всегда** создаёт новую пустую строку-заготовку вместо поиска существующей. Sync переиспользует эту заготовку как строку именно этой записи (её ещё нет в `form2_ahr_satellite_line_source`), лишних строк не появляется. - Заодно удалён мёртвый код `_ensure_form2_ahr_sync_line` / `_upsert_form2_ahr_sync_values` (0031) — не было вызывающих мест. - Не затронуто (намеренно, отдельные задачи): лимитная часть (`_ensure_form2_ahr_limit_sync_line`, AURORA-1907) и статьи 314/24/779, зашитые в коде (AURORA-1887). Уже испорченные строки на существующих формах автоматически не восстанавливаются — вне рамок задачи. **Эта ветка стоит поверх #263 (AURORA-1906, колонка СПОД в сателлитах АХР), который ещё не смёржен в `test`.** Решение человека — не ждать мерджа #263 и продолжать цепочку задач прямо на его ветке. Поэтому диф этого PR временно включает 2 коммита #263 (`feat(form2): колонка «СПОД»...`, `fix(tests): реальный прогон...`) — это ожидаемо; после мерджа #263 в `test` диф этого PR сократится сам до одного коммита с миграцией 0051 и тестами. ## Test plan - Новый интеграционный файл `api/tests/integration/test_form2_ahr_satellite_integration.py` (9 тестов, создаёт свою форму FORM_2 через `POST /api/v1/form/`): - Параметризовано по `AHR_RENT`(314)/`AHR_UTILITY`(24)/`AHR_SECURITY`(779): ручная строка на листе AHR (план q1=500, имя «моя строка») создаётся раньше записи сателлита → после добавления записи на сателлите ручная строка не меняется (тот же `line_id`, имя, план), а запись получает свою отдельную строку. - То же, но проверяется, что ручная строка остаётся удаляемой (`DELETE .../sheet/AHR/line/{id}` → 200) после добавления записи сателлита. - Две записи подряд на пустой статье (без ручной строки) → две разные строки АХР (до AURORA-1887/сводной строки источника — ожидаемое поведение, не в рамках этой задачи). - TDD подтверждён: все 6 тестов первого и второго сценариев **падают** на миграции 0050 (без фикса) с тем же симптомом, что в тикете (ручная строка переименовывается/блокируется для удаления — `ForeignKeyViolationError: Строку АХР, созданную из листа-сателлита..., нельзя удалить напрямую`), и **проходят** после миграции 0051. - Полный прогон `cd api && pytest` (локальный выделенный Postgres 16, не делился с другими параллельными агентами — см. Rulings) на миграции 0051: **224 passed, 3 failed, 4 skipped** (231 собрано). Те же 3 падения получены и на чистой миграции 0050 (без моей правки): **215 passed, 3 failed, 4 skipped** (222 собрано) — т.е. все 3 падения pre-existing и не связаны с этой задачей: - `tests/integration/test_db_functions_integration.py::test_backend_calls_form3_add_project_and_add_line_functions` - `tests/unit/test_export_service_query_params.py::test_export_full_form_ignores_non_applicable_params_for_other_sheets` - `tests/unit/test_export_service_query_params.py::test_export_form_payload_forwards_ignore_flag` ## Rulings I made - **R-1**: `perf-pg` (общий стенд на `localhost:55432` из `perf/README.md`) оказался в момент работы на миграции `0053` — используется параллельно другими агентами этой же серии задач (AURORA-1876), и его схема несовместима с локальной цепочкой миграций этой ветки (head=0051). Поднял отдельный одноразовый контейнер Postgres **16** (порт 55440, `perf`/`perf`/`perf`), прогнал полный `alembic upgrade head` и тесты там, контейнер удалён после прогона. Общий стенд не трогал. - **R-2**: обнаружил, что чисто пустая БД + `alembic upgrade head` на PostgreSQL **15** падает на миграции `0012_del_project.sql` (`can_delete_project`): `subquery in FROM must have an alias` — PL/pgSQL-компилятор PG15 строже проверяет тела функций при `CREATE FUNCTION`, чем PG16 (воспроизвёл минимальным примером на обеих версиях напрямую через `psql`). Это предсуществующий баг миграции 0012, не связанный с AURORA-1909 — на PG16 (как в `perf/README.md`) эта же миграция проходит без ошибок. Переключился на PG16 для прогона и зафиксировал находку здесь, чтобы не тратить раунды на диагностику при следующей задаче серии. - **R-3**: `codegraph_explore` по SQL-функциям (`add_budget_line`, `sync_form2_ahr_satellite_line(s)` и т.д.) не нашёл сами PL/pgSQL-функции (индекс не покрывает текстовые `.sql`-файлы с объектами БД) — вернул только связанный Python-код. Дальше читал SQL через `Read`/`grep`, как и предписано в задаче на случай пустого ответа. - **R-4**: тело `v3.add_budget_line` в этой ветке взято из `0034_ahr_rf_sort_order.sql:525-732` без изменений кроме ветки сателлитов — проверил, что миграция `0050_form2_ahr_spod_column.sql` (уже в этой ветке, PR #263) не переопределяет `add_budget_line` (переопределяет только `v_form2_view`, `_apply_form_cell`, `_upsert_form2_ahr_detail_sync_values`, `sync_form2_ahr_satellite_line(s)`, `v_svod2_go`), поэтому брать тело было безопасно взять как в 0034 "как есть".
aep added 3 commits 2026-10-31 14:08:40 +03:00
Добавлен столбец «СПОД» между «Декабрь» и «Итог IV кв.» на листах
AHR_RENT/AHR_UTILITY/AHR_SECURITY формы 2, с переносом значения в
budget_line_quarter.actual_spod синхронизируемой строки АХР (Support)
и, через уже существующую логику, в итог 4 кв. и года АХР/Смет/сводов.

- rent_detail/utility_detail/security_detail: колонка actual_spod
  (ALTER TABLE + ORM).
- v3.v_form2_view: fact_q4.spod и fact_q4.total с учётом spod в ветках
  AHR_RENT/AHR_UTILITY/AHR_SECURITY.
- v3._apply_form_cell: fact_q4.spod разрешён как обычное поле месяца.
- v3._upsert_form2_ahr_detail_sync_values: DROP+CREATE с новым
  параметром p_actual_spod (DROP обязателен из-за неоднозначности
  перегрузки с DEFAULT).
- v3.sync_form2_ahr_satellite_lines/line: actual_spod передаётся из
  rd/ud/sd в апсерт.
- v3.v_svod2_go: строка аренды также считается заполненной по
  actual_spod.
- Data-миграция: 'fact_q4.spod' добавлен в column_keys этапов, где уже
  есть 'fact_q4.dec' (phase_template/form_phase), идемпотентно.
- Тело CREATE OR REPLACE для v_form2_view/sync_form2_ahr_satellite_*
  собрано вручную по миграциям 0033/0034 + regexp-патчам из 0037
  (pg_get_functiondef на живой БД недоступен в этой среде — см.
  Rulings в PR).

Тесты: node --test на новых accessorKey в конфигах фронта + unit-тест
на 0049/0050 (v_svod2_go теперь засчитывает actual_spod для аренды).
Интеграционные тесты добавлены, но не выполнены — нет доступа к
Postgres в этой рабочей среде (см. Rulings в PR).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Прогнал api/tests/integration/test_form2_ahr_satellite_spod_integration.py
против живого Postgres (perf/perf/perf на localhost:55432, БД dfip_1906,
alembic upgrade head + фикстура) — нашёл и поправил два бага самого теста
(фичу/миграцию не трогал):

- _find_row не учитывал служебную META-строку (editable/role/user_id без
  id/header), которую v3.v_form_view добавляет первой в любой лист —
  KeyError: 'id' на первой же выборке. Теперь ищет только среди INPUT-строк.
- test_satellite_spod_phase_gates_non_admin_user делал один тест из
  "запрет без fact_q4.spod → добавили ключ → разрешено": неадминский
  403/400 от isp1 внутри теста вызывает db_ref.rollback() в
  tests/integration/conftest.py (per-request откат на ошибке), который
  стирает все несохранённые изменения этого же теста — включая только
  что созданные строку сателлита и form_phase. Разбил на два независимых
  теста (test_satellite_spod_rejected_before_phase_includes_spod_column /
  test_satellite_spod_allowed_once_phase_includes_spod_column), в каждом
  ровно один запрос, которому разрешено провалиться.

pytest tests/integration — 157 passed, 1 failed (pre-existing,
не связан: test_backend_calls_form3_add_project_and_add_line_functions,
FORM_3/add_project, 400 на /api/v1/projects/.../report/.../line), 4 skipped.
Новый файл — 15/15.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v3.add_budget_line (ветка сателлитов AHR_RENT/AHR_UTILITY/AHR_SECURITY)
искала любую существующую строку статьи без ORDER BY и без исключения
ручных/уже привязанных строк, и переиспользовала её как заготовку для
новой записи. Если у статьи ещё не было строк в
v3.form2_ahr_satellite_line_source (типично — первая запись при наличии
ручной строки той же статьи), sync переименовывал и перезаписывал план/факт
чужой ручной строки, блокируя её для правки.

Теперь для каждой новой записи сателлита всегда создаётся отдельная
пустая строка-заготовка — sync забирает её как строку именно этой записи.
Заодно удалён мёртвый код (_ensure_form2_ahr_sync_line,
_upsert_form2_ahr_sync_values), не имевший вызывающих мест.

AURORA-1907 (лимитная часть) и AURORA-1887 (статьи 314/24/779 в коде) —
отдельные задачи, не затронуты.

Closes #265.

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/265-aurora-1909-ahr-manual-row-capture:feat/265-aurora-1909-ahr-manual-row-capture
git checkout feat/265-aurora-1909-ahr-manual-row-capture
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#268
No description provided.