Листы Операц/АХР/КВ формы «Смета РФ» (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>
На листе «Смета» формы «Смета РФ» итоговые колонки каждой группы
(план/утверждённые/факт «Итого: Поддержка» и 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>
Удаление строки в формах 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>
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>
Хук useWheelScrollStep перехватывает щелчок колеса мыши над таблицей и
прокручивает вдвое больше (~100 px браузера -> 200 px). Тачпад, Ctrl и
Shift + колесо, горизонтальная прокрутка и поля ввода не меняются.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Чистые функции getWheelScrollDelta и canScrollBy для шага прокрутки
колесом мыши в таблице форм (AURORA-1944), с тестами node:test.
Co-Authored-By: Claude Opus 5.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>
Ячейки строк с data-svod2-placeholder рисуют MUI Skeleton wave высотой 20 с детерминированной шириной.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Конфликты в Svod2Page.jsx (импорты, content) и svod2Styles.test.js разрешены
объединением: серверная пагинация 1843 плюс grid-no-grow, pinning и портал заголовка 1842.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Селектор строк данных свода 2 исключает скелетоны MRT, settle ждёт 0 скелетонов;
first_rows_ms через waitForFunction(raf); счётчики загрузки не пишутся в запись фильтра;
scale_compare различает view/search; api_split не считает строки для facets;
README: порядок до/после и новые метрики.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
api_split: GO/RF полный и compact ответ страницей 300, /facets, RF compact с поиском.
explain_manual: svod2_go_page, svod2_rf_page, svod2_rf_search, svod2_go_facets
(год из манифеста). scale_compare: svod2_key разводит полный ответ, страницу и facets.
README: раздел «Свод 2 после AURORA-1843» (порядок до/после, сверка, команды, --only/--repeats).
Тесты: perf/tests/test_svod2_analysis.py.
Co-Authored-By: Claude Haiku 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>