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

19 KiB
Raw Blame History

Контракт 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 (сокращено):

{
  "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:

{"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}
]}

Ошибка строки:

{"detail": {"code": "COMMENT_REQUIRED", "message": "Укажите комментарий",
            "head_legal_entity_id": "a…", "subsidiary_legal_entity_id": "b…"}}