Ссылки README и CLAUDE.md переведены на архив. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
17 KiB
consolidations-api Specification
Purpose
Консолидация ГК за период: создание, дерево версий, карточка версии, периметр, блокировка «взять в работу», статусы
Среза 4 и чек-лист комплекта документов (§9.1, §10 05-domain-rules.md; DBML consolidation*, required_document_rule;
макет 34, 35, 36, 37, 42). Префикс всех маршрутов — /api/v1.
Requirements
Requirement: Создание консолидации
POST /consolidations (CONSOLIDATION_EDIT, policy по ГК) с телом {company_group_id, related_group_id, year, quarter}
SHALL в одной транзакции создать consolidation (reporting_date: Q1 → 01.04.year, Q2 → 01.07.year, Q3 → 01.10.year,
Q4 → 01.01.year+1; ASSUMPTION(OQ-40)), consolidation_version №1 (INITIATED, scenario = CREATE, approach = BASIC,
goodwill_enabled = false, initiator_id и initiator_office_id из пользователя, is_in_work = true),
consolidation_member из текущих членов ГСК (is_head_entity = false), первую запись consolidation_status_history
(from_status NULL → INITIATED), подхват подтверждённых документов (см. documents-api «Подхват документов») и
audit_event(CONSOLIDATION_CREATE); ответ 201 с карточкой версии. ГСК SHALL принадлежать ГК и иметь kind = gsk,
иначе 422 RELATED_GROUP_MISMATCH; членов меньше двух → 422 PERIMETER_TOO_SMALL; пара (ГК, год, квартал) уже есть
→ 409 CONSOLIDATION_EXISTS (в том числе при гонке двух запросов — UNIQUE (company_group_id, year, quarter));
year вне 2000..2100 → 422. Блокировка на создателя MUST NOT ставиться. related_group_id в consolidation SHALL
оставаться NULL (фиксируется при верификации, Срез 7).
Scenario: Консолидация РБ за Q3 2024
- WHEN
ka_rfСанкт-Петербургского РФ создаёт консолидацию ГК «Русский бисквит», ГСК из двух ЮЛ, 2024, Q3 - THEN 201; версия №1
INITIATED,reporting_date = 2024-10-01, дваconsolidation_member, история с одной записью,audit_event(CONSOLIDATION_CREATE)сconsolidation_version_id; активной блокировки нет
Scenario: ГСК из одного ЮЛ
- WHEN в ГСК один текущий член
- THEN 422
PERIMETER_TOO_SMALL;consolidationи версия не созданы
Scenario: Повтор периода
- WHEN консолидация ГК за 2024 Q3 уже есть, создаётся ещё одна
- THEN 409
CONSOLIDATION_EXISTS
Requirement: Список консолидаций ГК и карточка версии
GET /company-groups/{id}/consolidations (CONSOLIDATION_VIEW, policy) SHALL возвращать периоды ГК (consolidation_id,
year, quarter, reporting_date) по убыванию года и квартала, в каждом — версии по убыванию version_no
(id, version_no, status, scenario, approach, lock — активная блокировка или null, created_at,
members_count); параметр in_work_only=true SHALL оставлять только версии со статусом не из
{VERIFIED, CANCELLED, ARCHIVED, REJECTED}. GET /consolidations/versions/{id} SHALL отдавать карточку: поля
версии и консолидации, company_group {id, name}, инициатора и его офис, lock, members[] (ЮЛ: id, name, inn,
is_head_entity, checklist[] — ячейки по правилам подхода, см. «Чек-лист комплекта»), history[] (from_status,
to_status, changed_by, changed_at, comment). Консолидация ГК вне видимости и несуществующий идентификатор —
404 NOT_FOUND с сообщением CONSOLIDATION_NOT_FOUND / VERSION_NOT_FOUND и одинаковым телом (так же во всех
спецификациях среза: идентификаторы …_NOT_FOUND — текст сообщения при фиксированном коде NOT_FOUND).
Scenario: Дерево версий
- WHEN у ГК консолидации 2024 Q1 (версии 1 и 2) и 2024 Q3 (версия 1), запрошен список
- THEN первым период 2024 Q3, затем 2024 Q1 с версиями
[2, 1];members_countу каждой версии
Scenario: Чужой филиал
- WHEN
ka_rfдругого филиала запрашивает карточку версии - THEN 404
NOT_FOUND(сообщениеVERSION_NOT_FOUND), тело совпадает с ответом на случайныйuuid
Requirement: Периметр версии
PUT /consolidations/versions/{id}/members (CONSOLIDATION_EDIT, policy, статус «в работе», своя активная блокировка)
с телом {legal_entity_ids: [...]} SHALL заменить периметр версии: все ЮЛ SHALL принадлежать ГК консолидации
(legal_entity.company_group_id), иначе 422 ENTITY_NOT_IN_GROUP с перечнем; меньше двух → 422
PERIMETER_TOO_SMALL; дубли в списке схлопываются. Для добавленных ЮЛ SHALL выполняться подхват подтверждённых
документов, для удалённых — отвязка их consolidation_document; audit_event(MEMBERSHIP_CHANGE, section = CONSOLIDATION, object = consolidation_version) с before_value/after_value — полные списки ЮЛ. related_group_member
MUST NOT изменяться. Ответ — карточка версии.
Scenario: Добавление ЮЛ с подтверждёнными документами
- WHEN в периметр версии 2024 Q3 добавлено ЮЛ ГК, у которого подтверждена ОСВ 60 за 2024 Q3 (нарастающая)
- THEN 200; в
membersтри ЮЛ; у нового ЮЛ ячейка ОСВ 60CONFIRMED;consolidation_documentна этот документ есть
Scenario: ЮЛ другой ГК
- WHEN в список попало ЮЛ,
company_group_idкоторого не равен ГК консолидации - THEN 422
ENTITY_NOT_IN_GROUP, периметр не изменился
Scenario: Без блокировки
- WHEN у версии нет активной блокировки, пользователь с
CONSOLIDATION_EDITменяет периметр - THEN 409
LOCK_REQUIRED
Requirement: Взять в работу, перехват, снятие
POST /consolidations/versions/{id}/lock (CONSOLIDATION_EDIT, policy; версия в любом статусе, кроме CANCELLED —
409 VERSION_NOT_EDITABLE) SHALL: при отсутствии активной блокировки создать consolidation_lock(locked_by, locked_at)
и audit_event(CONSOLIDATION_LOCK) → 201; при своей активной — вернуть её без изменений → 200; при чужой без
takeover_reason → 409 LOCKED_BY_OTHER с locked_by, locked_by_name, locked_at в detail; при чужой с
непустым takeover_reason (≤ 1000 символов) — закрыть прежнюю (released_at = now), создать новую с
taken_over_from и takeover_reason, audit_event(CONSOLIDATION_LOCK_TAKEOVER, before = прежний владелец, after = новый, comment = причина) → 201. Два одновременных POST без активной блокировки → у одного 201, у другого 409
LOCKED_BY_OTHER (частичный UNIQUE). DELETE /consolidations/versions/{id}/lock SHALL снять свою активную
блокировку (released_at = now, audit_event(CONSOLIDATION_LOCK, after_value.released = true)) → 204; чужую — 409
NOT_LOCK_OWNER; без активной — 204. Блокировка MUST NOT истекать по времени (OQ-119).
Scenario: Перехват с причиной
- WHEN версию держит
ka_rfА,ka_rfБ того же филиала вызываетPOST …/lock {takeover_reason: "Отпуск"} - THEN 201; у Б активная блокировка с
taken_over_from = A; у Аreleased_atзаполнен;audit_event(CONSOLIDATION_LOCK_TAKEOVER)сcomment = "Отпуск"
Scenario: Перехват без причины
- WHEN Б вызывает
POST …/lockбез тела при активной блокировке А - THEN 409
LOCKED_BY_OTHER,detail.locked_by = A
Scenario: Чужое снятие
- WHEN Б вызывает
DELETE …/lockпри активной блокировке А - THEN 409
NOT_LOCK_OWNER, блокировка А активна
Requirement: Статусы Среза 4
Версия SHALL менять статус только через сервис статусов, который пишет consolidation_status_history(from, to, changed_by, changed_at, comment) и audit_event(CONSOLIDATION_STATUS_CHANGE, before = from, after = to, comment).
Автоматические переходы: загрузка файла с consolidation_version_id (см. primary-files) — INITIATED → DOCUMENTS_PROCESSING; маппинг документа NEEDS_CHECK, подходящего версии (см. osv-result-mapping) —
DOCUMENTS_PROCESSING → DATA_CORRECTION; подтверждение документа, привязанного к версии (см. documents-api) —
INITIATED или DOCUMENTS_PROCESSING → DATA_CORRECTION. Ручной POST /consolidations/versions/{id}/status
(CONSOLIDATION_EDIT, policy, своя блокировка) с телом {to_status, comment} SHALL допускать только: CANCELLED из
любого статуса «в работе» (comment обязателен, иначе 422 COMMENT_REQUIRED; cancelled_reason = comment,
is_in_work = false, активная блокировка снимается) и INITIATED из DOCUMENTS_PROCESSING / DATA_CORRECTION
(comment необязателен; consolidation_document сохраняются). Любой другой to_status → 409 TRANSITION_NOT_ALLOWED
с текущим и запрошенным статусами. Повтор того же статуса → 409 TRANSITION_NOT_ALLOWED. Статус «в работе» =
{INITIATED, DOCUMENTS_PROCESSING, DATA_CORRECTION}.
Scenario: Аннулирование
- WHEN владелец блокировки переводит версию
DATA_CORRECTIONвCANCELLEDс комментарием - THEN 200;
status = CANCELLED,cancelled_reason= комментарий,is_in_work = false, блокировка снята; запись историиDATA_CORRECTION → CANCELLEDс комментарием;audit_event(CONSOLIDATION_STATUS_CHANGE)
Scenario: Переход Среза 7
- WHEN запрошен
to_status = CONSOLIDATION_RF - THEN 409
TRANSITION_NOT_ALLOWED, статус не изменился
Scenario: Аннулирование без комментария
- WHEN
to_status = CANCELLED,commentпуст - THEN 422
COMMENT_REQUIRED
Requirement: Чек-лист комплекта
GET /consolidations/versions/{id}/checklist (CONSOLIDATION_VIEW, policy) SHALL вернуть rules[] (правила
required_document_rule подхода версии: document_type, account, is_mandatory; порядок — BALANCE, INCOME,
затем ОСВ по коду счёта) и rows[] по ЮЛ периметра (по имени) с cells[] в порядке rules. Ячейка {status, missing_quarters, document_ids}: CONFIRMED — пара (ЮЛ, правило) полностью покрыта привязанными документами (ОСВ: по
правилу выбора источников consolidated-osv; ББ/ОФР: документ за квартал версии); NEEDS_CHECK / RECOGNIZED —
есть неподтверждённый документ ЮЛ того же типа, счёта и года, подходящий версии (приоритет NEEDS_CHECK);
PROCESSING — у ЮЛ есть файл UPLOADED или PROCESSING; ERROR — последний по uploaded_at файл ЮЛ в ERROR;
иначе MISSING. missing_quarters SHALL перечислять кварталы без квартальной ОСВ при частичном покрытии. Правило ОСВ
SHALL закрываться только документом с osv_account_code, равным account правила (OQ-04). Те же ячейки SHALL
отдаваться в карточке версии у каждого члена. С ?format=csv SHALL отдаваться тот же чек-лист файлом text/csv; charset=utf-8 с BOM и разделителем ;: заголовок «Организация;ИНН;<правило>…» (правила — «Бух. баланс», «ОФР»,
«ОСВ <счёт>»), ячейки — подписи макета: CONFIRMED «Загружен», PROCESSING «Обработка», NEEDS_CHECK/RECOGNIZED
«Требуется проверка», ERROR «Ошибка загрузки», MISSING «Отсутствует» (с « (нет: Q1, Q2)» при missing_quarters);
Content-Disposition — attachment с именем checklist-<ГК>-<год>-<квартал>.csv в RFC 5987.
Scenario: Пробел Q2
- WHEN у ЮЛ привязаны квартальные ОСВ 60 за Q1 и Q3 2024, версия за 2024 Q3
- THEN ячейка ОСВ 60:
status = MISSING,missing_quarters = ["Q2"],document_ids— два документа
Scenario: Субсчёт не закрывает правило
- WHEN у ЮЛ подтверждена только ОСВ 58.03 за квартал версии
- THEN ячейка ОСВ 58 —
MISSING
Scenario: Файл в обработке
- WHEN у ЮЛ загружен файл, задание ещё не завершено, других документов нет
- THEN все ячейки ЮЛ —
PROCESSING
Scenario: Чек-лист в CSV
- WHEN запрошен чек-лист версии с
format=csv, у ЮЛ подтверждён ББ, ОСВ 60 отсутствует за Q2 - THEN файл начинается с BOM, первая строка — «Организация;ИНН;Бух. баланс;ОФР;ОСВ 60…», в строке ЮЛ — «Загружен» и «Отсутствует (нет: Q2)»
Requirement: Границы входных данных
Входы консолидаций SHALL иметь верхние границы: legal_entity_ids тела PUT …/members — не больше 500 идентификаторов,
comment тела POST …/status — не больше 1000 символов, q фильтров GET …/osv и экспорта — не больше 500 символов.
Превышение SHALL давать 422 со списком ошибок валидации (detail[].loc указывает поле) до обращения к БД. Версия
консолидации SHALL проверяться на видимость до блокировки FOR UPDATE: невидимая версия отвечает 404 сразу, не ожидая
транзакцию, которая держит её строку.
Scenario: Периметр длиннее предела
- WHEN в
PUT …/membersпередано 501 ЮЛ - THEN 422,
detail[0].loc = ["body", "legal_entity_ids"], периметр не изменился
Scenario: Невидимая версия при занятой строке
- WHEN версию чужого филиала держит
FOR UPDATEдругая транзакция, пользователь без видимости вызываетPOST …/lock - THEN 404
VERSION_NOT_FOUNDбез ожидания