До этой правки лист "Смета" всех форм выгружался одной плоской
раскладкой SMETA_COLUMNS (41 колонка) — для FORM_2/FORM_4 в выгрузке
были лишние колонки другого направления (поддержка/развитие), которых
нет на экране. Добавлена SMETA_COLUMNS_BY_FORM{"FORM_1","FORM_2","FORM_4"}
с раскладкой колонок строго в порядке экрана каждой формы;
_create_xlsxwriter_sheet_writers теперь выбирает нужный список по
form_type_code. SMETA_COLUMNS остаётся алиасом на FORM_1-раскладку для
обратной совместимости (неиспользуемый openpyxl-словарь, старый тест).
Также TabularSheetWriter.write и XlsxTabularSheetWriter.write теперь
пропускают служебную строку META (как уже делают depth-писатели) —
раньше она давала пустую вторую строку на листе "Смета".
На экране FORM_1/SMETA.js добавлены "Утверждено ИТОГО Год" (Z) и
"Факт ИТОГО Год" (AL) — SQL (v_form_view) всегда их отдавал и
выгрузка Excel их писала, но экран их терял.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AURORA-1848 (#239/#242) убрал null-поля из GET листа/сводов (jsonb_strip_nulls),
оставив PATCH как раньше (задокументированный breaking-change в PR, не баг).
После слияния веток в test это разошлось с тестами, написанными до #239/#242:
- sheet_contract.assert_patch_rows_match_sheet сравнивал PATCH и GET побайтно —
добавлен strip_nulls() на стороне PATCH перед сравнением.
- test_form4_sheet_sums.py сравнивал ответ API с "сырым" v3.jsonb_sum (с null-полями)
и ожидал явный null в некоторых полях ответа — эталон теперь тоже прогоняется через
strip_nulls, а прямые ожидания null заменены на .get(...) is None / отсутствие ключа.
Без изменений контракта API: PATCH продолжает отдавать null явно, как в #242.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
После jsonb_strip_nulls у групп/позиций нет ключа line_id, getDataRowId
возвращал undefined, и row_id в сообщении удаления сериализовался в {}
(KeyError на сервере). Возвращаем null, как до фазы B.
Добавлены тесты updateCells (глубокое отсутствие блока, два обновления в батче)
и сверка jsonb_strip_nulls с _strip_nulls.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
row_json_select(strip_nulls) оборачивает data в jsonb_strip_nulls внутри json_build_object;
флаг пробрасывается через get_json/get_svod1_json/get_svod2_json, роутеры листа и сводов
включают его. Проекты, PATCH/POST/DELETE не затронуты.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#229, #231 и #244 уже заняли 0050-0052 к моменту слияния этого PR.
Переносим form4_sheet_sum на 0053, down_revision=0052, и правим путь
к SQL-файлу в perf/analysis/explain_manual.py.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
При слиянии PR #244 в test оказались две миграции с revision="0051"
и down_revision="0050" (upd_form_cells_full_rows из #231 и
upd_form_cells_subtree из #244) — неоднозначная цепочка alembic.
Фикс-коммит с переименованием 0051→0052 для #244 был подготовлен
локально, но потерялся при повторном force-push после блокировки
классификатором permission — слияние прошло с исходной (дублирующейся)
нумерацией. Переносим upd_form_cells_subtree на 0052.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Три независимых approved PR (#229, #231, #232) добавляли миграцию
с одним и тем же revision=0050 поверх общего down_revision=0049.
#229 слит первым и занял 0050 (project_technical_number_lock),
поэтому здесь переносим upd_form_cells_full_rows на 0051.
Co-Authored-By: Claude Sonnet 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>
Добавлены 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>
AURORA-1845. process() больше не читает user_service.get / bf_service.get на
каждое сообщение: результат кэшируется в ConnectionCache (TTL 30 с, только
положительные результаты).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Выборка строк вынесена в v3._form_affected_rows; FORM_4 отбирает ROOT, GROUP и ITEM только изменённых строк; при sections убираются ключи исключённых разделов.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
asyncpg не принимает aware-datetime для CAST AS TIMESTAMP WITHOUT TIME ZONE (400).
Даты переводятся в пояс оргединицы и становятся naive, SQL приписывает тот же offset.
timezone_from_offset вынесен в src/core/timezones.py, добавлен to_org_local_naive.
Aware-дата при NULL utc_offset оргединицы даёт 422.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
UserInDB наследует новую UserReadBase (email/username/full_name без
валидации формата). Вход (UserBase/UserCreate/UserUpdate) не менялся.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
На PostgreSQL 15 подзапрос в FROM без алиаса не принимается, миграция
падала. Добавлен алиас active_reports, смысл не меняется. Правка старой
миграции: 0012 выполняется раньше любой новой, на базах, где 0012 уже
применена, ничего не меняет.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>