35 KiB
Raw Blame History

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, legal_entity_ids?} 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), у которого after_value.members — отсортированный список ЮЛ периметра; ответ 201 с карточкой версии. При создании версии система SHALL автоматически записать в consolidation_version.elimination_rules_version номер самой актуальной версии правил: максимальный version_no среди правил, действующих на московскую календарную дату создания версии (valid_from <= дата и valid_to IS NULL либо valid_to >= дата; OQ-174, docs PR #146). Пользователь не выбирает версию правил. Отчётные год, квартал и reporting_date не влияют на определение номера. Если на дату создания нет действующих правил, поле остаётся NULL. Периметр SHALL быть: без legal_entity_ids (поле отсутствует или null) — текущие члены ГСК; с legal_entity_ids — ровно эти ЮЛ после схлопывания дублей (порядок первого вхождения), каждое SHALL принадлежать ГК консолидации (legal_entity.company_group_id), иначе 422 ENTITY_NOT_IN_GROUP с перечнем legal_entity_ids чужих в порядке запроса; состав ГСК при явном списке не проверяется и не меняется. ГСК SHALL быть обязательной, принадлежать ГК и иметь kind = gsk, иначе 422 RELATED_GROUP_MISMATCH. Периметр меньше двух ЮЛ (членов ГСК без списка либо выбранных ЮЛ после схлопывания) → 422 PERIMETER_TOO_SMALL; пара (ГК, год, квартал) уже есть → 409 CONSOLIDATION_EXISTS (в том числе при гонке двух запросов — UNIQUE (company_group_id, year, quarter)); year вне 2000..2100 → 422. Порядок проверок: ГК видима (404) → ГСК видима (404) и соответствует ГК (422) → периметр (422) → период свободен (409). Блокировка на создателя MUST NOT ставиться; MEMBERSHIP_CHANGE при создании MUST NOT писаться. related_group_id в consolidation SHALL оставаться NULL (фиксируется при верификации, Срез 7); related_group_member MUST NOT изменяться.

Scenario: Консолидация РБ за Q3 2024

  • WHEN ka_rf Санкт-Петербургского РФ создаёт консолидацию ГК «Русский бисквит», ГСК из двух ЮЛ, 2024, Q3, без legal_entity_ids
  • THEN 201; версия №1 INITIATED, reporting_date = 2024-10-01, два consolidation_member, история с одной записью, audit_event(CONSOLIDATION_CREATE) с consolidation_version_id; активной блокировки нет

Scenario: Правила действуют на дату создания версии

  • WHEN в день создания консолидации за 2024 год действует версия правил 2, а версия 1 действовала в отчётном периоде
  • THEN consolidation_version.elimination_rules_version = 2; номер не меняется при последующей публикации правил

Scenario: Выбранный периметр шире ГСК

  • WHEN в ГК три ЮЛ, в ГСК два из них, создаётся консолидация с legal_entity_ids из всех трёх ЮЛ ГК
  • THEN 201; в members три ЮЛ; after_value.members события CONSOLIDATION_CREATE — три ЮЛ; состав ГСК не изменился

Scenario: ГСК из одного ЮЛ, второе отмечено вручную

  • WHEN в ГСК один текущий член, в legal_entity_ids он и ещё одно ЮЛ той же ГК
  • THEN 201; в members два ЮЛ

Scenario: ГСК из одного ЮЛ

  • WHEN в ГСК один текущий член, legal_entity_ids не передан
  • THEN 422 PERIMETER_TOO_SMALL; consolidation и версия не созданы

Scenario: ЮЛ другой ГК в списке

  • WHEN в legal_entity_ids попало ЮЛ, company_group_id которого не равен ГК консолидации
  • THEN 422 ENTITY_NOT_IN_GROUP с этим ЮЛ в legal_entity_ids; consolidation не создана

Scenario: Дубли схлопываются до одного ЮЛ

  • WHEN legal_entity_ids — один и тот же идентификатор дважды
  • THEN 422 PERIMETER_TOO_SMALL

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[], history[] (from_status, to_status, changed_by, changed_at, comment). Член периметра SHALL содержать id, name, inn, entity_type (ЮЛ/ИП, из legal_entity), is_head_entity, checklist[] (ячейки по правилам подхода, см. «Чек-лист комплекта») и osv_accounts[] — уникальные osv_account_code документов ЮЛ, у которых document_type = OSV, period_year равен году версии, period_quarter не позже квартала версии, статус документа не ERROR, файл не в SUPERSEDED и не в ERROR; документы без квартала или без кода счёта не учитываются; статус подтверждения и привязка к версии не важны; порядок — натуральная сортировка кода счёта (как столбцы чек-листа: 01 < 51 < 58.03 < 60). Консолидация ГК вне видимости и несуществующий идентификатор — 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

Scenario: Счета ОСВ участника

  • WHEN версия за 2024 Q3; у ЮЛ есть ОСВ 60 за Q1 2024 (RECOGNIZED), ОСВ 51 за Q3 2024 (CONFIRMED), ОСВ 58.03 за Q3 2024 (NEEDS_CHECK), ОСВ 62 за Q4 2024, ОСВ 01 за 2023, ОСВ 76 в заменённом (SUPERSEDED) файле и ОСВ 90 в документе ERROR
  • THEN osv_accounts = ["51", "58.03", "60"]

Scenario: Тип ЮЛ

  • WHEN в периметре индивидуальный предприниматель
  • THEN у него entity_type = "ИП", у остальных — "ЮЛ"

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 три ЮЛ; у нового ЮЛ ячейка ОСВ 60 CONFIRMED; 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). Автоматические переходы: загрузка в консолидацию (POST /consolidations/{id}/legal-entities/{le}/files, см. 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. Ячейка {document_type, account, status, missing_quarters, document_ids}: document_type и account — правила ячейки (ячейка самоописывающая, фронт не опирается на позицию); 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). Те же ячейки (с document_type и account, в том же порядке) 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: document_type = OSV, account = "60", status = MISSING, missing_quarters = ["Q2"], document_ids — два документа

Scenario: Субсчёт не закрывает правило

  • WHEN у ЮЛ подтверждена только ОСВ 58.03 за квартал версии
  • THEN ячейка ОСВ 58 — MISSING

Scenario: Файл в обработке

  • WHEN у ЮЛ загружен файл, задание ещё не завершено, других документов нет
  • THEN все ячейки ЮЛ — PROCESSING

Scenario: Ячейки карточки подписаны

  • WHEN запрошена карточка версии подхода BASIC
  • THEN у каждого члена checklist[i].document_type и checklist[i].account равны rules[i] чек-листа той же версии; ячейка ББ — document_type = BALANCE, account = null

Scenario: Чек-лист в CSV

  • WHEN запрошен чек-лист версии с format=csv, у ЮЛ подтверждён ББ, ОСВ 60 отсутствует за Q2
  • THEN файл начинается с BOM, первая строка — «Организация;ИНН;Бух. баланс;ОФР;ОСВ 60…», в строке ЮЛ — «Загружен» и «Отсутствует (нет: Q2)»

Requirement: Границы входных данных

Входы консолидаций SHALL иметь верхние границы: legal_entity_ids тела PUT …/members и тела POST /consolidations — не больше 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 в POST /consolidations передано 501 ЮЛ в legal_entity_ids
  • THEN 422, detail[0].loc = ["body", "legal_entity_ids"], консолидация не создана

Scenario: Невидимая версия при занятой строке

  • WHEN версию чужого филиала держит FOR UPDATE другая транзакция, пользователь без видимости вызывает POST …/lock
  • THEN 404 VERSION_NOT_FOUND без ожидания

Requirement: Правила элиминации версии

GET /api/v1/consolidations/versions/{version_id}/elimination-rules (CONSOLIDATION_VIEW, policy по ГК) SHALL возвращать объект {version_no, is_version_fixed, rules}. Если у версии задан consolidation_version.elimination_rules_version, version_no SHALL равняться ему, is_version_fixed = true, а rules — правилами этой версии. Если он не задан, is_version_fixed = false, а rules — открытые правила (valid_to IS NULL) только с максимальным version_no; ответ SHALL содержать этот фактический version_no. При отсутствии открытых правил version_no = null, rules = []. Каждое правило SHALL содержать id, rule_no, version_no, statement_type, side_1 (D/K), measure (closing/turnover), company_1, company_2; каждая сторона SHALL содержать accounts: UUID[] и excluded: UUID[] до раскрытия иерархии счетов. Невидимая и несуществующая версия SHALL отвечать одинаковым 404 NOT_FOUND.

Scenario: Правило с исключением

  • WHEN у правила первая сторона включает счёт 76 и исключает 76.07.5
  • THEN ID 76 находится в company_1.accounts, ID 76.07.5 — в company_1.excluded

Scenario: Версия правил зафиксирована

  • WHEN у версии консолидации elimination_rules_version = 1
  • THEN ответ содержит is_version_fixed = true, version_no = 1 и только правила с version_no = 1

Scenario: Версия правил не зафиксирована

  • WHEN у версии консолидации elimination_rules_version пуст
  • THEN ответ содержит is_version_fixed = false, фактический version_no и открытые правила только этой версии

Scenario: Открыты две версии правил

  • WHEN открыты правила с version_no = 1 и version_no = 2, в том числе с одинаковым rule_no
  • THEN ответ содержит только правила версии 2 и version_no = 2

Requirement: Сопоставленные строки сводной ОСВ по правилу и паре компаний

GET /api/v1/consolidations/versions/{version_id}/elimination-pairs SHALL требовать CONSOLIDATION_VIEW и проверять видимость версии по ГК. Запрос SHALL принимать обязательные query-параметры rule_id, company_a_id, company_b_id. Компании SHALL быть разными участниками периметра версии, а правило — пресетом ББ её версии правил. Для этой тройки SHALL возвращаться только её результат; строки других правил и направленных пар компаний в расчёт не входят. Отбор счетов, раскрытие счетов-предков и их исключений SHALL совпадать с расчётом шахматки PRESET. Ответ SHALL содержать version_no, rule_id, rule_no, company_a_id, company_b_id, matched[] (точные группы), with_discrepancy[] (группы с допустимым ненулевым расхождением) и unpaired[] (строки без группы), без полного перечня допустимых пар счетов. На уровне тройки ответ SHALL содержать amount_a, amount_b (суммы всех выбранных строк, включая оставшиеся без пары), elimination_amount (абсолютное расхождение), deviation_percent (от меньшей суммы, null при нуле), eliminated_amount (сумма фактической элиминации) и exceeds_threshold. При расхождении не более 5 % eliminated_amount SHALL равняться min(amount_a, amount_b); при превышении порога, нулевой стороне или elimination_mode = manual SHALL равняться нулю. Упаковка строк SHALL не менять этот итог. Каждая сопоставленная группа SHALL содержать rule_id, company_a_id, company_b_id, amount_a, amount_b, elimination_amount, deviation_percent и исходные osv_entry_ids_a/b. Суммы amount_a/b SHALL рассчитываться по ОСВ; elimination_amount SHALL равняться abs(amount_a - amount_b). Невидимая и несуществующая версия или отсутствующее правило SHALL отвечать 404 NOT_FOUND; недопустимая пара компаний — 422 INVALID_COMPANY_PAIR. Пары и группы не сохраняются.

Строка сводной ОСВ неделима, даже если содержит несколько entry_ids. Внутри выбранной тройки строки с распознанным встречным ЮЛ и допустимыми счетами группируются как 1:1, 1:N или N:1 при N ≤ 5; N:M не допускается. Комбинации строятся из всех строк выбранных счетов соответствующей стороны правила, без дополнительного требования совпадения конкретных счетов внутри группы. Сравниваются модули остатков по соответствующей стороне. Нулевые строки остаются в unpaired с причиной ZERO_AMOUNT. Сначала выбираются группы с наименьшим общим числом строк: 1:1, затем 1:2 и 2:1, и так до 1:5 и 5:1. Внутри размера сначала точные, затем с расхождением не более 5 % относительно меньшей из двух сумм, по возрастанию расхождения. Строка входит не более чем в одну группу данного правила. Если группы одного размера с одинаковым расхождением пересекаются, все строки конкурирующих групп исключаются из автоматического поиска и возвращаются в unpaired с причиной AMBIGUOUS для ручного разбора. Остальные строки без группы возвращаются в unpaired с причиной NO_MATCH независимо от того, отсутствуют ли встречные суммы, превышен ли порог или подходящие строки уже заняты другой группой. Упаковка не меняет сумму элиминации.

Scenario: Исключённый счёт не входит в пары

  • WHEN правило включает родительский счёт 76 и исключает 76.07.5
  • THEN строки счёта 76.07.5 и его потомков не входят в сопоставленные группы и unpaired

Scenario: Две строки свода упакованы с одной встречной

  • WHEN по одному правилу есть дебетовые строки 40 и 60 и встречная кредитовая строка 100
  • THEN matched[] содержит пару с суммами 100 и 100, нулевым расхождением и исходными osv_entry_ids_a/b

Scenario: Пара с допустимым расхождением

  • WHEN группа имеет суммы 103 и 100 при пороге 5 %
  • THEN она входит в with_discrepancy[], а не в matched[]

Scenario: Другая пара компаний не смешивается с выбранной

  • WHEN по одному правилу есть встречные строки для A→Б и A→В, а запрошены company_a_id=A, company_b_id=Б
  • THEN ответ содержит только строки A→Б и Б→A

Requirement: Результирующая таблица ВГО

GET /api/v1/consolidations/versions/{version_id}/intercompany-matrix/result-table (CONSOLIDATION_VIEW, policy) SHALL возвращать {companies[], articles[]}. Компании — все члены периметра по имени; статья содержит code, name, amounts[] {company_id, amount} и group_total. Пять фиксированных статей §6: дебиторская и кредиторская задолженность, финансовые вложения, долгосрочные и краткосрочные займы. Сумма компании SHALL считаться по всем остальным компаниям периметра из конечного сальдо нужной стороны и выбранных счетов статьи; group_total — сумма по компаниям. Отсутствие любого необходимого кода счёта SHALL давать 422 RESULT_ACCOUNTS_MISSING с перечнем кодов.

Scenario: Компания без ОСВ

  • WHEN в периметре есть компания без подходящих строк ОСВ
  • THEN она есть в companies, её сумма по каждой статье — строка "0"

Scenario: Нет обязательного счёта

  • WHEN в плане счетов отсутствует счёт 58.03
  • THEN ответ 422 RESULT_ACCOUNTS_MISSING, сообщение содержит 58.03

Requirement: XLSX результирующей таблицы ВГО

GET /api/v1/consolidations/versions/{version_id}/intercompany-matrix/result-table/export (CONSOLIDATION_VIEW, policy) SHALL возвращать XLSX с теми же статьями, компаниями и итогами, что GET …/intercompany-matrix/result-table, из того же расчёта сводной ОСВ версии. Книга — лист «Результирующая таблица» с шапкой (ГК, период, версия, единицы «руб.») и таблицей «Статья ВГО» × компании периметра + колонка «Итого по группе»; суммы — числа с двумя знаками. Имя файла — intercompany-result-table_<ГК>_<год>-<квартал>_v<n>.xlsx. Ошибки — как у JSON-маршрута: невидимая или несуществующая версия — 404, отсутствие необходимого кода счёта — 422 RESULT_ACCOUNTS_MISSING.

Scenario: Совпадение с JSON

  • WHEN пользователь выгружает результирующую таблицу ГК «Русский бисквит» за 2024 Q1, версия 1
  • THEN имя содержит Русский бисквит_2024-Q1_v1.xlsx, порядок компаний, названия статей, суммы по компаниям и «Итого по группе» совпадают с ответом GET …/result-table той же версии

Scenario: Нет счёта статьи

  • WHEN в плане счетов нет кода 58.03
  • THEN выгрузка отвечает 422 RESULT_ACCOUNTS_MISSING с кодом в сообщении, файл не формируется

Scenario: Версия вне видимости

  • WHEN пользователь филиала выгружает таблицу версии чужой ГК
  • THEN ответ 404 с тем же телом, что у несуществующей версии