arm-ka-docs/dev/api-contract-goodwill.md
Andrey Epifancev 6c7408a9aa Гудвилл: в экспорте строка «Нет вложений» помечена (#204)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 18:52:59 +04:00

262 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.

# Контракт API таблицы гудвилла (AURORA-1543 / AURORA-1544)
Контракт таблицы гудвилла версии консолидации — по факту реализации arm-core (#222, OpenSpec change `goodwill-table`).
Машиночитаемая копия — `dev/api-contract-goodwill.openapi.yaml`: она выгружена из `app.openapi()` и сверяется с
приложением тестом `arm-core/api/tests/test_goodwill_contract.py`. Если файлы расходятся, прав код, а оба файла
правятся вслед за ним.
Источники, в порядке приоритета:
1. **DOC-259025 «Гудвилл»** (вики аналитики, версия 03.11.2026, Лысенко) — пары, жизненный цикл строк, формулы, итоги,
проверки ввода.
2. `05-domain-rules.md` §8 «Гудвилл» — выжимка того же с решениями по допущениям.
3. DBML v5.24: `Table goodwill`, `consolidation_version.goodwill_enabled`.
4. Макет `[31-03]` «Гудвилл» (таблица, режим «Редактировать»).
## 1. Общее
Всё как в `dev/api-contract-slice-4.md` §1 и `dev/api-contract-corrections.md` §1:
- префикс `/api/v1`, `Authorization: Bearer <JWT>`;
- чтение и экспорт — право `CONSOLIDATION_VIEW`, правки — `CONSOLIDATION_EDIT`;
- версия вне видимости — 404 `NOT_FOUND`, как несуществующая;
- доменные ошибки — `{"detail": {"code", "message", ...}}`, 422 валидации тела — массив pydantic;
- **деньги — строки с двумя знаками в рублях** (`"1000000.00"`) или `null`; `null` — «не рассчитано», а не ноль.
Перевод в тыс./млн руб. — на фронте, после суммирования;
- **доля владения — строка с двумя знаками** (`"80.00"`) или `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. Понятия
- **Пара ГО → ДО.** ГО — компания периметра, у которой в сводной ОСВ версии есть строки счёта `58.01` или его потомков
(по `chart_of_account.parent_id`) с сопоставленным контрагентом из периметра; этот контрагент — ДО. Признак
`is_head_entity` **не используется**: одна компания может быть ГО в одном блоке и ДО в другом, встречные направления —
разные пары. Пустой контрагент, вложение в себя и контрагент вне периметра пару не дают. Ручного добавления пар нет.
- **Финансовые вложения (`financial_investment_amount`, ФВ)** — сумма заполненных `closing_debit` подходящих строк
пары; ФВ = 0 — пары нет.
- **«Нет вложений» (`has_investments = false`).** Сохранённая пара, которая больше не находится (замена ОСВ, нулевое
сальдо, компания вышла из периметра). Строка остаётся с сохранёнными долей, комментарием и галочкой, все денежные поля
— `null`, в итоги и КФО не входит. Повторное появление пары восстанавливает расчёт по текущим данным.
- **Уставный капитал ДО (`charter_capital_amount`, УК)** — стр. `1310` подтверждённого ББ ДО, привязанного к версии,
по колонке **конца отчётного квартала** (не по `is_current`), в рублях: единицы формы читаются словарём единиц
`arm_statements` («тыс.руб.», «в тысячах рублей» → тыс. руб.). Повторы кода 1310 суммируются. Ноль — допустимое значение.
Нет ББ, строки 1310 или колонки даты, единицы не распознаны — `null` и предупреждение `CAPITAL_SOURCE_MISSING`;
у ДО несколько подходящих ББ — `null` и `CAPITAL_SOURCE_AMBIGUOUS`.
- **Добавочный капитал (`additional_capital_amount`)** — стр. `1350` того же ББ, справочно; в формулу не входит
(ASSUMPTION OQ-10).
- **Доля владения (`ownership_share`)** — ручной ввод, % от `0` до `100`, не больше двух знаков после запятой.
- **Доля участия (`participation_amount`, P)** = `round(УК × доля / 100, 2)` (`ROUND_HALF_UP`), **гудвилл
(`goodwill_amount`, G)** = `ФВ − P`, может быть отрицательным и нулевым. P и G считаются **только у действующей
отмеченной строки** (`include_in_kfo = true`) с заполненными долей и УК, иначе `null`.
- **Учёт в отчёте (`include_in_kfo`)** — галочка строки. У отмеченной действующей строки обязательны доля и непустой
комментарий. Снятие галочки не стирает долю и комментарий.
- **Признак учёта гудвилла (`goodwill_enabled`)** — переключатель версии (решение OQ-27: на вкладке «Гудвилл», не в
модалке создания). Выключен — таблица и ручной ввод не меняются, но в КФО ничего не уходит
(`goodwill_in_kfo_amount = "0.00"`).
- **Предупреждения (`warnings[]`)** — `{code, message}`, не блокируют сохранение:
`CAPITAL_SOURCE_MISSING`, `CAPITAL_SOURCE_AMBIGUOUS` (см. УК), `OWNERSHIP_TOTAL_EXCEEDED` — сумма заполненных долей
действующих пар разных ГО в одной ДО больше 100 % (показывается у всех строк этой ДО).
## 3. Эндпоинты
| Метод и путь | Право | Запрос | Ответ | Ошибки |
|---|---|---|---|---|
| `GET /consolidations/versions/{version_id}/goodwill` | VIEW | — | 200 `GoodwillTableOut` | 404 `NOT_FOUND` |
| `PUT /consolidations/versions/{version_id}/goodwill` | EDIT | `GoodwillBatchIn` | 200 `GoodwillTableOut` | 404 `NOT_FOUND` (версия или пара), 409 правок версии, 422 `INVALID_SHARE`, `COMMENT_REQUIRED`, `MONEY_OUT_OF_RANGE`, `DUPLICATE_ROW`, 422 валидации тела |
| `PUT /consolidations/versions/{version_id}/goodwill-settings` | EDIT | `GoodwillSettingsIn` | 200 `GoodwillTableOut` | 404, 409 правок версии |
| `GET /consolidations/versions/{version_id}/goodwill/export?sign=all\|positive\|negative\|zero` | VIEW | — | файл xlsx | 404 `NOT_FOUND`, 422 неизвестный `sign` |
### 3.1 `GET …/goodwill` — таблица
Все строки одним ответом, без страниц. Порядок строк — имя ГО, имя ДО, затем id; `groups` — в том же порядке ГО.
Чтение ничего не пишет (записи `goodwill` создаются только сохранением). Нет пар и сохранённых строк — `rows = []`,
`groups = []`, итоги `"0.00"`.
### 3.2 `PUT …/goodwill` — сохранить набор строк
Тело — изменённые аналитиком строки целиком (`ownership_share`, `include_in_kfo`, `comment` каждой строки приходят
каждый раз). **Пакет сохраняется в одной транзакции**: сначала проверяются все строки, ошибка любой — ответ с её ключом
(`detail.head_legal_entity_id`, `detail.subsidiary_legal_entity_id`) и ничего не сохраняется.
- Пара должна быть в текущей таблице (действующая или «Нет вложений»), иначе 404 `NOT_FOUND` с ключом пары.
- Одна пара дважды в пакете — 422 `DUPLICATE_ROW`.
- `ownership_share` — `null` или строка `0`…`100`, не больше двух знаков после запятой, запятая или точка
(`"80,5"` = `"80.5"`). Лишние знаки, отрицательные, экспонента — 422 `INVALID_SHARE` (без округления). У отмеченной
строки доля обязательна — `null` даёт `INVALID_SHARE`.
- `comment` — до 1000 символов; у отмеченной строки пустой после обрезки — 422 `COMMENT_REQUIRED`.
- У строки «Нет вложений» `null` в доле или комментарии не стирает сохранённое значение.
- Строка, ввод которой совпадает с сохранённым (или для пары без записи — с умолчанием «не отмечена, доли и комментария
нет»), не перезаписывается: `updated_by/updated_at` не меняются, аудита нет. Поэтому фронт может отправлять весь набор
отмеченных строк, но лучше — только изменённые.
- У изменённой действующей строки вместе с вводом сохраняется снимок сумм (ФВ, УК, 1350, P, G); сумма вне
`numeric(18,2)` — 422 `MONEY_OUT_OF_RANGE`.
- Аудит — `GOODWILL_EDIT` на каждую изменённую строку (`object_type = goodwill`, `before`/`after` — строка ответа).
Ответ — пересчитанная таблица целиком (строки, итоги по ГО и общие).
### 3.3 `PUT …/goodwill-settings` — переключатель версии
Меняет `consolidation_version.goodwill_enabled`. Повтор прежнего значения ничего не пишет. Изменение — аудит
`GOODWILL_EDIT` (`object_type = consolidation_version`, `changed_field = goodwill_enabled`). Ручной ввод строк не
трогается.
### 3.4 `GET …/goodwill/export` — xlsx
Лист «Гудвилл»: восемь колонок экранной таблицы (Учёт в отчёте, Наименование дочерней организации, Доля владения,
Фондовые вложения головной организации, Доля участия ГО в УК, Уставный капитал, Гудвилл (+/−), Комментарий), блок на
каждую ГО со строкой «Всего», в конце — «Итого по листу». Деньги — в рублях, `null` — пустая ячейка; у строки «Нет
вложений» в колонке ФВ — текст «Нет вложений». `sign` оставляет
строки с гудвиллом нужного знака (нерассчитанные в фильтры по знаку не попадают), итоги остаются по полному составу.
Выгружается последнее сохранённое состояние. Имя — `Гудвилл_<ГК>_<год>_<квартал>.xlsx` в `Content-Disposition`
(`filename*`). Аудит `EXPORT` с `{report: "goodwill", rows: <число строк в файле>}`.
## 4. Схемы
Поля без `?` есть всегда; `null` указан явно. `money` — строка с двумя знаками, `share` — строка с двумя знаками.
**`GoodwillTableOut`**
```
version_id uuid
goodwill_enabled bool
editable bool
read_only_reason null | "VERSION_NOT_EDITABLE" | "LOCK_REQUIRED" | "LOCKED_BY_OTHER"
rows[] GoodwillRowOut
groups[] GoodwillGroupOut итог «Всего» по каждой ГО
totals GoodwillTotalsOut «Итого по листу»
```
**`GoodwillRowOut`**
```
head_legal_entity Ref {id, name}
subsidiary_legal_entity Ref
ownership_share share | null
financial_investment_amount money | null null у «Нет вложений»
charter_capital_amount money | null null — нет однозначного 1310 или «Нет вложений»
additional_capital_amount money | null справочно, в формулу не входит
participation_amount money | null только у действующей отмеченной строки
goodwill_amount money | null может быть < 0 и = 0
include_in_kfo bool
comment string | null
has_investments bool
warnings[] {code, message}
updated_by Ref | null
updated_at datetime | null
```
**`GoodwillGroupOut`** `{head_legal_entity: Ref, totals: GoodwillTotalsOut}`.
**`GoodwillTotalsOut`** — по действующим строкам (без «Нет вложений»):
```
financial_investment_amount money все действующие строки, независимо от галочки
charter_capital_amount money все действующие строки (УК одной ДО у нескольких ГО входит несколько раз)
participation_amount money только отмеченные
goodwill_amount money только отмеченные, отрицательные и нулевые тоже
goodwill_in_kfo_amount money положительные G отмеченных строк с долей и комментарием; 0.00 при goodwill_enabled = false
```
**`GoodwillBatchIn`** `{rows: GoodwillRowKeyIn[]}` (минимум одна строка).
**`GoodwillRowKeyIn`** `{head_legal_entity_id: uuid, subsidiary_legal_entity_id: uuid, ownership_share: string | null,
include_in_kfo: bool, comment: string | null (≤ 1000)}`.
**`GoodwillSettingsIn`** `{goodwill_enabled: bool}`.
## 5. Граница этапа и открытые вопросы
- **КФО.** Применение гудвилла в КФО (1170 ГО − ФВ, 1310 ДО − P, строка `GOODWILL` + G), исключение обычного ББ-2 по
паре, пометка результата КФО устаревшим и фиксация при VERIFIED — задача AURORA-1546, этим API не делается.
- **OQ-10** — 1350 в базу участия не входит (DOC-259025 §6: «Для УК используется 1310»), колонка — справочно.
- **OQ-37** — служебный код `GOODWILL` для строки КФО (DOC-259025 §7).
## 6. Что изменилось против черновика 30.10
- ГО — по фактическим вложениям 58.01, а не по `is_head_entity`; строк «все ДО под каждой ГО» больше нет.
- Новые поля строки: `has_investments`, `warnings`; УК, 1350, P, G и ФВ — nullable.
- Новые поля ответа: `groups[]`, `totals.charter_capital_amount`.
- P и G — только у отмеченных строк; итоги P и G — по отмеченным.
- Доля — два знака, а не четыре.
- Построчный `PUT …/goodwill/{head}/{sub}` заменён пакетным `PUT …/goodwill` (одна транзакция, ответ — вся таблица).
- У экспорта — параметр `sign`.
## 7. Пример
`GET /api/v1/consolidations/versions/7b…/goodwill` (сокращено):
```json
{
"version_id": "7b1c…",
"goodwill_enabled": true,
"editable": true,
"read_only_reason": null,
"rows": [
{
"head_legal_entity": {"id": "a…", "name": "Альфа"},
"subsidiary_legal_entity": {"id": "b…", "name": "Бета"},
"ownership_share": "80.00",
"financial_investment_amount": "180000.00",
"charter_capital_amount": "100000.00",
"additional_capital_amount": "0.00",
"participation_amount": "80000.00",
"goodwill_amount": "100000.00",
"include_in_kfo": true,
"comment": "Доля подтверждена уставом",
"has_investments": true,
"warnings": [],
"updated_by": {"id": "u…", "name": "Иванов И. И."},
"updated_at": "2026-11-06T12:00:00Z"
},
{
"head_legal_entity": {"id": "a…", "name": "Альфа"},
"subsidiary_legal_entity": {"id": "c…", "name": "Гамма"},
"ownership_share": "80.00",
"financial_investment_amount": "50000.00",
"charter_capital_amount": "100000.00",
"additional_capital_amount": null,
"participation_amount": "80000.00",
"goodwill_amount": "-30000.00",
"include_in_kfo": true,
"comment": "Доля подтверждена уставом",
"has_investments": true,
"warnings": [],
"updated_by": null,
"updated_at": null
}
],
"groups": [
{
"head_legal_entity": {"id": "a…", "name": "Альфа"},
"totals": {
"financial_investment_amount": "230000.00",
"charter_capital_amount": "200000.00",
"participation_amount": "160000.00",
"goodwill_amount": "70000.00",
"goodwill_in_kfo_amount": "100000.00"
}
}
],
"totals": {
"financial_investment_amount": "230000.00",
"charter_capital_amount": "200000.00",
"participation_amount": "160000.00",
"goodwill_amount": "70000.00",
"goodwill_in_kfo_amount": "100000.00"
}
}
```
`PUT /api/v1/consolidations/versions/7b…/goodwill`:
```json
{"rows": [
{"head_legal_entity_id": "a…", "subsidiary_legal_entity_id": "b…", "ownership_share": "80", "include_in_kfo": true, "comment": "Доля подтверждена уставом"},
{"head_legal_entity_id": "a…", "subsidiary_legal_entity_id": "d…", "ownership_share": null, "include_in_kfo": false, "comment": null}
]}
```
Ошибка строки:
```json
{"detail": {"code": "COMMENT_REQUIRED", "message": "Укажите комментарий",
"head_legal_entity_id": "a…", "subsidiary_legal_entity_id": "b…"}}
```