# Контракт 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 `; - чтение и экспорт — право `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…"}} ```