arm-ka-docs/analyst/eliminations-vgo.md
Andrey Epifancev e70f589112 DBML v5.23 (elimination_rules_version NOT NULL), §5 без каскада, пары ВГО по версии (#164, AURORA-1536)
Решения владельца 23.10.2026 по ревью arm-core #175:
- consolidation_version.elimination_rules_version — not null, существующим
  версиям номер проставляет миграция по дате создания;
- §5: каскад по остатку отменён (21.10), правила считаются отдельно до
  разнесения строк AURORA-1751; суммы сторон со знаком, при min <= 0 процент
  не считается; отрицательные строки в упаковку не идут (NEGATIVE_AMOUNT);
  группы сортируются по проценту расхождения; выдача — список троек версии
  и детализация тройки;
- OQ-173, OQ-174, OQ-175; страница аналитика; гайд фронта §5.7.2;
  ссылки на модель и диаграммы — v5.23.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:34:21 +04:00

328 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Элиминация ВГО: правила, расчёт, корректировки
Документ для аналитика, 19.10.2026 (RSHB/arm-ka-docs#132; сопоставление — по решениям OQ-07 и OQ-173, #136). Основа — модель данных
[`model/data-model-v5.23.dbml`](../model/data-model-v5.23.dbml) и правила предметной области
[`context/05-domain-rules.md`](../context/05-domain-rules.md) §3–§6, §8. Источник истины — они: при расхождении прав
DBML и `05-domain-rules.md`, а этот документ поправь следом. Где написано наше прочтение ТЗ, а не прямая цитата, это
помечено; такие места собраны в раздел 6 вопросами.
**Что нужно от аналитика:** подтвердить разделы 1–4 (или поправить) и ответить на вопросы раздела 6. Ответы попадают в
`05-domain-rules.md` и `07-open-questions.md`, затем — сюда.
---
## 1. Что хранится в БД, а что считается
Главный принцип модели (DECISION-5): **производные консолидации не хранятся, пока версия в работе**. Они каждый раз
считаются заново из первички, периметра версии и правил. В БД лежат только исходные данные и ручные решения аналитика.
| Хранится | Что это |
|---|---|
| `osv_entry` | строки ОСВ как пришли из файла + сопоставленный контрагент (`counterparty_id`, `counterparty_match_kind`) |
| `consolidation_member`, `consolidation_document` | периметр версии и привязанные к ней документы |
| `elimination_rule`, `elimination_rule_account` | правила элиминации Прил. 3 / Прил. 4 (раздел 2) |
| `consolidation_version.elimination_rules_version` | по какой версии правил считалась версия консолидации |
| `correction` | ручные корректировки аналитика (раздел 4) |
| `goodwill` | гудвилл: доля владения, решение «учитывать в КФО», комментарий |
| `kfo_result`, `kfo_result_line` | результат КФО |
| Не хранится, считается на лету | Почему |
|---|---|
| сводная ОСВ, признак контрагента ГСК / ГК / Внешний | функция от `osv_entry` и периметра версии |
| пары ВГО (кто с кем сопоставлен, по какому правилу, суммы сторон, расхождение, id строк ОСВ) | функция от `osv_entry` × периметр × правила |
| шахматка | функция от `osv_entry` × периметр × выбранный набор счетов |
| суммы «с учётом корректировок» | сумма пары ± применённые корректировки |
Таблиц сопоставления в модели нет: `vgo_match` (строки 1:1) убрана в v5.3 — её нет ни в ТЗ, ни в UI; агрегат
`vgo_pair_match` убран в v5.10 по DECISION-5.
**Заморозка.** При переходе версии в `VERIFIED` всё посчитанное фиксируется один раз в `ex_snapshot_item.payload`
(jsonb, `item_type` = `vgo_pairs`, `chess`, `corrections`, `goodwill`, `kfo_balance`, `kfo_income`, `summary_osv`).
Срез самодостаточен и не ссылается на таблицы консолидации. Версия после `VERIFIED` — только чтение; изменения — через
новую версию консолидации.
---
## 2. Как разложены правила элиминации
Правила — **данные, а не код**: две таблицы, версионируются.
### 2.1. `elimination_rule` — одна строка приложения
| Поле | Значение |
|---|---|
| `statement_type` | `balance` — Прил. 3 (ББ), `income` — Прил. 4 (ОФР) |
| `rule_no` | № строки приложения |
| `article_1_line` | статья Компании 1 — код строки ББ/ОФР (например, `1230`) |
| `article_2_line` | статья Компании 2 (например, `1520`) |
| `side_1` | сторона Компании 1: `D` или `K` (у Компании 2 — противоположная) |
| `measure` | `closing` — сальдо на конец (ББ), `turnover` — оборот (ОФР) |
| `elimination_mode` | `min` — min(A, B); `full` — вся сумма стороны А (выручка ОФР); `manual` — только вручную |
| `vat_handling` | только ОФР: `none` / `check_90_01_vs_2110` / `weighted_rate` |
| `excise_adjust` | учитывать акциз |
| `version_no`, `valid_from`, `valid_to` | версия набора правил; уникально (`statement_type`, `rule_no`, `version_no`) |
### 2.2. `elimination_rule_account` — счета сторон правила
| Поле | Значение |
|---|---|
| `rule_id` | правило |
| `company_no` | `1` или `2` — сторона |
| `account_id` | счёт или субсчёт из плана счетов (`chart_of_account`) |
| `is_excluded` | `true` — исключение внутри маски («76 кроме 76.07.1») |
Счёт-родитель покрывает свои субсчета (иерархия `chart_of_account.parent_id`), исключения перечисляются отдельными
строками с `is_excluded = true`.
**Пример — правило № 6 ББ** (основная пара «дебиторка ↔ кредиторка»):
| `company_no` | `account` | `is_excluded` |
|---|---|---|
| 1 | 60.02, 60.22, 60.32, 62.01, 62.03, 62.21, 62.31 | false |
| 1 | 76 | false |
| 1 | 76.07.5, 76.07.9 | **true** |
| 2 | 60.01, 60.03, 60.21, 60.31, 62.02, 62.22, 62.32 | false |
| 2 | 76 | false |
| 2 | 76.07.1 | **true** |
(каждый счёт — отдельная строка таблицы; в примере сгруппированы для краткости)
### 2.3. Версии правил
Правила меняются выпуском новой версии (`version_no`), старая не правится. Версия, по которой посчитана версия
консолидации, фиксируется в `consolidation_version.elimination_rules_version` при создании версии (версия правил,
действующая на дату создания, OQ-174; с DBML v5.23 поле обязательно) и дальше не меняется — так расчёт объясним и
воспроизводим, даже если правила потом поменялись.
### 2.4. Стартовый набор — Прил. 3 (ББ, `measure = closing`)
| № | Статья А (Дт) | Счета А | Статья Б (Кт) | Счета Б | mode | Примечание |
|---|---|---|---|---|---|---|
| 1 | 1150 ОС | 03, 76.07.9 (01 — резервный) | 1450 / 1550 | 76.07.1 | manual | лизинг/аренда внутри группы; у Б: 76.07.1 − 76.07.5 − 76.07.9 |
| 2 | 1170 ФВ долгосрочные | 58.01 | 1310 / 1350 | 80, 83 | manual | гудвилл (раздел 4.5) |
| 3 | 1170 ФВ долгосрочные | 58.03 | 1410 Займы долгосрочные | 67.03 | min | |
| 4 | 1240 ФВ краткосрочные | 58.03 | 1510 Займы краткосрочные | 66.03 | min | |
| 5 | 1230 Дебиторка | 76 (кроме 76.07.5, 76.07.9) | 1510 | 66.04, 67.04 | min | проценты |
| 6 | 1230 Дебиторка | 60.02, 60.22, 60.32, 62.01, 62.03, 62.21, 62.31, 76 (кроме 76.07.5, 76.07.9) | 1520 Кредиторка | 60.01, 60.03, 60.21, 60.31, 62.02, 62.22, 62.32, 76 (кроме 76.07.1) | min | основная пара |
| 7 | 1230 Дебиторка | те же, что № 6 | 1410 | 67 | min | |
| 8 | 1230 Дебиторка | те же, что № 6 | 1510 | 66 | min | |
### 2.5. Прил. 4 (ОФР, `measure = turnover`) — не формализовано
| Статья | Сторона А | Сторона Б | mode / НДС |
|---|---|---|---|
| 2110 Выручка | Кт 90.01 (с НДС, приоритет) либо Дт 62.01/62.21/62.31 | Кт 60.01/60.21/60.31 + 76 (кроме 76.07.*, 76.03.*, плюс 76.07.2) | `full`; без НДС = с НДС / (1 + средневзвешенная ставка / 100) |
| 2120 Себестоимость | — | — | = ВГО по 2110, зеркально, без НДС |
| 2310 Доходы от участия | Дт 76.03 | Кт 76.03 | сторона А |
| 2320 Проценты к получению | Дт 76.03/76.05/76.08/76.09 | Кт 66.02/.04/.06/.22/.24/.26, 67.02/.04/.06/.22/.24/.26 | min |
| 2330 Проценты к уплате | Кт чётных субсчетов 66/67 + 76.07.5/76.08/76.28/76.38 | Дт 76.03/.05/.08/.09 | min |
Это выжимка из FNR03; в правила-данные её можно превратить только после формализации с методологами (вопрос В-8).
---
## 3. Как считаем
### 3.1. Вход
- Периметр — `consolidation_member` версии (не текущий состав ГСК).
- ОСВ — документы, привязанные к версии (`consolidation_document`), только `CONFIRMED`.
- Строки — `group_type = COUNTERPARTY`, `data_type ∈ (BU, SUM)`, контрагент сопоставлен с ЮЛ **из периметра**.
- Для ББ — только сальдо на конец (`closing_debit` / `closing_credit`).
### 3.2. Автоматическое сопоставление пар (DOC-223044, `05-domain-rules.md` §5)
Единица расчёта — **тройка** (правило, компания A, компания B). Правила считаются для каждой упорядоченной пары
компаний — и (A, B), и (B, A), поэтому зеркальные правила № 9–16 Прил. 3 отдельно не заводятся (№ 14 для (A, B) — это
№ 6 для (B, A)). У тройки два уровня (решения владельца 19.10.2026, OQ-07 и OQ-173):
**Сумма элиминации** — агрегат, идёт в КФО. Каждое правило считается **отдельно** (каскад по остатку отменён решением
владельца 21.10.2026 по созвону с заказчиком 19.10.2026):
```
ΣA = Σ строк A с контрагентом B по счетам стороны 1 (с исключениями), со знаком
ΣB = Σ строк B с контрагентом A по счетам стороны 2, со знаком
deviation_amount = |ΣA − ΣB|; deviation_percent = deviation_amount / min(ΣA, ΣB) × 100
элиминация = min(ΣA, ΣB) при расхождении ≤ 5 % (раздел 3.3)
```
Суммы — **со знаком**, как в шахматке (решение владельца 23.10.2026): отрицательное сальдо строки — встречная позиция
(аванс, переплата), у A по B строки 150 и −50 дают ΣA = 100.
Счета правил пересекаются (дебиторка у № 5–8 общая; 67.03 входит и в № 3, и в маску 67 у № 7; 66.03 — в № 4 и в № 8),
поэтому строка такого счёта пока учитывается в **каждом** подходящем правиле, и сумма по паре компаний может превысить
сумму стороны. Двойной учёт снимается разнесением строк по правилам и ручным переносом между правилами (AURORA-1751).
**Упаковка строк** — какие строки ОСВ с какими сопоставлены (детализация и строки без пары). Правило охватывает
несколько счетов, поэтому строки сопоставляются группами **1:1, 1:N, N:1**; N:M нет, N ≤ 5. Выбирается **минимальная
упаковка** — группа с меньшим числом строк: если сумму закрывают и «1 Дт ↔ 2 Кт», и «1 Дт ↔ 3 Кт», берётся 1:2.
```
каждое правило — отдельно; строки с суммой > 0 по счетам правила
строки с отрицательной суммой → «без пары», разбирает аналитик; с нулевой → «без пары»
для k = 2 … 6 (число строк группы: 1:1, затем 1:2 и 2:1, … до 1:5 и 5:1):
кандидаты размера k из ещё не сопоставленных строк, закрывающие сумму:
сначала точные, затем с расхождением ≤ 5 % к меньшей — по возрастанию процента расхождения
группа без соперников → пара, её строки выбывают
две и более группы с тем же k и тем же процентом, делящие строку → на ручное
строки, не вошедшие ни в одну группу → «без пары»
```
Способ упаковки сумму элиминации не меняет: в КФО идёт агрегат тройки.
**Результат** (структура в памяти, в БД не пишется): правило, компания A, компания B, ΣA, ΣB, расхождение; пары
упаковки с id строк `osv_entry` обеих сторон; строки без пары.
### 3.3. Сколько элиминируется по тройке
| Ситуация | Что происходит |
|---|---|
| ΣA = ΣB | элиминируется сумма |
| расхождение ≤ 5 % (к меньшей) | элиминируется `min(ΣA, ΣB)` |
| расхождение > 5 % | подсветка, автоматически не элиминируется, ждёт корректировки |
| нет второй стороны (ΣB = 0) | элиминируется 0, строки — «без пары» (вопрос В-4) |
| правило с `mode = manual` (№ 1, № 2) | автоматически не элиминируется, только корректировкой / гудвиллом |
Каждое правило считается отдельно, поэтому строка общего счёта пока входит в суммы нескольких правил (раздел 3.2) —
двойной учёт снимается разнесением строк (AURORA-1751).
Алгоритм — по ТЗ (решение владельца 08.10.2026, OQ-155): пары, `min`, порог 5 %. Эталонный Excel «Консолидация_Группа
РБ» элиминирует у каждой компании полную сумму её строк на группу — это сознательное расхождение с эталоном.
**Куда идёт сумма** (наше прочтение — подтвердить, В-3): элиминируемая сумма тройки уменьшает статью `article_1_line` у
Компании 1 и статью `article_2_line` у Компании 2.
### 3.4. Шахматка (DOC-221277, `05-domain-rules.md` §6)
Шахматка — **сверочный инструмент**, она ничего не элиминирует. Строки и столбцы — компании периметра, диагональ пуста.
```
amount_a(A,B) = Σ closing_debit строк A с контрагентом B по выбранным дебетовым счетам (kind ∈ A, AP)
amount_b(A,B) = Σ closing_credit строк B с контрагентом A по выбранным кредитовым счетам (kind ∈ P, AP)
variance = |amount_a − amount_b|; variance_pct = variance / min(amount_a, amount_b) × 100; > 5 % — подсветка
```
Предустановки — **наборы счетов**: «58.03 vs 67.03 + 66.03», «60 + 62 + 76 vs 60 + 62 + 76», «все счета». Так
решено 15.10.2026 и так сделано в arm-core (AURORA-1537): шахматка — по наборам счетов, а пары по правилам Прил. 3,
элиминируемая сумма и версия правил — отдельная задача AURORA-1536 (§5). Шахматка по правилу элиминации — вопрос В-1.
Результирующая таблица «компания против группы» по статьям: Дебиторка, Кредиторка, ФВ, Долгосрочные займы,
Краткосрочные займы; колонка «Итого по группе» (состав счетов — §6).
### 3.5. Путь до КФО (`05-domain-rules.md` §8)
По каждому коду строки ББ/ОФР:
```
sum_amount = Σ по компаниям периметра (отчётная колонка ББ / ОФР, тыс. руб.)
eliminated_auto = Σ сумм элиминации троек (3.2–3.3), в тыс. руб.
eliminated_manual = Σ корректировок со статусом APPLIED
goodwill_adjust = гудвилл, если «учитывать в КФО»
consolidated = sum_amount − eliminated_auto − eliminated_manual ± goodwill_adjust
vgo_amount = eliminated_auto + eliminated_manual
```
Единицы: ОСВ — рубли, ББ/ОФР — тыс. руб.; считаем в рублях, округляем до тыс. на выходе (допущение OQ-26, В-10).
Пороги существенности (ТЗ 6.8): ББ — 1 % валюты баланса или 10 % ЧА; ОФР — 1 % выручки или 10 % валовой прибыли;
превышение требует обоснования (`threshold_justification`).
---
## 4. Как производятся корректировки
### 4.1. Принцип
Корректировка — **наложение поверх первички**: строки ОСВ и формы не меняются, итог = свод первички − Σ применённых
корректировок. Корректировка — единственное, что аналитик хранит по паре ВГО; сама пара не хранится.
### 4.2. Привязка к паре
Корректировка ссылается на пару **по естественному ключу**, а не на id строк ОСВ:
`(consolidation_version_id, rule_id, legal_entity_id, counterparty_id)` + `statement_type`
- `rule_id` — правило; `NULL` — ручная корректировка без пары;
- `legal_entity_id` — компания 1 (чья первичка корректируется);
- `counterparty_id` — компания 2.
Благодаря этому корректировка переживает пересчёт: пары каждый раз строятся заново, а корректировка находит свою пару по
ключу. Что делать, если пара после перезагрузки ОСВ исчезла, — вопрос В-6.
### 4.3. Поля `correction`
| Поле | Значение |
|---|---|
| `statement_type` | ББ или ОФР (ТЗ 6.8 — отдельные формы) |
| `debit_line_code`, `debit_account_id`, `debit_adjustment` | дебетовая сторона: строка формы, счёт, сумма |
| `credit_line_code`, `credit_account_id`, `credit_adjustment` | кредитовая сторона |
| `deviation_amount`, `deviation_percent` | расхождение пары — справочно, пересчитывается |
| `reason_code` | ТЗ 6.4: нереализованная выручка, акциз, агентское вознаграждение, амортизация по внутригрупповой аренде, проценты по внутригрупповым займам, доходы от участия, прочее |
| `comment` | **обязателен** (ТЗ 6.8) |
| `status` | `DRAFT` → `APPLIED` / `REVOKED`; в расчёт входят только `APPLIED` |
| `parent_correction_id` | распределение суммы одной стороны по нескольким статьям — дочерние записи, глубина 1 |
| `version_no` | растёт при каждом изменении |
### 4.4. Правила
- **Контроль полноты:** `Σ debit_adjustment ≈ Σ credit_adjustment` (±5 %), иначе ВГО распределено не полностью.
- **История:** каждое изменение — `audit_event(CORRECTION_*)` со значениями до/после, `version_no++` (сноска 22 ТЗ).
- **Кто и когда:** право `CONSOLIDATION_EDIT`, версия «в работе», своя блокировка версии. После `VERIFIED` — только
чтение.
- **Типичный сценарий:** пара с расхождением > 5 % → аналитик разбирается в причине → создаёт корректировку по паре с
причиной и комментарием → применяет (`APPLIED`) → КФО пересчитывается.
### 4.5. Гудвилл (правило № 2) — отдельно от корректировок
Таблица `goodwill`, по каждой паре ГО → ДО периметра: `FV = Σ closing_debit 58.01` (контрагент ДО), `UK = 1310 ББ ДО`
(FNR03: Кт 80 + Кт 83, при отсутствии — 1310), `share` — ручной ввод, `participation = 1310 × share / 100`,
`goodwill = FV − participation` (может быть < 0), `include_in_kfo` + обязательный комментарий. Положительный итог —
строка «Гудвилл» в разделе I КФО.
---
## 5. Сквозной пример: «Пекарня Счастливочная» ↔ «Русский бисквит», 30.09.2024
Данные — фикстуры, ОСВ Q3 2024. Строки Счастливочной с контрагентом «Русский бисквит» на дебетовых счетах правила № 6:
| Счёт | Аналитика | Сумма, руб. |
|---|---|---|
| 62.01 | «БП Производство» | 106 311 116,46 |
| 62.01 | «БП Аренда» | 1 401 330,00 |
| 76.06 | | 350 000,00 |
| 76.АВ | НДС по авансам | 2 366 666,69 |
У «Русского бисквита» — одна строка 60.01 (кредиторка) на Счастливочную: 106 311 116,46.
**Упаковка:** 62.01 «БП Производство» 106 311 116,46 ↔ 60.01 РБ 106 311 116,46 — точная пара 1:1 (минимальная
упаковка; вариант 2:1 «БП Производство + БП Аренда» = 107 712 446,46, расхождение 1,32 %, больше по числу строк и не
выбирается). 62.01 «БП Аренда», 76.06, 76.АВ — без пары.
**Сумма элиминации (правило № 6):** ΣA = 110 429 113,15, ΣB = 106 311 116,46, расхождение 3,87 % ≤ 5 % →
элиминируется `min` = 106 311 116,46: статья 1230 у Счастливочной, 1520 у РБ. Это при условии, что у РБ нет 66.04 / 67.04
на Счастливочную: иначе 76.06 и 76.АВ войдут и в правило № 5 — двойной учёт до разнесения строк (AURORA-1751).
**76.АВ** (НДС по авансам) сейчас входит в дебиторку по правилу «76 кроме 76.07.5/76.07.9». Эталон его исключает —
вопрос OQ-164 (В-9).
**Эталон** («Консолидация_Группа РБ.xlsx», лист `01.10.2024`) элиминирует у Счастливочной 108 062 тыс. — всё, кроме
76.АВ. Разница с нашим расчётом (1 751 тыс. = 1 401 330 + 350 000) — ожидаемая (OQ-155).
---
## 6. Вопросы к аналитику
| № | Вопрос | Что сейчас |
|---|---|---|
| В-1 | Шахматка: предустановки — наборы счетов (§6) или правила элиминации? Если правила — как быть с «58.03 vs 67.03 + 66.03» (это правила № 3 и № 4 сразу) и «все счета» (не правило)? | решено 15.10: наборы счетов, сделано (AURORA-1537); правила — в сопоставлении пар (AURORA-1536) |
| В-2 | ~~Сопоставление строго 1:1 по строкам или с агрегацией?~~ | **решено 19.10.2026 (OQ-07):** агрегат тройки в КФО, упаковка 1:1 / 1:N / N:1 — минимальная (раздел 3.2) |
| В-3 | Элиминация списывается со статей `article_1_line` / `article_2_line` правила? Как выбирать статью у правил с двумя статьями: «1450 / 1550» (№ 1), «1310 / 1350» (№ 2)? | наше прочтение, не подтверждено |
| В-4 | Строка без встречной стороны (у второй компании нет строки на первую): не элиминируется до корректировки, элиминируется целиком или показывается как отдельная категория? | в упаковке — «без пары»; в сумме тройки элиминируется 0, если ΣB = 0 |
| В-5 | Корректировка по паре добавляется к автоматической элиминации или заменяет её? Как проводится корректировка «односторонней» пары, если контроль требует Σ Дт ≈ Σ Кт (±5 %)? | по §8: добавляется (`eliminated_manual` отдельно от `eliminated_auto`) |
| В-6 | Пара исчезла после перезагрузки ОСВ (суммы изменились, пара не собралась) — что с её корректировками: остаются, помечаются, снимаются? | не описано |
| В-7 | Средневзвешенная ставка НДС для выручки ОФР: ставка считается из ОСВ (по какому правилу) или вводится вручную? Хранить её негде: в модели она жила в `vgo_pair_match`, убранной в v5.10 | не описано |
| В-8 | Правила ОФР (Прил. 4): нужна формализация с методологами — счета сторон, режимы, НДС, акциз | открыто |
| В-9 | 76.АВ / 76.ВА (НДС по авансам) — исключать из ДЗ / КЗ? Есть ли ещё такие субсчета 76? | OQ-164 открыт, вопрос в AURORA-1373 |
| В-10 | Округление при элиминации: рубли → тыс. руб. на выходе, ROUND_HALF_UP? | допущение OQ-26 |
| В-11 | Правило № 1: если у компании А нет ОСВ по 03 — результат в строку «ППА»? | открыто в §4 |
| В-12 | ~~Счета правил пересекаются — как избежать двойной элиминации?~~ | **решено 21.10.2026:** каскад по остатку отменён, правила считаются отдельно, двойной учёт снимает разнесение строк по правилам (AURORA-1751, раздел 3.2) |