2026-11-06 23:25:40 +04:00

130 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Design: схема среза ЕХ ex-payload-v1
Change `ex-payload-schema`, issue RSHB/arm-ka-docs#208, ветка `feat/208-ex-payload` (от `feat/206-kfo-contract`).
Читатель — Потапова (AURORA-1560, запись среза), следом — исполнитель AURORA-2001 (чтение среза) и тестировщик 1562.
Читают как постановку, без созвона.
## 1. Зачем
Срез ЕХ — самодостаточная заморозка верифицированной версии: его читают и выгружают, когда рабочих таблиц консолидации
уже нет. Всё производное (сводная ОСВ, агрегаты ВГО, шахматка, КФО, гудвилл, корректировки, сравнение с клиентом,
карточка версии) лежит в `ex_snapshot_item.payload` (jsonb). Без схемы payload каждый, кто пишет и читает срез, решает
формат сам — и просмотр среза разойдётся с экраном рабочей версии.
Успех: Потапова пишет срез, проверяя каждый элемент схемой; исполнитель 2001 рендерит и выгружает срез по той же схеме,
не заглядывая в таблицы консолидации.
## 2. Цели, не цели, анти-цели
**Цели** — схема конверта и данных по каждому `item_type`, примеры, проверка в `make check`; правила среза в §15; правки
DBML, без которых схему не записать (`version_card`, действие журнала).
**Не цели** — `proposal.md`.
**Анти-цели**
- Payload, который без таблиц консолидации не прочитать: ссылки допустимы только на то, что живёт не меньше среза —
`document`, `legal_entity`, `related_group`, `company_group`, `app_user` (DOC-274687 правило 4); остальные id (строк
ОСВ, корректировок, правил) — справочно, для рендера не нужны.
- Схема, которая заставляет фронт ЕХ пересчитывать: в payload лежат готовые значения так, как их видел аналитик.
- Повторный перевод единиц: суммы в payload — как в источнике (DOC-274687 правило 5), единица — рядом.
## 3. Ограничения
- JSON Schema 2020-12, как остальные контракты; проверка примеров — `scripts/validate_contracts.py` (`make check`).
- Деньги — строки с двумя знаками (`^-?\d{1,16}\.\d{2}$`), как в API; `null` — «не рассчитано».
- Схема версионируется: `schema_version` в конверте и `ex_snapshot_item.payload_schema_version` = `"1.0"`;
несовместимое изменение — `ex-payload-v2.schema.json` рядом, старые срезы не переписываются.
- Модель — DBML v5.26 из #207: эта ветка дописывает в неё пункты changelog (версия ещё не слита, номер не тратим).
## 4. Что решено
### 4.1 Конверт элемента
Каждый `payload` (у всех `item_type`, кроме `primary_document`, у которого `payload = null` и есть `document_id`):
```
{schema_version: "1.0", item_type, units: "rub" | "thousand_rub" | "none", generated_at (date-time),
source_version_no (int), data: {...}}
```
`item_type` в конверте совпадает с колонкой; `units` — единица всех денег в `data`; `generated_at` — момент расчёта
перед записью. Конверт — `additionalProperties: false`.
### 4.2 Данные по типам
Общий принцип (**главнее любой таблицы ниже**): `data` — **ответ API экрана рабочей версии в момент верификации, теми же
DTO, именами и вложенностью**, без полей редактирования (`editable`, `read_only_reason`, `lock`) и плюс то, что нужно
**выгрузке xlsx рабочей версии** (срез выгружается на лету из payload и должен совпадать с выгрузкой рабочей версии —
proposal, AURORA-1562). Обязательные поля задаёт схема; лишние поля допускаются (`additionalProperties: true`), рендер
опирается только на обязательные. Ссылки на компании — как в DTO (`Ref {id, name}` или `legal_entity_id` +
`legal_entity_name`/`_inn`): имена в DTO уже есть, отдельный `CompanyRef` не вводится; список компаний периметра с ИНН —
в `version_card.members`. Id строк ОСВ, корректировок и правил остаются в данных справочно.
| `item_type` | `units` | `data` (DTO) |
|---|---|---|
| `summary_osv` | `rub` | `{rows: ConsolidatedRowOut[], summary: OsvSummaryOut}` (`dev/api-contract-slice-4.md` §3) — строки свода, включая `match_kind` (колонка «Способ сопоставления» выгрузки), и `summary.gaps[]` (лист «Пробелы») |
| `vgo_pairs` | `rub` | `{balance: CorrectionTableOut, income: CorrectionTableOut \| null}` (`dev/api-contract-corrections.*`) — агрегаты таблицы корректировок со сторонами и строками ОСВ (`entries`, нужны выгрузке), нераспределённые строки, итоги; без `editable`/`read_only_reason`; `income` — `null`, пока агрегатов ОФР нет (AURORA-1999). Это вход КФО, не прежний экран «Пары ВГО» |
| `chess` | `rub` | `{matrix_all: IntercompanyMatrixOut, presets: [{rule_no, matrix: IntercompanyMatrixOut}], result_table}` (`dev/frontend-api-guide.md` §5.7.1) — «все счета», пресет каждого правила ББ версии правил, результирующая таблица пяти статей |
| `kfo_balance` / `kfo_income` | `thousand_rub` | `KfoResultOut` (`dev/api-contract-kfo.md`) без `editable`/`read_only_reason`, с `balance` **или** `income` своего отчёта (второй отчёт — в своём элементе); `requisites` (в т. ч. `corrections_balance`/`_income` для формы 2.03.01.01с), `summary`, `goodwill`, обоснование, `warnings` — в обоих элементах одинаковые |
| `goodwill` | `rub` | только при `goodwill_enabled`: `GoodwillTableOut` (`dev/api-contract-goodwill.*`) без `editable`/`read_only_reason` — строки, группы, итоги |
| `corrections` | `rub` | применённые корректировки (`APPLIED`; `REVOKED` нет, `DRAFT` не пишется): `{items: [{statement_type, rule_no, company_1: Ref, company_2: Ref, correction: CorrectionOut}]}` — `CorrectionOut` как в контракте корректировок (`updated_by`/`updated_at` — кто и когда сохранил) |
| `other_report` | `thousand_rub` | только при `scenario = CONFIRM`: `{report_kind: "client_kfo_comparison", lines[] {statement_type, line_code, line_name, client_amount, client_vgo_amount, bank_amount, bank_vgo_amount, deviation_amount, deviation_percent, deviation_reason, comment}}` по `client_kfo_line` (контракта API ещё нет — AURORA-1549; имена — как в DBML) |
| `version_card` | `none` | один на срез, **новый тип**: `company_group {id, name}`, `related_group {id, name}`, `reporting_date`, `year`, `quarter`, `version_no`, `scenario`, `approach`, `goodwill_enabled`, `initiator` (UserRef); `quarter` — `"Q1"…"Q4"`, `initiator_office {id, name}`, `members[]` `{legal_entity_id, name, inn, is_head_entity}`, `history[]` как `VersionCard.history` (`{from_status, to_status, changed_by (UserRef), changed_at, comment}`), `verified_by` (UserRef), `verified_at`, `uob_decision_no`, `uob_decision_date`, `methodology_deviations` |
`UserRef {user_id, full_name}` — ФИО на дату верификации (пользователь может быть отключён, но не удаляется).
### 4.3 Правила среза — §15 `05-domain-rules.md`
Выжимка DOC-274687 под нашу модель: срез пишется один раз при «Верифицирован» (после фиксации ГСК, AURORA-2002), один на
версию (`source_version_id` уникален); состав — таблица 4.2 + `primary_document` по каждому документу
`consolidation_document` + `ex_snapshot_member` по каждому ЮЛ версии; строки форм без кода, непривязанные документы и
черновики корректировок не входят; `goodwill` — только при `goodwill_enabled`, `other_report` — только при `CONFIRM`;
номер среза — следующий по (ГСК, год, квартал) (уникальный индекс DBML; новая ГСК после смены периметра начинает
с 1); прежний срез запись в архив не переводит (архив ручной, решение 27.10.2026): переход в «Верифицирован» уже
проверил, что у той же **ГК** за ту же дату актуального среза нет (AURORA-2002), поэтому после записи актуальный срез по
ГК и дате ровно один — новый; архивным срезам той же ГК и даты без ссылки ставится `superseded_by_snapshot_id` нового
(ГК, а не ГСК — чтобы ссылку получил и срез прежней ГСК); разархивирование ссылку снимает (AURORA-2001). Если заказчик
выберет ключ «ГСК × дата» (вопрос AURORA-1397) — меняются проверка 2002 и это правило связи, схема payload — нет; производные считаются в
последний раз и замораживаются, `kfo_result.is_frozen = true`; сбой записи не отменяет верификацию (повтор — вопрос
заказчику, OQ); аудит записи — `EX_SNAPSHOT_WRITE`. Вопросы заказчику (кто архивирует, ключ ГК или ГСК, повтор при
сбое, PDF) — OQ со ссылкой на AURORA-1397.
### 4.4 DBML v5.26 (дописать в changelog #207)
- `ex_snapshot_item.item_type` — + `version_card`; `payload` — «конверт `contracts/ex-payload-v1.schema.json`, у
`primary_document` — null»; `payload_schema_version` — `"1.0"`.
- `audit_action` + `EX_SNAPSHOT_WRITE` (раздел `KFO`, `object_type = ex_snapshot`, `after` — номер среза, ГСК, дата,
число элементов).
- `ex_snapshot.status` — «ACTUAL — не архивирован; ARCHIVE — архивирован пользователем в ЕХ» (сейчас — «предыдущие →
ARCHIVE», противоречит ручному архиву). Миграция колонок не нужна; значение enum — в миграции AURORA-1560.
### 4.5 Вложения в Jira
После открытия PR: AURORA-1560 — схема, §15 (`05-domain-rules.md`), DBML; комментарий со ссылкой на PR. AURORA-2001 —
ссылка в комментарии (чтение — по той же схеме).
## 5. Что остаётся исполнителю плана
Точная разметка схемы (`$defs`, имена), тексты §15, числа примеров (из `scripts/kfo_contract/kfo_example.py`), какие
элементы показать примерами (минимум: `kfo_balance`, `version_card`, `corrections`, `summary_osv`).
## 6. Decisions made without discussion
| # | Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|---|
| 1 | 06.11.2026 | `data` = замороженный ответ экрана теми же именами полей, обязательные поля — схемой, лишние допускаются | Рендер ЕХ переиспользует экраны рабочей версии; исполнителю не надо вырезать поля | Лишние поля раздувают срез; рендер, опирающийся на необязательное поле, сломается на старом срезе |
| 2 | 06.11.2026 | `vgo_pairs` — агрегаты таблицы корректировок, не прежний экран «Пары ВГО» | Это вход КФО и то, что аналитик видит в UI | Если ЕХ нужна детализация упаковки по строкам — новый тип элемента |
| 3 | 06.11.2026 | `chess` — «все счета» + пресет каждого правила ББ + результирующая таблица; ручной выбор счетов не замораживается | Ручной режим — исследование, его состояние не хранится | В ЕХ нельзя посмотреть шахматку по произвольным счетам |
| 4 | 06.11.2026 | Карточка версии — новый `item_type version_card` с ФИО, а не колонки `ex_snapshot` | DOC-274687 оставляет выбор команде; элемент не трогает схему таблицы | Фильтры ЕХ по участникам маршрута пойдут по jsonb |
| 5 | 06.11.2026 | В срез — только `APPLIED` корректировки (DOC-274687 предполагал и `REVOKED`) | `REVOKED` в системе нет: корректировку меняют пересохранением (ответ Лысенко п. 9) | История правок корректировки — только в аудите |
| 6 | 06.11.2026 | DBML-правки — в ту же неслитую v5.26, что в #207 | Одна версия на пакет КФО + ЕХ; номер не тратим | Если #207 сольют раньше и v5.26 «закроется» — перенести правки в v5.27 |
| 7 | 06.11.2026 | `EX_SNAPSHOT_WRITE` — раздел журнала `KFO` | Раздела `EX` в `audit_section` нет; запись среза — часть верификации КФО | Фильтр журнала по ЕХ не найдёт событие |
| 9 | 06.11.2026 | Payload покрывает и выгрузку xlsx рабочей версии (пробелы, способ сопоставления, строки ОСВ агрегатов, сводка корректировок), не только экран | Срез выгружается на лету из payload, выгрузка должна совпасть с рабочей (1562); ответ на вопрос проверки исполнителем 1 | Срез заметно больше (строки ОСВ агрегатов) |
| 10 | 06.11.2026 | `data` — DTO API без изменений имён и вложенности; таблица §4.2 подчинена принципу | Рендер и выгрузка ЕХ переиспользуют виджеты и выгрузки рабочей версии без маппинга; вопрос проверки 2 | Изменение DTO API после 1.0 требует новой версии схемы или совместимого расширения |
| 11 | 06.11.2026 | Нумерация среза — по ГСК; «один актуальный» и `superseded_by` — по ГК и дате | Как в DBML и DOC-274686 (решения Р-35/Р-36 по ГК); вопрос проверки 3 | При ключе «ГСК» у заказчика — правка правила связи и проверки 2002 |
| 12 | 06.11.2026 | Схема обязывает то, что читают выгрузки xlsx рабочей версии: итоги шахматки `total_debit`/`total_credit`, правило пресета и `rules` таблицы корректировок, `pairs`, `balance` корректировки, `entry_ids` строк | Ревью C/I: без них выгрузка среза не совпадёт с рабочей (решение #9) | Больше обязательных полей — писателю нельзя «урезать» DTO |
| 13 | 06.11.2026 | Проценты к меньшей стороне (шахматка, агрегаты, корректировки) — без ограничения разрядов; квартал — `"Q1"…"Q4"`; `updated_by` — `Ref`, как в DTO | Ревью C-1, C-2, I-2: настоящие ответы API не проходили схему | — |
| 14 | 06.11.2026 | КФО в срезе — только замороженная и `VERIFIED`; карточка — непустые состав и история | Ревью M-1: пустой или незамороженный payload не должен проходить | Срез версии без истории маршрута (миграция старых данных) схему не пройдёт |
| 15 | 06.11.2026 | Ошибка пресета правила (422 `INVALID_RULE`/`ACCOUNTS_REQUIRED`) — пресет пропускается; другая ошибка сборки — сбой записи среза | Ревью M-5 | Срез без пресета правила, которое на экране тоже не строится |
| 8 | 06.11.2026 | Без отдельного согласования дизайна с владельцем: схема описана в чате 06.11 («все так»), владелец сказал «делай схему» | Срок 12.11, владелец просил не останавливаться | Расхождение с ожиданием владельца видно только в PR |