Добавлен столбец «СПОД» между «Декабрь» и «Итог 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>
Перенос в фонд экономии (q1/q2/q3/q4.transfer_econ, столбцы 75/107/137
и их Q4-аналог формы РФ) был raw passthrough-полем budget_line_quarter.
transfer_to_economy: ничего его не считало, поэтому после сохранения
строки с планом без переносов поле оставалось пустым, а "ВСЕГО" блоков
показывало прочерк вместо суммы.
v3.v_form4_sheet_sections теперь считает transfer_econ_QN из уже
имеющегося residual_after_actual_QN с поправкой на то, что тот уже
вычитает перенос в СЛЕДУЮЩИЙ квартал один раз (через cp1/cp2/cp3):
transfer_econ_Q1 = residual_after_actual_Q1 - transfer_q3 - transfer_q4
transfer_econ_Q2 = residual_after_actual_Q2 - transfer_q4
transfer_econ_Q3 = residual_after_actual_Q3
transfer_econ_Q4 = residual_after_actual_Q4
Буквальный перенос формулы Excel (ещё и минус transfer_q2 для Q1 /
transfer_q3 для Q2) задвоил бы вычитание. АХР, КВ (CAP) и Операц (OPER)
используют одну и ту же функцию, поэтому один фикс закрывает все три
листа; STRUCTURE — агрегирующий read-only свод без INPUT-строк, его не
касается.
v3._apply_form_cell для FORM_4 больше не принимает прямую запись в
q*.transfer_econ (ValueError computed_field, как у residual_after_actual).
Тесты (api/tests/integration/test_form4_transfer_econ.py):
- репро тикета (переносы = 0);
- регрессия на двойное вычитание с ненулевыми transfer_q2/q3/q4,
отличающая верную формулу от наивного Excel-порта;
- КВ и Операц дают те же числа, что АХР;
- запись в transfer_econ отклоняется.
Closes#255.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- FormPhaseRepository.get_list и Form3PhaseRepository.get_list: новый
необязательный параметр role для фильтрации по v3.role.code.
- FormPhaseService.get_list и Form3PhaseService.get_list: для не-админа
передают в репозиторий role по коду роли пользователя (единая функция
role_code_for_user в form_phase_service.py), для ADMIN фильтр не нужен.
- GET /stages/form/{form_id} и GET /stages/project/{project_id}: сначала
проверяют доступ к форме/проекту (BudgetFormService.get /
ProjectService.get_instance), при отсутствии доступа — 404 вместо
AttributeError → 500.
- StagesTable.jsx: подпись роли этапа показывается только админу.
Closes#250.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Удаление строки в формах 1/2/4 не проверяло владельца: любой исполнитель
мог удалить чужую строку мгновенно, без подтверждения.
- v3.del_budget_line (миграция 0050): не-admin может удалить только
budget_line, где created_by совпадает с current_user_id() (проверка
и для обычных листов, и для AHR_RENT/UTILITY/SECURITY — через
created_by связанной budget_line). Нарушение -> role_not_allowed:
(403), по существующей конвенции PG_403_ERRORS.
- RealtimeTable: кнопка «Удалить строку» теперь открывает
ConfirmDeleteRowModal, реальное удаление — только после подтверждения.
- Интеграционные тесты: новый исполнитель isp2 (fixture.sql) в том же
org_unit, что и budget_form id=1 — проверяют 403 при чужой строке,
200 при своей и при удалении администратором.
Issue: #249 (AURORA-1936, эпик AURORA-1876, Высокий приоритет)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Поиск в v3.svod2_lines строил jsonb header || contract_summary || contract_detail на каждую
строку, разворачивал его в jsonb_each и прогонял каждое значение через svod2_casefold, а числа
через svod2_js_number_text. Теперь:
- разбор поисковой строки (числа возможны, нет U+0001, нужные пары таблицы регистра) делается
один раз в CTE q; раньше при подстановке SQL-функции выражение нормализации p_search
вычислялось на каждое значение каждой строки;
- значения берутся напрямую из колонок, без jsonb; склеиваются и проверяются одним strpos через
разделитель U+0001 (со строкой поиска, где есть U+0001, значения проверяются по одному);
- регистр данных приводится только по парам таблицы, чей результат есть в строке поиска;
- числа в текст JS переводятся, только если все символы строки возможны в записи числа, и
только если по остальным значениям совпадения нет; нули без обращения к plpgsql;
- v_svod2_go и v_svod2_rf с SET jit = off (JIT плана добавлял сотни миллисекунд).
Тесты: поиск svod2_lines равен прежнему поиску по jsonb (эталонный SQL в тесте) на обоих листах
для 55 строк, включая числа, экспоненту, NaN и Infinity, стык значений, U+0001, ненормализованные
строки; состав колонок contract_* закреплён тестом.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- M-1: поиск по перекрытым полям contract_summary (ооо лютик, дс-2, дс-1) и неперекрытому (70), явные assert для строки 910004
- M-2: в лог db_time вместо значений поиска и фильтров пишутся признаки has_*/search_len; unit-тест на caplog
- M-5: total читается по имени столбца total_count
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Фикстура свода 2 достроена до 6 строк на GO и RF по двум орг. единицам с разными section_code;
все случаи SVOD2_CASES проверяют непустоту. Тест rf-rollup переименован (фиксирует существующий дефект),
добавлен тест gzip, в api_split.py оставлены только GET-места.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>