AURORA-1978: удаление пустой программы и проекта на форме 4 #304

Merged
aep merged 1 commits from feat/303-aurora-1978-pd-delete-program-project into test 2026-11-11 18:12:08 +03:00
Owner

Closes #303 (Jira AURORA-1978).

Проблема

«Удалить строку» на строке программы (GROUP) или проекта (ITEM) проектной деятельности ничего не делала: у этих строк нет line_id, getRowId возвращал null, окно подтверждения не открывалось. Сервер события program_deleted / project_deleted уже поддерживает (проверка доступа, v3.del_form4_program / v3.del_form4_project с запретом удалять непустые), но фронт их не отправлял и не обрабатывал.

Что сделано (только web/)

  • rowUtils.getDeleteTarget — по выделенной строке выбирает программу / проект / строку бюджета. Для программы с проектами или расходами и проекта с расходами сразу показывает причину запрета, без запроса.
  • RealtimeContext — deleteProgram / deleteProject, обработка ответов (subscribeToNodeDeletes).
  • useRealtimeData — строка убирается из дерева (deleteProgramRow / deleteProjectRow), пересчёт ROOT/GROUP из ответа вливается через alignNodeRows: сопоставление по section_code / program_id / project_id, а не по sort_order.
  • ConfirmDeleteRowModal — заголовок и текст по типу: «Удалить программу «X»?».
  • getWsErrorMessage срезает префикс <class 'asyncpg.exceptions.…'>: .

Проверка

  • node --test src/components/RealtimeTable/utils/*.test.js — 171/171 (новые тесты: getDeleteTarget, deleteProgramRow/deleteProjectRow/deleteRow, alignNodeRows, префикс класса исключения).
  • npx rsbuild build — ок. Biome: новых замечаний нет (остальные — в существующем коде).
  • Вживую на стенде (perf-pg, форма 4, лист CAP, admin): удалена пустая программа; удалён пустой проект; непустые программа и проект — тост с причиной, запроса нет; обычное удаление строки расхода работает как раньше; устаревший клиент (проект добавлен в обход) — сервер отказал, тост «Нельзя удалить программу, содержащую проекты», строка осталась; БД и журнал аудита (PROJECT_DELETE) совпадают с экраном.
  • При живой проверке найден и исправлен баг: после удаления последнего расхода сервер не отдаёт ROOT и нумерует строки заново, и пересчёт программы «В» по sort_order затирал программу «Б». Покрыт регрессионным тестом alignNodeRows.

Rulings I made

  • R-1: технический префикс <class 'asyncpg…'>: срезается на фронте в getWsErrorMessage, бэкенд (map_sqlalchemy_error) не трогаю — bounded-задача фронта; цена ошибки — префикс остаётся в REST-ответах и мешает VALIDATION_PREFIXES на бэке (см. O-3).
  • R-2: причина запрета для непустых программы/проекта показывается на клиенте по subRows; сервер остаётся источником истины для устаревшего клиента.
  • R-3: выделенная строка без id (ROOT и т.п.) теперь даёт тост «Эту строку нельзя удалить» вместо молчания.
  • R-4: интеграционный тест на WS-события program_deleted/project_deleted не добавлял — бэкенд не менялся; проверено вживую.

Отложено (deferred)

  • O-1: после удаления последней строки бюджета ROOT остаётся на экране до перезагрузки (сервер его уже не отдаёт). Существующий путь row_deleted/updateCells сопоставляет строки по sort_order и уязвим к той же перенумерации — deferred.
  • O-2: api/tests/unit/test_export_service_query_params.py::test_export_full_form_ignores_non_applicable_params_for_other_sheets падает на базе test (бэкенд в PR не менялся) — deferred.
  • O-3: map_sqlalchemy_error отдаёт str(exc.orig) с префиксом класса asyncpg, из-за чего startswith(VALIDATION_PREFIXES) для RAISE из PL/pgSQL, похоже, не срабатывает (закреплено в test_heavy_get_json.py) — deferred.
Маркер Суть Статус
R-1 префикс класса исключения срезается на фронте fixed 1aec5d2
R-2 причина запрета на клиенте по subRows fixed 1aec5d2
R-3 тост для строки без id fixed 1aec5d2
R-4 без интеграционного теста WS rejected: бэкенд не менялся, проверено вживую
O-1 ROOT остаётся после удаления последнего расхода; row_deleted по sort_order deferred
O-2 падающий unit-тест экспорта на базе test deferred
O-3 map_sqlalchemy_error и префикс класса asyncpg deferred

🤖 Generated with Claude Code

Closes #303 (Jira AURORA-1978). ## Проблема «Удалить строку» на строке программы (GROUP) или проекта (ITEM) проектной деятельности ничего не делала: у этих строк нет `line_id`, `getRowId` возвращал `null`, окно подтверждения не открывалось. Сервер события `program_deleted` / `project_deleted` уже поддерживает (проверка доступа, `v3.del_form4_program` / `v3.del_form4_project` с запретом удалять непустые), но фронт их не отправлял и не обрабатывал. ## Что сделано (только web/) - `rowUtils.getDeleteTarget` — по выделенной строке выбирает программу / проект / строку бюджета. Для программы с проектами или расходами и проекта с расходами сразу показывает причину запрета, без запроса. - `RealtimeContext` — `deleteProgram` / `deleteProject`, обработка ответов (`subscribeToNodeDeletes`). - `useRealtimeData` — строка убирается из дерева (`deleteProgramRow` / `deleteProjectRow`), пересчёт ROOT/GROUP из ответа вливается через `alignNodeRows`: сопоставление по `section_code` / `program_id` / `project_id`, а не по `sort_order`. - `ConfirmDeleteRowModal` — заголовок и текст по типу: «Удалить программу «X»?». - `getWsErrorMessage` срезает префикс `<class 'asyncpg.exceptions.…'>: `. ## Проверка - `node --test src/components/RealtimeTable/utils/*.test.js` — 171/171 (новые тесты: getDeleteTarget, deleteProgramRow/deleteProjectRow/deleteRow, alignNodeRows, префикс класса исключения). - `npx rsbuild build` — ок. Biome: новых замечаний нет (остальные — в существующем коде). - Вживую на стенде (perf-pg, форма 4, лист CAP, admin): удалена пустая программа; удалён пустой проект; непустые программа и проект — тост с причиной, запроса нет; обычное удаление строки расхода работает как раньше; устаревший клиент (проект добавлен в обход) — сервер отказал, тост «Нельзя удалить программу, содержащую проекты», строка осталась; БД и журнал аудита (PROJECT_DELETE) совпадают с экраном. - При живой проверке найден и исправлен баг: после удаления последнего расхода сервер не отдаёт ROOT и нумерует строки заново, и пересчёт программы «В» по sort_order затирал программу «Б». Покрыт регрессионным тестом `alignNodeRows`. ## Rulings I made - R-1: технический префикс `<class 'asyncpg…'>: ` срезается на фронте в `getWsErrorMessage`, бэкенд (`map_sqlalchemy_error`) не трогаю — bounded-задача фронта; цена ошибки — префикс остаётся в REST-ответах и мешает `VALIDATION_PREFIXES` на бэке (см. O-3). - R-2: причина запрета для непустых программы/проекта показывается на клиенте по `subRows`; сервер остаётся источником истины для устаревшего клиента. - R-3: выделенная строка без id (ROOT и т.п.) теперь даёт тост «Эту строку нельзя удалить» вместо молчания. - R-4: интеграционный тест на WS-события `program_deleted`/`project_deleted` не добавлял — бэкенд не менялся; проверено вживую. ## Отложено (deferred) - O-1: после удаления последней строки бюджета ROOT остаётся на экране до перезагрузки (сервер его уже не отдаёт). Существующий путь `row_deleted`/`updateCells` сопоставляет строки по `sort_order` и уязвим к той же перенумерации — deferred. - O-2: `api/tests/unit/test_export_service_query_params.py::test_export_full_form_ignores_non_applicable_params_for_other_sheets` падает на базе `test` (бэкенд в PR не менялся) — deferred. - O-3: `map_sqlalchemy_error` отдаёт `str(exc.orig)` с префиксом класса asyncpg, из-за чего `startswith(VALIDATION_PREFIXES)` для RAISE из PL/pgSQL, похоже, не срабатывает (закреплено в `test_heavy_get_json.py`) — deferred. | Маркер | Суть | Статус | |---|---|---| | R-1 | префикс класса исключения срезается на фронте | fixed 1aec5d2 | | R-2 | причина запрета на клиенте по subRows | fixed 1aec5d2 | | R-3 | тост для строки без id | fixed 1aec5d2 | | R-4 | без интеграционного теста WS | rejected: бэкенд не менялся, проверено вживую | | O-1 | ROOT остаётся после удаления последнего расхода; row_deleted по sort_order | deferred | | O-2 | падающий unit-тест экспорта на базе test | deferred | | O-3 | map_sqlalchemy_error и префикс класса asyncpg | deferred | 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 1 commit 2026-11-08 19:03:35 +03:00
«Удалить строку» на строке программы (GROUP) или проекта (ITEM) ничего не
делала: у этих строк нет line_id, окно подтверждения не открывалось, а
события program_deleted / project_deleted, которые сервер уже поддерживает,
фронт не отправлял.

- getDeleteTarget: по выделенной строке выбирает программу, проект или
  строку бюджета; для непустых программы/проекта сразу показывает причину.
- RealtimeContext: deleteProgram / deleteProject и обработка ответов.
- useRealtimeData: строка убирается из дерева, пересчёт ROOT/GROUP из
  ответа сопоставляется по section_code / program_id / project_id
  (alignNodeRows), а не по sort_order — сервер нумерует строки заново, и у
  листа без строк бюджета нет ROOT (найдено при проверке на стенде).
- Окно подтверждения называет удаляемую программу/проект.
- getWsErrorMessage срезает префикс "<class 'asyncpg...'>: " — иначе отказ
  сервера (программа с проектами у устаревшего клиента) показывался с ним.

Refs #303

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aep requested review from PotapovaA 2026-11-09 08:12:52 +03:00
PotapovaA approved these changes 2026-11-09 14:28:07 +03:00
aep merged commit 90e4084e7d into test 2026-11-11 18:12:08 +03:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RSHB/DFiP_Budget_planing#304
No description provided.