- суммы сторон со знаком, как в шахматке; строки с суммой < 0 в упаковку не идут, причина NEGATIVE_AMOUNT; при min(ΣA, ΣB) ≤ 0 процент null и элиминация 0, статус списка AUTO — только при элиминации > 0 - группы одного размера выбираются по проценту расхождения к меньшей сумме (точной дробью), ничья — по равному проценту - elimination_amount группы и тройки переименован в deviation_amount; version_no тройки обязателен - суммы и проценты пар в JSON — с двумя знаками - elimination-pairs по правилу с measure ≠ closing — 422 INVALID_RULE - дельта спеки, design.md (R-7, R-8), tasks, README make check-full: exit 0 (1896 passed; integration 990 passed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
17 KiB
MODIFIED 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и версия не созданы
ADDED Requirements
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
REMOVED Requirements
Requirement: Правила элиминации версии
Reason: с DBML v5.23 elimination_rules_version задан у каждой версии консолидации; ветка «версия правил не
зафиксирована — последние открытые правила» больше не существует.
Migration: требование «Правила элиминации зафиксированной версии»; клиенту is_version_fixed всегда true.