161 Commits

Author SHA1 Message Date
Andrey Epifancev
66ceb6c5b2 fix(form4): рассчитывать перенос в фонд экономии на листах АХР/КВ/Операц
Перенос в фонд экономии (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>
2026-10-31 00:24:43 +04:00
Andrey Epifancev
c1c0d73387 fix(stages): исполнитель видит только свои этапы, 404 вместо 500 на чужую форму/проект
- 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>
2026-10-31 00:04:21 +04:00
Andrey Epifancev
b04c87f47c fix(export): выгрузка форм 1 и 4 — порядок столбцов и заголовки как на экране
Выгрузка Excel листов АХР/КВ/Операц/Структура форм 1 и 4 расходилась с экраном
по нескольким независимым причинам:

- FORM_4: _patch_form4_regular_columns() вызывался дважды — второй вызов
  стирал только что добавленные actual_rsbu_spod_q1..q3, поэтому в выгрузке
  были только «Факт 4 кв + СПОД», без «Факт 1/2/3 кв». Убрал второй вызов
  и больше не удаляю эти ключи в самом патче.
- _patch_form4_actual_rsbu_spod_columns: SQL отдаёт actual_rsbu_spod только
  для 4 кв.; для 1-3 кв. ключ теперь q{n}.actual_quarter (как на экране),
  не q{n}.actual_rsbu_spod (всегда пустой).
- Базовая раскладка FORM1_DEPTH_COLUMNS (отражается и в FORM_4, т.к. она из неё
  строится): убрал collegial.empty и группы "shift" (ключ sum, SQL не отдаёт);
  в q2/q3 добавил comment_correct (adj_comment) сразу после reserve_correct —
  раньше его не было вовсе; в q4 то же поле вынес из rev_correct в отдельный
  comment_correct на то же место (было в начале квартала вместе с комментарием
  корректировки плана). group "approved.plan" переименована из скопированного
  "Резерв Финансового Комитета" в "План {year}"; q4 "quarter_correct" — из
  ошибочного "Факт РСБУ 3 кв" в "Факт РСБУ 4 кв {year_short}".
- render_column_years: добавлен плейсхолдер year_plus_2 (year+2); contract_summary
  получил future_y2 "Платежи в {year_plus_2} году" (раньше его не было, и FORM_4
  patch всё равно дублировал его с неверным {next_year}). На экране «Операц»
  формы 1 такой колонки нет — для неё новая FORM1_OPER_DEPTH_COLUMNS без future_y2.
- FORM4_OPER: proc_plan/proc_method после переименования уезжали в конец блока
  ckk (pop+переприсвоение добавляет ключ в конец словаря) — вернул порядок
  экрана через _reorder_children.
- web/.../FORM_1/{AHR,CAP,OPER}.js: в q4 узел "Комментарий" (adj_comment, GG)
  шёл после "Скорректированный план" (corrected_plan, GH) — переставил перед,
  как в 1-3 кв.

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-30 22:04:02 +04:00
Andrey Epifancev
daed54d436 fix(export): Смета РФ — выгрузка Операц/АХР/КВ своей раскладкой, не формы 1
Листы Операц/АХР/КВ формы «Смета РФ» (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>
2026-10-30 21:47:24 +04:00
Andrey Epifancev
b95029f70a fix(forms): запретить удаление чужих строк, добавить подтверждение
Удаление строки в формах 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>
2026-10-30 18:06:52 +04:00
Andrey Epifancev
c8e957b13d perf(svod2): ускорен серверный поиск свода 2 без изменения результата
Поиск в 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>
2026-10-29 14:07:01 +04:00
Andrey Epifancev
05fbfd1e1d perf(form4): jit=off на v_form4_sheet_jsonb — компиляция JIT дороже выполнения
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-29 12:11:25 +04:00
Andrey Epifancev
782a04381b Правки финального ревью фазы 1: M-1, M-2, M-5
- 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>
2026-10-28 22:12:14 +04:00
Andrey Epifancev
db28138b5b perf(api): свод 2 — новый режим страниц с total и эндпоинт вариантов фильтров
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:56:34 +04:00
Andrey Epifancev
e3a9a08e84 perf(api): свод 2 — страницы и варианты фильтров в репозитории и сервисе
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:53:16 +04:00
Andrey Epifancev
f5bccc5ba2 test(api): свод 2 — поиск из пробелов в сверке с оракулом
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:50:27 +04:00
Andrey Epifancev
e81df1d71e fix(api): свод 2 — варианты фильтров без коррелированного подзапроса поиска, откат засева в teardown
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:50:04 +04:00
Andrey Epifancev
b612178b12 fix(tests): тесты тяжёлых GET — непустые данные свода 2, gzip, правки докстрингов
Фикстура свода 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>
2026-10-28 21:43:31 +04:00
Andrey Epifancev
719f568863 perf(api): свод 2 — общий отбор svod2_lines, страницы с total_count, варианты фильтров в SQL
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:40:41 +04:00
Andrey Epifancev
a62add9779 perf(api): свод 2 — миграция 0053: нормализаторы поиска как в JS и компактная проекция
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:24:34 +04:00
Andrey Epifancev
604e277371 perf(api): отчёты и сводные проектов отдают готовый JSON из Postgres
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:24:14 +04:00
Andrey Epifancev
b134cfd118 test(api): свод 2 — засев, эталон прежнего ответа и тесты-оракулы нового режима (красные)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 21:19:22 +04:00
Andrey Epifancev
ca7160124a perf(api): GET сводов 1 и 2 отдают готовый JSON из Postgres
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:16:36 +04:00
Andrey Epifancev
d8dba2f7be perf(api): GET листа отдаёт готовый JSON из Postgres без SheetResponse на строку
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:12:09 +04:00
Andrey Epifancev
174ec73732 test(api): свод 2 — Python-оракул экрана, сверенный с векторами JS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:11:17 +04:00
Andrey Epifancev
adcb9d1d3b test: свод 2 — JS-оракул поиска, фильтров и вариантов экрана «до» и векторы
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:09:29 +04:00
Andrey Epifancev
7094fe839f perf(api): хелпер ответа из готовых JSON-строк и SQL-выражение строки
Добавлены list_json_response (обёртка BaseListResponse из готовых строк),
ROW_COLUMNS, row_json_select и fetch_row_texts для тяжёлых GET (AURORA-1848).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:07:40 +04:00
Andrey Epifancev
0175759f95 test(api): фикстура db_session и снимок схемы OpenAPI тяжёлых GET
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:06:16 +04:00
Andrey Epifancev
4dae72da2b test(ws): кэш соединения - unit и интеграционный через websocket_connect
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:17:56 +04:00
Andrey Epifancev
55ba4e7e96 test(form4): суммы ROOT/GROUP/ITEM листа CAP против v3.jsonb_sum и Python, фикстура db_session
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:33:58 +04:00
Andrey Epifancev
f0cac30622 test(sheet): контрактный тест ответа PATCH /cell с полными строками (AURORA-1844)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 18:18:07 +04:00
Andrey Epifancev
663f45ee04 test(projects): гонка technical_number и переполнение счётчика (AURORA-1831)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 18:14:33 +04:00
Andrey Epifancev
d73333705c test(stages): aware-даты этапов форм и проектов
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:13:13 +04:00
Andrey Epifancev
b0dcc0b0f5 fix(deps): явно ставить greenlet в dev-окружении через sqlalchemy[asyncio]
SQLAlchemy не объявляет greenlet без extra asyncio. На arm64 (macOS)
и на Python >= 3.13 greenlet не ставится, и async-движок падает с
"the greenlet library is required". Тест фиксирует наличие greenlet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:11:10 +04:00
Andrey Epifancev
2dc9b4df03 fix(users): выходные схемы пользователя без входных ограничений (/users/me 500 на .local)
UserInDB наследует новую UserReadBase (email/username/full_name без
валидации формата). Вход (UserBase/UserCreate/UserUpdate) не менялся.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 18:11:06 +04:00
Andrey Epifancev
5e7ac886aa test(alembic): регрессия на подзапросы в FROM без алиаса (PostgreSQL 15)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-28 18:10:13 +04:00
744c8962cd Merge pull request 'svod2-filter-fix2: если заполнен только всп не берем в свод 2' (#197) from svod2-filter-fix2 into test
Reviewed-on: #197
2026-10-21 17:33:12 +03:00
8d6e7ba51b svod2-filter-fix2: если заполнен только всп не берем в свод 2 2026-10-21 17:31:34 +03:00
18b834e1f8 Merge pull request 'indent-export: отсупы в экспорте по глубине вложенности' (#196) from indent-export into test
Reviewed-on: #196
2026-10-21 15:48:42 +03:00
669e352980 indent-export: отсупы в экспорте по глубине вложенности 2026-10-21 15:46:08 +03:00
f92c0e8d32 svod2-filter-fix: строки совсем без значений не попадают в свод 2 2026-10-21 15:38:48 +03:00
1463b3ad8b export-hierarchy: иерархия expense-item при выгрузке 2026-10-21 11:44:24 +03:00
172b343c69 export-optimize-refresh: очередь загрузки при экспорте 2026-09-30 20:40:21 +03:00
5f887e4261 phase-template-edit: редактирование шаблонов этапов 2026-09-11 16:40:10 +03:00
b34f1f7cad tasks-filter: фильтры для задач форм 1 2 4 и обратная сортировка 2026-09-02 12:30:14 +03:00
5dfb9653c2 delete-from-cell: удаление данных из ячейки numeric делает значение 0 2026-08-28 12:38:27 +03:00
02b746c5ad Merge pull request 'svod: свод 2' (#128) from svod into test
Reviewed-on: #128
2026-08-27 17:01:10 +03:00
4708acbd20 svod: свод 2 2026-08-27 16:52:58 +03:00
658bfe6515 Merge pull request 'добавление пагинация для архивных проектов' (#125) from server-side-archived-projects into test
Reviewed-on: #125
2026-08-26 13:59:42 +03:00
PotapovaA
f7dc90e63b добавление пагинация для архивных проектов 2026-08-26 13:52:53 +03:00
d551939e99 Merge pull request 'svod: свод 1' (#123) from svod into test
Reviewed-on: #123
2026-08-26 13:14:29 +03:00
6f6f35c8b1 svod: свод 1 2026-08-26 13:09:54 +03:00
1789f58581 projects-filters: offset, limit, status_in фильтры для проектов в ручке with-reports 2026-08-25 16:56:17 +03:00
9095cad992 project-back-fix: экпорт проектов, нельзя ставить отрицательные числа в полях проектов, создание года только на следующий год 2026-08-20 21:27:08 +03:00
4e8be39df1 tech-num-fix: поправил формирование технического номера проекта, исправил шаблоны для формы 4, поправил получение стадий у проектов 2026-08-19 17:31:58 +03:00