arm-ka-docs/dev/api-contract-kfo.md

182 lines
15 KiB
Markdown
Raw Permalink 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.

# Контракт API результата КФО (AURORA-1546 / AURORA-1547)
Контракт вкладки «КФО» отчёта версии консолидации — **до реализации** (docs #206, OpenSpec change
`kfo-calculation-contract`). Машиночитаемая копия — `dev/api-contract-kfo.openapi.yaml` с тремя полными примерами
ответа (их можно брать как фейк для экрана); она генерируется `scripts/kfo_contract/gen_kfo_openapi.py` из
сквозного примера §8 (`scripts/kfo_contract/kfo_example.py`, там же проверки правил) — руками не править. Когда расчёт появится в `arm-core`, копия выгружается из `app.openapi()` и
сверяется тестом, как у гудвилла; если файлы разойдутся с кодом — прав код, оба файла правятся вслед за ним.
Источники, в порядке приоритета:
1. `05-domain-rules.md` §8 «КФО» — алгоритм (п. 1–12) и сквозной пример. Здесь формулы не повторяются — только
ссылки «п. N».
2. DBML v5.26: `kfo_result`, `kfo_result_line`, `kfo_result_line_company`, `consolidation_version`.
3. DOC-274647 «Экран: КФО» (черновик) и макет `[31-04]`, `[31-04-01]` — состав колонок, состояния экрана.
## 1. Общее
Как в `dev/api-contract-corrections.md` §1 и `dev/api-contract-goodwill.md` §1:
- префикс `/api/v1`, `Authorization: Bearer <JWT>`;
- **чтение — право `KFO_VIEW`** (не `CONSOLIDATION_VIEW`, как у корректировок и гудвилла: право в seed есть и означает
ровно это), **правки — `CONSOLIDATION_EDIT`** (`KFO_EDIT` в seed нет);
- версия вне видимости — 404 `NOT_FOUND`, как несуществующая;
- доменные ошибки — `{"detail": {"code", "message", ...}}`, 422 валидации тела — массив pydantic;
- **деньги — строки с двумя знаками в тыс. руб.** (`"588500.00"`), поле `units = "thousand_rub"`. Это отличие от
корректировок и гудвилла, где рубли: результат КФО по определению в тысячах (округление — часть расчёта, §8 «КФО»
п. 6), рубли из него не восстановить. Переключатели «Единицы» (тыс. / млн) и «Знаков после запятой» — только показ;
- проценты — строки с двумя знаками (`"70.42"`), `null` — не определён (нулевая база);
- порядок проверок правок: право (403) → видимость (404) → версия редактируема (409 `VERSION_NOT_EDITABLE`,
`LOCK_REQUIRED`, `LOCKED_BY_OTHER`) → операция. Фронт берёт `editable` / `read_only_reason` из ответа и правило сам не
повторяет; у пользователя без `CONSOLIDATION_EDIT` — `read_only_reason = "VERSION_NOT_EDITABLE"`.
**Фронт ничего не считает.** Доли, итоги, «в т. ч. взаимные», расхождения, базы и пределы порогов, признак превышения
и подсветка строки базы приходят с сервера.
## 2. Понятия
- **Строки** — бланк ББ и ОФР редакции на отчётную дату версии (66н до 31.12.2024, ФСБУ 4 позже) плюс служебная
`GOODWILL` в разделе I сразу после 1170 (§8 «КФО» п. 1, 8). Приходят все строки бланка в его порядке (`ordinal`),
включая нулевые; `is_total` — итоговая строка.
- **По компании** (`companies[]` строки, порядок — как `KfoResultOut.companies`): «По данным клиента»
(`client_amount`), «В том числе взаимные обязательства / обороты» (`vgo_amount`), «Очищенные показатели»
(`cleaned_amount = client_amount − vgo_amount`), «Доля в показателе по группе, %» (`share_percent`; `null` при нулевом
итоге строки или `|доля| ≥ 1000 %`). Строки расходов
(2120, 2210, 2220, 2330, 2350) — со знаком минус, и их `vgo_amount` тоже (п. 2, 6). У `GOODWILL` разреза нет.
- **По строке:** `sum_amount` (Σ клиента), `vgo_amount` (Σ взаимных = `eliminated_auto + eliminated_manual`),
`goodwill_adjust` (у `GOODWILL`, 1100, 1600), **`bank_amount`** — «КФО, подготовленная банком»
(`sum_amount − vgo_amount + goodwill_adjust`), `deviation_amount` — «Расхождения» (у итогов `null`), `comment`.
- **Сводка расхождений** (`summary.balance`, `summary.income`) — две плашки экрана: сумма расхождений, база (`base_kind`:
ЧА / ВБ / прибыль от продаж / выручка), предел и процент от базы, `exceeded` — красная плашка (п. 10). При превышении
у строки базы `is_threshold_base = true` — подсветка.
- **Обоснование** (`threshold_justification`) — принятие риска при превышении; `justification_required` — превышение
есть хотя бы по одному отчёту. Обязательность перед отправкой на верификацию проверяет переход статуса (AURORA-2002),
не этот API.
- **Реквизиты формы** (`requisites`, п. 11): ГК, ГСК, отчётная дата, номер и статус версии, сценарий, подход (пакет
документов), причины отклонений от Методики (`methodology_deviations` — редактируется здесь), решение УОБ (только
чтение), корректировки — число агрегатов с применённой корректировкой и Σ их исключений.
- **Предупреждения** (`warnings[]`, п. 12) — не блокируют: `FORM_MISSING`, `FORM_AMBIGUOUS`, `FORM_UNITS_UNKNOWN`,
`LINE_OUTSIDE_BLANK`, `UNALLOCATED_ROWS` (`count` — строк), `INCOME_RULES_MISSING`, `GOODWILL_NOT_APPLIED`,
`GOODWILL_ROW_REASSIGNED`, `CORRECTION_SUPERSEDED_BY_GOODWILL` (корректировка агрегата правила 2 не применена —
пару закрыл гудвилл). Тексты `message` — с сервера, фронт показывает как есть.
## 3. Эндпоинты
| Метод и путь | Право | Запрос | Ответ | Ошибки |
|---|---|---|---|---|
| `GET /consolidations/versions/{version_id}/kfo` | `KFO_VIEW` | — | 200 `KfoResultOut` | 404 `NOT_FOUND` |
| `PUT /consolidations/versions/{version_id}/kfo/annotations` | `CONSOLIDATION_EDIT` | `KfoAnnotationsIn` | 200 `KfoResultOut` | 404 `NOT_FOUND`, 409 правок версии, 422 `UNKNOWN_LINE`, 422 валидации тела |
Выгрузки («Таблица», «Форма 2.03.01.01с») — AURORA-1565, отдельным контрактом.
### 3.1 `GET …/kfo` — результат
Весь результат одним ответом, без страниц. **Пока версия не заморожена, ответ — по текущим данным версии**: если
что-то изменилось после `computed_at` (документы, сопоставление, разнесение, корректировки, гудвилл), сервер
пересчитывает и сохраняет результат до ответа (как он это обнаруживает — AURORA-1417). Кнопки «Рассчитать» нет. У
замороженной версии (`is_frozen = true`, ставится при «Верифицирован») — сохранённый результат без пересчёта,
`editable = false`.
Версия без компаний или без форм — не ошибка: строки бланка с нулями и `warnings` (`FORM_MISSING` по каждой компании
и отчёту).
### 3.2 `PUT …/kfo/annotations` — ручные поля
Тело:
```json
{
"comments": [{"statement_type": "balance", "line_code": "1520", "comment": "Акт сверки на 01.10 не получен"}],
"threshold_justification": "Расхождение по 1520 — сверка в работе; риск принят",
"methodology_deviations": null
}
```
- Поле, которого нет в теле, не меняется; `null` — очищает. Пустой объект `{}` — допустим, ничего не пишет.
- `comments[]` — только изменённые строки; `comment` ≤ 2000 символов, `null` или пустой после обрезки — очистить.
Строки нет в бланке версии (или `GOODWILL` в `income`) — 422 `UNKNOWN_LINE` с `detail.statement_type`,
`detail.line_code`; ничего не сохраняется.
- `threshold_justification`, `methodology_deviations` — ≤ 4000 символов; длиннее — 422 массивом pydantic.
- Обоснование можно сохранить и без превышения — сервер его не стирает; `justification_required` от этого не меняется.
- Всё одной транзакцией; комментарии и обоснование переживают пересчёт (по `statement_type`, `line_code`).
- Аудит — одно событие `KFO_EDIT` на запрос (`object_type = kfo_result`, раздел `KFO`), `before` / `after` — только
изменённые поля. Запрос без изменений аудит не пишет.
Ответ — `KfoResultOut` целиком (с пересчётом, если он нужен).
## 4. Схемы
Полностью — в OpenAPI. Кратко:
```
KfoResultOut
version_id uuid; computed_at date-time; is_frozen bool; editable bool; read_only_reason enum|null
units "thousand_rub"
companies[] {legal_entity_id, name, inn} — порядок колонок (по имени)
requisites KfoRequisitesOut
summary {balance: KfoThresholdOut, income: KfoThresholdOut}
justification_required bool; threshold_justification string|null
goodwill {enabled bool, total money}
balance, income {lines: KfoLineOut[]}
warnings[] KfoWarningOut {code, message, legal_entity_id?, statement_type?, count?}
KfoLineOut
line_code, line_name, ordinal, is_total, is_threshold_base
companies[] {legal_entity_id, client_amount, vgo_amount, cleaned_amount, share_percent|null} (у GOODWILL — [])
sum_amount, vgo_amount, eliminated_auto, eliminated_manual, goodwill_adjust, bank_amount money
deviation_amount money|null (у итогов null)
comment string|null
KfoThresholdOut
deviation_total money; base_kind NET_ASSETS|BALANCE_TOTAL|SALES_PROFIT|REVENUE; base_amount money
limit_percent "10.00"|"1.00"; limit_amount money; deviation_percent pct|null; exceeded bool
KfoRequisitesOut
company_group_name, related_group_name|null, reporting_date, version_no, version_status, scenario, approach
methodology_deviations|null, uob_decision_no|null, uob_decision_date|null
corrections_balance, corrections_income {count int, amount money}
KfoAnnotationsIn {comments?: [{statement_type, line_code, comment|null}], threshold_justification?, methodology_deviations?}
```
## 5. Состояния экрана (DOC-274647) → поля ответа
| Состояние | Как видно из ответа |
|---|---|
| Нет форм у части компаний | `warnings[].code = FORM_MISSING` с `legal_entity_id`, `statement_type`; у компании нули |
| ОФР не исключается автоматически | `INCOME_RULES_MISSING` (до AURORA-1999); взаимные обороты — только корректировками |
| Есть нераспределённые строки | `UNALLOCATED_ROWS`, `count` |
| Превышение порога | `summary.*.exceeded = true`, `is_threshold_base` у строки базы, `justification_required = true` |
| Гудвилл учтён | `goodwill.enabled = true`, `goodwill.total`, строка `GOODWILL` с `bank_amount` |
| Только чтение | `editable = false`, `read_only_reason`; при «Верифицирован» ещё `is_frozen = true` |
## 6. Примеры
Три полных ответа — в OpenAPI (`examples` у `GET …/kfo`), на числах сквозного примера §8 «КФО» (три компании, бланк
сокращён до строк примера; настоящий ответ содержит все строки бланка):
- `goodwill_exceeded` — гудвилл включён, превышение порога ББ (42 900 > 37 100), `justification_required = true`;
- `goodwill_disabled` — та же версия без учёта гудвилла: строка `GOODWILL` = 0, 1600 = 580 500;
- `frozen` — «Верифицирован»: `is_frozen`, `editable = false`, обоснование, комментарий 1520, реквизиты УОБ.
Фрагмент `goodwill_exceeded` — строка 1230:
```json
{
"line_code": "1230", "line_name": "Дебиторская задолженность", "ordinal": 40,
"is_total": false, "is_threshold_base": false,
"companies": [
{"legal_entity_id": "1111…", "client_amount": "520000.00", "vgo_amount": "120000.00", "cleaned_amount": "400000.00", "share_percent": "70.42"},
{"legal_entity_id": "2222…", "client_amount": "130000.00", "vgo_amount": "10000.00", "cleaned_amount": "120000.00", "share_percent": "21.13"},
{"legal_entity_id": "3333…", "client_amount": "48000.00", "vgo_amount": "0.00", "cleaned_amount": "48000.00", "share_percent": "8.45"}
],
"sum_amount": "698000.00", "vgo_amount": "130000.00", "eliminated_auto": "100000.00", "eliminated_manual": "30000.00",
"goodwill_adjust": "0.00", "bank_amount": "568000.00", "deviation_amount": "2900.00", "comment": null
}
```
## 7. Открытые вопросы
Варианты, принятые на старте, могут измениться по ответу заказчика — контракт меняется вместе с §8: OQ-180 (строки вне
бланка), OQ-181 (колонки), OQ-182 (значение без формы), OQ-183 (итоговая строка), OQ-184 (округление), OQ-185 (место
гудвилла), OQ-186 (объём расхождений), OQ-187 (ЧА), OQ-188 (нулевая база), OQ-189 (действие при превышении),
OQ-190/191 (подход по Методике, согласование клиентом — полей нет), OQ-192 (пересчёт), OQ-194 (КФО при неполном
комплекте).