# Контракт API результата КФО (AURORA-1546 / AURORA-1547) Контракт вкладки «КФО» отчёта версии консолидации — **до реализации** (docs #206, OpenSpec change `kfo-calculation-contract`). Машиночитаемая копия — `dev/api-contract-kfo.openapi.yaml` с тремя полными примерами ответа (их можно брать как фейк для экрана). Когда расчёт появится в `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 `; - **чтение — право `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`). Строки расходов (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`. Тексты `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 (пересчёт).