37 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, 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 быть: без
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 изменяться.
В consolidation_version.elimination_rules_version SHALL записываться номер версии правил элиминации, действующей на
московскую календарную дату создания: максимальный version_no среди правил с valid_from <= дата и valid_to IS NULL
либо valid_to >= дата (OQ-174; DBML v5.23, поле NOT NULL). Пользователь версию правил не выбирает; год, квартал и
reporting_date на номер не влияют; позже номер не меняется. Нет правил, действующих на дату, → 409
ELIMINATION_RULES_MISSING, консолидация не создаётся.
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 в ГК три ЮЛ, в ГСК два из них, создаётся консолидация с
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
Scenario: Правила действуют на дату создания версии
- WHEN в день создания консолидации за 2024 год действует версия правил 2, а версия 1 действовала в отчётном периоде
- THEN
consolidation_version.elimination_rules_version = 2; номер не меняется при последующей публикации правил
Scenario: Нет действующих правил
- WHEN у всех правил
valid_fromпозже даты создания - THEN 409
ELIMINATION_RULES_MISSING;consolidationи версия не созданы
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три ЮЛ; у нового ЮЛ ячейка ОСВ 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).
Автоматические переходы: загрузка в консолидацию (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}/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 с тем же телом, что у несуществующей версии
Requirement: Правила элиминации зафиксированной версии
GET /api/v1/consolidations/versions/{version_id}/elimination-rules (CONSOLIDATION_VIEW, policy по ГК)
SHALL возвращать объект {version_no, is_version_fixed, rules}: version_no — consolidation_version.elimination_rules_version
(с DBML v5.23 задан у каждой версии), is_version_fixed = true (поле оставлено для совместимости клиента), rules —
правила этой версии; правила других версий, в том числе более новых, MUST NOT попадать в ответ. Каждое правило 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 = 1открыта версия правил 2 - THEN ответ по-прежнему содержит
version_no = 1и правила версии 1; пресет правила версии 2 для этой версии — 404
Requirement: Пары ВГО тройки
GET /api/v1/consolidations/versions/{version_id}/elimination-pairs?rule_id=&company_a_id=&company_b_id= SHALL требовать
CONSOLIDATION_VIEW и видимость версии по ГК; три параметра обязательны. Компании SHALL быть разными участниками
периметра версии (иначе 422 INVALID_COMPANY_PAIR), правило — правилом ББ версии правил версии консолидации с
measure = closing (иначе 404 NOT_FOUND или 422 INVALID_RULE). Отбор счетов, раскрытие счетов-предков и исключения
SHALL совпадать с шахматкой PRESET. Расчёт — по 05-domain-rules.md §5, на лету, без сохранения.
Итог тройки: amount_a — сумма строк A с контрагентом B по счетам стороны 1, amount_b — строк B с контрагентом A по
счетам стороны 2, со знаком (как в шахматке); deviation_amount = |amount_a − amount_b|; deviation_percent — к
min(amount_a, amount_b), null, если он ≤ 0; exceeds_threshold — процент > 5 % или меньшая сторона ≤ 0 при ненулевом
расхождении; eliminated_amount = min(amount_a, amount_b) при расхождении ≤ 5 %, иначе, при elimination_mode = manual и
при меньшей стороне ≤ 0 — 0. Упаковка итог не меняет. Каждое правило считается отдельно: строка общего счёта может войти
в тройки нескольких правил (каскад отменён, AURORA-1751).
Упаковка: строка свода неделима; в группы идут только строки с суммой > 0; группы 1:1, 1:N, N:1 при N ≤ 5, N:M нет;
сначала меньшее число строк, внутри размера — точные, затем с расхождением ≤ 5 % к меньшей сумме по возрастанию
процента расхождения; строка — не более чем в одной группе правила; группы одного размера с одинаковым процентом,
делящие строку, не сопоставляются. Ответ SHALL содержать version_no, rule_id, rule_no, company_a_id,
company_b_id, итог тройки, matched[] (точные группы), with_discrepancy[] (расхождение ≤ 5 %) и unpaired[]. Группа —
{rule_id, company_a_id, company_b_id, amount_a, amount_b, deviation_amount, deviation_percent, osv_entry_ids_a[], osv_entry_ids_b[]}; строка без пары — {row, reason}, reason: NO_MATCH, AMBIGUOUS (ничья), ZERO_AMOUNT,
NEGATIVE_AMOUNT (сумма < 0, в упаковку не идёт). Суммы и проценты в JSON SHALL быть строками с двумя знаками.
Scenario: Точная строка среди несопоставленных остатков
- WHEN правило № 6 для Счастливочной и АО «Русский бисквит» даёт суммы 110 429 113,15 и 106 311 116,46 руб., а точная группа 62.01 ↔ 60.01 составляет 106 311 116,46 руб.
- THEN точная группа в
matched, три остальные строки стороны A — вunpaired,eliminated_amount = 106311116.46,deviation_percent = 3.87
Scenario: Отрицательная строка
- WHEN у A по B строки 150 и −50, у B по A строка 100
- THEN
amount_a = 100.00,amount_b = 100.00,eliminated_amount = 100.00; строка −50 — вunpairedс причинойNEGATIVE_AMOUNT
Scenario: Две строки свода упакованы с одной встречной
- WHEN по одному правилу есть дебетовые строки 40 и 60 и встречная кредитовая строка 100
- THEN
matched[]содержит группу с суммами 100.00 и 100.00,deviation_amount = 0.00и исходнымиosv_entry_ids_a/b
Scenario: Ручное правило и превышение порога
- WHEN правило ручное либо расхождение больше 5 %
- THEN
eliminated_amount = 0.00независимо от упаковки
Scenario: Исключённый счёт не входит в пары
- WHEN правило включает родительский счёт 76 и исключает 76.07.5
- THEN строки счёта 76.07.5 и его потомков не входят ни в группы, ни в
unpaired
Scenario: Другая пара компаний не смешивается с выбранной
- WHEN по одному правилу есть встречные строки для A→Б и A→В, а запрошены
company_a_id=A,company_b_id=Б - THEN ответ содержит только строки A→Б и Б→A
Scenario: Расхождение сравнивается в процентах
- WHEN со строкой 100 стороны A могут сойтись строки B 95,50 (4,50 руб., 4,71 %) и 104,60 (4,60 руб., 4,60 %)
- THEN группа — 100 ↔ 104,60; строка 95,50 — в
unpairedс причинойNO_MATCH
Scenario: Правило по оборотам
- WHEN запрошена тройка по правилу ББ с
measure = turnover - THEN 422
INVALID_RULE
Requirement: Пары ВГО версии
GET /api/v1/consolidations/versions/{version_id}/elimination-totals (CONSOLIDATION_VIEW, policy по ГК) SHALL
возвращать {version_no, items[]} — итоги всех троек версии: правила ББ с measure = closing версии правил ×
упорядоченные пары разных компаний периметра, у которых есть строки хотя бы на одной стороне. Сводная ОСВ SHALL строиться
один раз на запрос. Элемент — {rule_id, rule_no, elimination_mode, company_a_id, company_b_id, amount_a, amount_b, deviation_amount, deviation_percent, eliminated_amount, exceeds_threshold, status, osv_entry_ids_a[], osv_entry_ids_b[]}
с теми же суммами, что у тройки («Пары ВГО тройки»); status: MANUAL — elimination_mode = manual; ONE_SIDED — одна из
сумм нулевая; AUTO — eliminated_amount > 0; иначе DISCREPANCY (расхождение > 5 % или меньшая сторона < 0). Порядок — rule_no, затем порядок компаний периметра.
Суммы и проценты — строки с двумя знаками. Невидимая и несуществующая версия — 404 NOT_FOUND.
Scenario: Список совпадает с тройкой
- WHEN по правилу № 6 у A на B 300, у B на A 100, у B на A дебиторка 70 и у A на B кредиторка 70
- THEN в
itemsтройка (6, A, B) —DISCREPANCY,eliminated_amount = 0.00; тройка (6, B, A) —AUTO,eliminated_amount = 70.00; те же суммы возвращаетelimination-pairsдля каждой тройки
Scenario: Пересечение правил
- WHEN дебиторка A на B по счетам № 6 есть, а займов B у A (67) нет
- THEN в
itemsесть и тройка (6, A, B), и тройка (7, A, B) со статусомONE_SIDED— двойной учёт до разнесения строк (AURORA-1751)
Scenario: Правило без строк
- WHEN по счетам правила № 3 строк ни у A, ни у B нет
- THEN тройки (3, A, B) в
itemsнет
Scenario: Обе стороны отрицательные
- WHEN у A по B −50, у B по A −50
- THEN тройка
DISCREPANCY,deviation_percent = null,eliminated_amount = 0.00