331 Commits

Author SHA1 Message Date
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
c01356736f fix(form2): Смета РФ — итоги групп и IV кв. на свои места
На листе «Смета» формы «Смета РФ» итоговые колонки каждой группы
(план/утверждённые/факт «Итого: Поддержка» и IV квартал скорректированной
сметы) были оторваны от своих групп и собраны в отдельную группу
«В тыс. рублей» справа — хотя в шаблоне идут сразу после своего квартала.

- Перенёс H/N/T (data.*.support.year) в конец соответствующих групп план/
  утверждённые/факт, X (data.corrected.support.q4) — в конец группы
  скорректированной сметы после III квартала.
- Удалил опустевшую группу «В тыс. рублей»; accessorKey групп
  (v_tys_rubley_*) нигде больше не использовались (grep по web/src).
  columnLetter и leaf accessorKey (data.*) не менял — видимые значения
  те же, меняется только расположение колонок.
- Тест читает FORM_2/SMETA.js как текст (модуль не грузится напрямую —
  импортирует '../columnColors' без расширения) и проверяет порядок
  accessorKey листьев и отсутствие заголовка «В тыс. рублей».

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-30 18:12:29 +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
edaed04a03 fix(auth): session no longer drops after access-token expiry
Access-токен жил 60 минут без обновления: фронт не слал response-interceptor
на 401, refresh-токен лежал в localStorage неиспользуемым, WebSocket-сокеты
(форма, статусы выгрузки) не переподключались после закрытия с auth-кодом,
а после редиректа на /login пользователь терял исходную страницу.

- tokenRefresh.js: чистые getTokenExp/msUntilRefresh/createSingleFlight (TDD).
- auth.js: refreshTokens — обновление токена вне интерсепторов api (без
  рекурсии), централизованно чистит токены и шлёт auth:expired при неудаче.
- client.js: response-interceptor повторяет запрос после обновления токена.
- AuthProvider: при старте и по 'auth:expired'/'auth:refreshed' не выкидывает
  из системы, пока жив refresh; проактивный таймер обновления + visibilitychange.
- Routes/LoginPage: PrivateRoute сохраняет исходный location, вход возвращает
  туда же вместо /tasks; убрана мёртвая ветка loading.
- useWebSocket/exports: переподключение после 1008/4401 с одной попыткой
  refreshTokens() перед тем как сдаться.

Issue: #248 (AURORA-1877, эпик AURORA-1876, Критичный приоритет)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-30 17:18:11 +04:00
Andrey Epifancev
dfccd209a7 fix(realtime-table): плавная прокрутка колесом с накоплением цели вместо мгновенного прыжка
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:37:02 +04:00
Andrey Epifancev
36d8192221 perf(realtime-table): шаг прокрутки колесом мыши ×2 (AURORA-1944)
Хук useWheelScrollStep перехватывает щелчок колеса мыши над таблицей и
прокручивает вдвое больше (~100 px браузера -> 200 px). Тачпад, Ctrl и
Shift + колесо, горизонтальная прокрутка и поля ввода не меняются.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:26:04 +04:00
Andrey Epifancev
ef83a6c361 test(realtime-table): шаг прокрутки колесом — чистые функции
Чистые функции getWheelScrollDelta и canScrollBy для шага прокрутки
колесом мыши в таблице форм (AURORA-1944), с тестами node:test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:26:04 +04:00
Andrey Epifancev
1b8a1a44ad fix(svod2): после смены условий старые строки не рисуются, пока нет ответа нового поколения (полосы загрузки)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:21:51 +04:00
Andrey Epifancev
ea6915c52f fix(svod2): при поиске/фильтре на глубине total берётся из запасного offset=0, а не из долгого ответа за концом списка
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:18:13 +04:00
Andrey Epifancev
eef358c56e feat(svod2): полосы загрузки в строках-заглушках (Skeleton, как у MRT при «Обновить»)
Ячейки строк с data-svod2-placeholder рисуют MUI Skeleton wave высотой 20 с детерминированной шириной.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:18:13 +04:00
Andrey Epifancev
e3a2683b1d Merge origin/feat/236-aurora-1843-svod2-server-paging в web: исправленная база 1842
Конфликты в Svod2Page.jsx (импорты, content) и svod2Styles.test.js разрешены
объединением: серверная пагинация 1843 плюс grid-no-grow, pinning и портал заголовка 1842.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:36:58 +04:00
Andrey Epifancev
7b67740029 fix(dict-vsp): заголовок «Действия» на одной линии с остальными в grid-раскладке
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-29 12:23:02 +04:00
Andrey Epifancev
4cb0862ca5 fix(svod2): заголовок без виртуализации колонок, группы над своими колонками
MRT при виртуализации колонок рисует каждую строку заголовка по индексам
листовых колонок, из-за чего двухуровневые группы разъезжались. Штатный thead
отключён (enableTableHead: false), тот же MRT_TableHead без columnVirtualizer
вставляется порталом в <table>: все заголовки сразу, стили и встроенные фильтры
MRT сохраняются. Тело по-прежнему виртуализировано по строкам и колонкам.

Стили: order -1 для портального thead, flexShrink 0 у td/th (распорки
виртуализации сжимались и сдвигали тело на 2-8 px), скрыта подложка ::before
закреплённых ячеек (перекрывала чередование фона).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:18:41 +04:00
Andrey Epifancev
bf64fa0dc6 fix(dict-vsp): ширины колонок как в test — grid-раскладка не должна менять вид справочника
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:10:47 +04:00
Andrey Epifancev
b834f1008d fix(svod2): grid-no-grow и enableColumnPinning для виртуализации колонок
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:07:38 +04:00
Andrey Epifancev
fbc3601673 perf(web): свод 2 — refine по условиям данных, измерение высоты вне загрузки, окно по поколению
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 00:16:41 +04:00
Andrey Epifancev
d74f5286f9 perf(web): свод 2 — refine во время первой загрузки, «Найдено» без ошибки, измеренная высота строки
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 00:03:43 +04:00
Andrey Epifancev
1aa138a839 perf(web): свод 2 — строки страницами с сервера, поиск, фильтры и варианты через API
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:26:30 +04:00
Andrey Epifancev
325c1c5f15 perf(web): свод 2 — begin(load) не очищает данные до первого ответа
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:22:43 +04:00
Andrey Epifancev
78e06583f4 perf(web): свод 2 — состояние вкладки с поколениями запросов
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:21:04 +04:00
Andrey Epifancev
900a3c6535 perf(web): свод 2 — правка по biome (pages.at(-1))
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:19:09 +04:00
Andrey Epifancev
a9a05c2360 perf(web): свод 2 — чистая логика окна страниц, параметров и вариантов
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:19:00 +04:00
Andrey Epifancev
2421f7ddd5 test(web): свод 2 — окно строк через отступы виртуализатора
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:17:42 +04:00
Andrey Epifancev
f7e553a7ab style(table): переименовать параметр escape в activeCellCss (biome)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 18:14:53 +04:00
Andrey Epifancev
21aded26cc perf(table): статичные стили ячеек в CSS контейнера, overscan строк 2 и колонок 3
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 18:14:38 +04:00
Andrey Epifancev
399234a827 perf(web): свод 2 — виртуализация колонок и статичные стили ячеек одним CSS-правилом
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 18:12:41 +04:00
Andrey Epifancev
4393e964f4 perf(realtime): убрать console.log каждого WebSocket-сообщения
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 18:10:30 +04:00
Andrey Epifancev
17dddc21d3 perf(realtime): updateCells без structuredClone и мутации входа
Копируются только строки на пути к затронутой, остальные сохраняют идентичность;
без изменений возвращается тот же data. Отсутствующие в row.data ключи теперь
вставляются (нужно для AURORA-1844/1848). Тесты с паритетом к прежней реализации.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 18:10:30 +04:00
Andrey Epifancev
12a315b04a perf(table): фиксированная высота строк вместо measureElement (лист, свод 1, свод 2)
Высота строки и ячейки задана явно в rem, виртуализатор не читает offsetHeight.
Мёртвая muiTableRowProps удалена. Overscan строк свода 2: 8 -> 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:10:21 +04:00
Andrey Epifancev
bdb00afc5a test(table): хелпер createFixedRowVirtualizerOptions без чтения layout
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:10:21 +04:00
Andrey Epifancev
b0de763259 perf(dict-vsp): виртуализация строк, состояние модалок вынесено из таблицы
Модалки в DictVspModals (forwardRef), таблица в memo со стабильными колбэками,
enableRowVirtualization, size/minSize/maxSize колонок выверены под grid-раскладку.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:09:52 +04:00
Andrey Epifancev
3ab229f6f8 test(dict-vsp): фильтрация ВСП по ССП вынесена в чистую функцию
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:09:52 +04:00
Andrey Epifancev
365becc55a perf(таблица): стабильные колбэки поиска для SettingsPanel
onSearchNext/onSearchPrevious были стрелками в JSX и ломали memo панели.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:09:26 +04:00
Andrey Epifancev
2cb9006786 perf(web): форматтер чисел свода 1 и сводных проектов создаётся один раз
AURORA-1835: Cell в scaleColumns создавал Intl.NumberFormat на каждую ячейку.
Форматирование вынесено в formatSummaryAmount (summaryFormat.js) с модульным форматтером.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 18:09:12 +04:00
Andrey Epifancev
c724197682 perf(таблица): вынести состояние модалок листа из RealtimeTable в SheetModals
Открытие модалки больше не перерисовывает RealtimeTable: состояние живёт
в memo-компоненте SheetModals, наружу открывается через ref.open(kind).
Проверки выделенной строки остаются в таблице. Редьюсер вынесен в
utils/sheetModalsState.js с node-тестом.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:09:11 +04:00
PotapovaA
7174703149 изменение дублей accessorKey 2026-10-20 16:21:42 +03:00
PotapovaA
bce897f4ed правки 2026-10-16 14:55:58 +03:00
PotapovaA
6c3cd5b2e0 добавление селекта для изменения разрядности в твблицах 2026-10-16 14:41:19 +03:00
PotapovaA
f3539a5f36 новые своды для проектов 2026-10-01 18:20:30 +03:00
PotapovaA
b8632b445d Merge remote-tracking branch 'origin/test' into add-svod-projects 2026-10-01 15:43:47 +03:00
PotapovaA
633c36787d своды для проектов 2026-10-01 15:43:26 +03:00
172b343c69 export-optimize-refresh: очередь загрузки при экспорте 2026-09-30 20:40:21 +03:00
341e8b3936 last-column: Колонка Сумма оплаты структуры формы 4 2026-09-30 14:02:41 +03:00
PotapovaA
02a24954e5 исправила цвета колонок 2026-09-29 15:48:11 +03:00
PotapovaA
253e5d7c82 удаление задач 2026-09-28 19:12:03 +03:00
PotapovaA
056d00b2b4 Merge branch 'test' into ui-tasks 2026-09-28 17:38:04 +03:00
18faf6a724 svod-fix: свод 2 из формы 2, новый свод 1 для формы 3 2026-09-28 17:01:07 +03:00
1e02b0ca61 fix-columns: добавил комментарий, строки из Лимита не удаляются 2026-09-28 13:19:26 +03:00