182 lines
15 KiB
Markdown
182 lines
15 KiB
Markdown
# Контракт 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 (КФО при неполном
|
||
комплекте).
|