Контракт dev/api-contract-goodwill.md/.openapi.yaml — по факту arm-core #222 после доводки: пары по вложениям 58.01, «Нет вложений», nullable суммы, warnings, groups и УК в итогах, пакетный PUT …/goodwill, доля до двух знаков, sign в экспорте; OpenAPI выгружен из app.openapi(). §8 «Гудвилл» — выжимка DOC-259025 (03.11); OQ-10/OQ-37 — что принято в реализации. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
261 lines
19 KiB
Markdown
261 lines
19 KiB
Markdown
# Контракт 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…"}}
|
||
```
|