418 lines
41 KiB
Markdown
418 lines
41 KiB
Markdown
# 05. Правила предметной области (выжимка из ТЗ, имена — v5.13)
|
||
|
||
Каждый раздел: источник в ТЗ → правило → где в модели. Где ТЗ молчит или противоречит — пометка **[открыто]** и
|
||
ссылка на `07-open-questions.md`.
|
||
|
||
---
|
||
|
||
## §1. Парсинг ОСВ (tabular-parser + маппинг в ядре)
|
||
|
||
Источник: «Парсинг ОСВ» (DOC-260010), FNR01 (DOC-037740), реальные файлы `fixtures/osv/`.
|
||
|
||
### 1.0 Главный принцип: структура определяется динамически, не по координатам
|
||
|
||
ОСВ приходят из разных конфигураций 1С (8.2/8.3, УПП, ERP, БП 3.0), из других учётных систем и после ручной правки
|
||
бухгалтером. **Ни одна буква колонки, ни один номер строки, ни одно имя листа не являются контрактом.** Фикстуры в
|
||
`fixtures/osv/` — это примеры двух организаций, а не спецификация формата. Парсер обязан:
|
||
|
||
1. **Найти лист(ы) с ОСВ** по содержимому: любая ячейка, матчащая `оборотно[- ]сальдовая ведомость` (без учёта регистра,
|
||
«ё/е», переносов строк). Один файл может содержать несколько листов с разными ОСВ → несколько `document`.
|
||
Листы без признаков ОСВ/ББ/ОФР — пропускать с warning, не падать.
|
||
2. **Найти заголовок таблицы** по содержимому: строка(и), где встречаются одновременно «Сальдо на начало…»,
|
||
«Оборот(ы) за период», «Сальдо на конец…» (с вариантами: «Нач. сальдо», «Обороты», «Кон. сальдо», «Дебет/Кредит»,
|
||
«Дт/Кт», «Д-т/К-т»). Заголовок может занимать 1–4 строки с merged-ячейками; группировки могут быть как под «Счет»
|
||
в первой колонке, так и строкой «Субконто», так и перечислением через запятую (вариант 2).
|
||
3. **Определить колонки сумм по тексту**, а не по позиции: для каждой из трёх групп («начало», «оборот», «конец»)
|
||
найти подзаголовки «Дебет»/«Кредит» в следующей строке под ней (с учётом merged-диапазонов). Допустимы ОСВ без
|
||
«начала» (только обороты и конец), с одной свёрнутой колонкой сальдо (знак = сторона), с дополнительными
|
||
колонками («Количество», «Валюта», «ИНН», «КПП», «Код»). Неизвестные колонки — сохранять в `extra` строки, не терять.
|
||
4. **Определить колонку текста строки** — первая непустая слева от сумм (обычно A, но не обязательно); ИНН — колонка с
|
||
заголовком `ИНН` **или** колонка, где ≥ 80 % непустых значений — 10/12 цифр.
|
||
5. **Определить иерархию строк** по outline level Excel; если уровней нет (файл пересохранён, csv, другая система) —
|
||
по отступу (ведущие пробелы / `alignment.indent`), по жирности счёта, по шаблону кода счёта и, в крайнем случае, по
|
||
факту «сумма детей = родитель». Стратегии применяются каскадом, выбранная стратегия пишется в `metrics.warnings`.
|
||
6. **Шапку документа** (организация, счёт, период, «выводимые данные») искать регулярками по всем ячейкам выше заголовка
|
||
таблицы; отсутствие любого поля — не ошибка, а `null` + warning (ядро попросит аналитика уточнить при проверке).
|
||
7. **Самопроверка результата** (контрольные суммы §1.4) — единственный способ понять, что структура угадана верно.
|
||
Если тождества не сходятся — не «подгонять», а вернуть `needs_check` с диагностикой, какой шаг не уверен.
|
||
8. Правила парсера — **данные, а не код**: словари синонимов заголовков, группировок, периодов лежат в
|
||
`tabular-parser/rules/*.yaml` и расширяются без релиза. Новый формат ОСВ = новая фикстура + при необходимости новый
|
||
синоним, а не новая ветка `if`.
|
||
|
||
Тесты парсера строятся по принципу «свойств»: для любого входного файла результат удовлетворяет инвариантам §1.4,
|
||
число строк-контрагентов ≥ 0, `Итого` = корень. Фикстуры — регрессия, не оракул формата.
|
||
|
||
### 1.1 Шапка — два варианта 1С (как **примеры**, не исчерпывающий список)
|
||
|
||
**Вариант 1** (все наши фикстуры):
|
||
```
|
||
r1 <Наименование организации> ← владелец ОСВ (сверить с legal_entity → document.counterparty_check)
|
||
r2 Оборотно-сальдовая ведомость по счету 60 за 1 квартал 2025 г. ← счёт после «по счету», период после «за»
|
||
r4 Выводимые данные: БУ (данные бухгалтерского учета) ← data_type шапки
|
||
r7 Параметры: | Валюта БУ, НУ: RUB (или «руб.»)
|
||
r9 Счет | Сальдо на начало периода | Обороты за период | Сальдо на конец периода
|
||
r10 Контрагенты | [ИНН] | Дебет | Кредит | Дебет | Кредит | Дебет | Кредит
|
||
r11 Договоры ← может отсутствовать
|
||
```
|
||
Группировки — отдельные строки первой колонки блока заголовка (`Счет`, `Подразделение`, `Контрагенты`, `Договоры`),
|
||
**порядок строк = порядок вложенности**. Ячейка `ИНН` в той же строке, что `Контрагенты` → `has_inn = true`.
|
||
|
||
**Вариант 2** (встречается у других клиентов):
|
||
```
|
||
Оборотно-сальдовая ведомость по счету 60
|
||
Период: 1 квартал 2026 г.
|
||
Детализация по субсчетам, субконто: Контрагенты, Договоры ← группировки через запятую, первая неявно ACCOUNT
|
||
Выводимые данные: сумма
|
||
Субконто | Сальдо на начало периода | Оборот за период | Сальдо на конец периода
|
||
| Дебет | Кредит | Дебет | Кредит | Дебет | Кредит
|
||
```
|
||
|
||
Нормализация значений шапки в типы:
|
||
|
||
| Текст в шапке | `group_type` |
|
||
|---|---|
|
||
| Счет, Счета, Субконто (первая) | ACCOUNT |
|
||
| Подразделение, Подразделения | DEPARTMENT |
|
||
| Контрагент, Контрагенты | COUNTERPARTY |
|
||
| Договор, Договоры, Договоры контрагентов | CONTRACT |
|
||
| прочее (Валюта, Номенклатура, Статьи…) | OTHER |
|
||
|
||
Результат шапки → `recognition_job.group_schema` (по листу): `{"locator":"sheet:Лист_1","account":"60",
|
||
"period_text":"1 квартал 2025 г.","data_type":"BU","groups":[{"type":"ACCOUNT"},{"type":"COUNTERPARTY","has_inn":true},
|
||
{"type":"CONTRACT"}]}` и → `document(document_type=OSV, osv_account_code, period_year, period_quarter, is_cumulative, locator)`.
|
||
|
||
### 1.2 Колонки сумм
|
||
|
||
Заголовки сумм объединены (merged) и позиции колонок плавают между файлами даже одной организации. Определять по тексту
|
||
(§1.0 п. 3). Всё правее последней распознанной колонки сумм — не часть таблицы (в фикстурах там встречаются посторонние
|
||
числа) — складывать в `extra`, в суммы не брать. Числа могут быть numeric **или** текстом («1 234 567,89», «(1 234)»,
|
||
«-», «—», неразрывные пробелы) — нормализовать в Decimal; пустая ячейка/прочерк = NULL (не 0). Отрицательные возможны.
|
||
|
||
### 1.3 Строки
|
||
|
||
Для каждой строки табличной части: `row_number` (исходный номер), `level` (= outline level Excel; в xls — `rowinfo_map`),
|
||
`row_value` (текст первой колонки), шесть сумм, ИНН (если колонка есть; оставить только цифры, иначе NULL),
|
||
`data_type` (строки «USD», «Вал.», «Кол.» под контрагентом → CURRENCY/QUANTITY; по умолчанию — из шапки).
|
||
|
||
Классификация строки (п. 6–10 «Парсинг ОСВ»):
|
||
1. Строка — счёт, если текст матчится `^\d{2,3}(\.\d{1,2}){0,2}(,\s.*)?$` (с опциональным названием после запятой:
|
||
«60.01, Расчеты с поставщиками») → ACCOUNT, `account_code` (нормализованный), все дочерние строки наследуют счёт.
|
||
2. Не-счёт: позиция в ветке = количество не-счётных предков + 1 → соответствующая группировка из `group_schema`
|
||
(после последнего ACCOUNT). Увеличение уровня — ребёнок, равный — сосед, уменьшение — конец группы.
|
||
3. После COUNTERPARTY дочерние продолжают классифицироваться по схеме (CONTRACT, затем OTHER).
|
||
4. Строка «Итого» (level 0, последняя) — контрольная сумма всего документа; не сохранять как `osv_entry`
|
||
**[открыто №2]**, но использовать для сверки.
|
||
5. Все остальные строки сохраняются (п. 11 «Парсинг ОСВ») с `parent_entry_id` **[открыто №2]**.
|
||
|
||
Нормализация кода счёта: `60.1 → 60.01`, `60.01.1 → 60.01.1`, `76.7.1 → 76.07.1`; `account_root` = первые две-три цифры
|
||
(`001` — забалансовый). `account_id` = точное совпадение с `chart_of_account.code`, иначе NULL (администратор добавит).
|
||
|
||
### 1.4 Контрольные суммы парсера
|
||
|
||
- По каждой строке: `opening_debit − opening_credit + turnover_debit − turnover_credit == closing_debit − closing_credit` (±0.01).
|
||
- По каждому родителю: суммы родителя = Σ детей того же `data_type` (по каждой из шести колонок). Строки CURRENCY/QUANTITY
|
||
в суммирование не входят.
|
||
- Строка счёта верхнего уровня == «Итого».
|
||
Нарушение → `recognition_job.status = needs_check`, `document.status = NEEDS_CHECK`, причина — в `metrics.warnings`.
|
||
|
||
### 1.5 Маппинг периода (FNR01) → `period_quarter`, `is_cumulative`
|
||
|
||
| Текст | `period_quarter` | `is_cumulative` |
|
||
|---|---|---|
|
||
| 1 квартал, I кв., 3 месяца, январь-март, 01.01–31.03 | Q1 | true |
|
||
| 2 квартал, II кв. | Q2 | **false** [открыто: 1С может считать нарастающим] |
|
||
| полугодие, 1 полугодие, 6 месяцев, январь-июнь | Q2 | true |
|
||
| 3 квартал, III кв. | Q3 | **false** [открыто] |
|
||
| 9 месяцев, январь-сентябрь | Q3 | true |
|
||
| 4 квартал, IV кв. | Q4 | **false** [открыто] |
|
||
| год, за 2025 год, 12 месяцев, годовая | Q4 | true |
|
||
|
||
Практика на фикстурах: ОСВ «за 3 квартал 2024» у обеих компаний — **квартальные** (opening ≠ сальдо на 01.01), а
|
||
ОСВ «за 2024 г.» — годовые. Признак можно проверить по данным: если есть ОСВ Q1 того же года и
|
||
`Q3.opening == Q1.opening` → нарастающая.
|
||
|
||
Сверка периода документа с периодом версии → `document.period_check` (MATCH / MISMATCH / UNKNOWN); загрузка не блокируется.
|
||
|
||
---
|
||
|
||
## §2. Приведение периодов (DOC-259006)
|
||
|
||
Комплект (ББ + ОФР + ОСВ) за один период нарастающим итогом. Если ОСВ квартальные (`is_cumulative = false`), перед
|
||
консолидацией строится **расчётное представление** (не хранится):
|
||
|
||
```
|
||
opening_* = opening_* первой ОСВ года (Q1)
|
||
turnover_* = Σ turnover_* по кварталам Q1..Qn
|
||
closing_* = closing_* последней ОСВ (Qn)
|
||
```
|
||
Ключ соединения строк между кварталами — (счёт, контрагент, [договор]). **[открыто]**: поведение при отсутствии
|
||
промежуточного квартала; агрегировать на уровне контрагента или договора.
|
||
|
||
---
|
||
|
||
## §3. Сводная ОСВ и сопоставление контрагентов (DOC-221278, FNR01 п. 9)
|
||
|
||
Вход: `consolidation_version_id`. Периметр — `consolidation_member` версии (не текущий `related_group_member`!).
|
||
|
||
1. Для каждой компании периметра: `document` типа OSV со статусом CONFIRMED за `year/quarter` версии, входящие в
|
||
`consolidation_document` версии (если версия ещё не зафиксировала документы — последние подтверждённые).
|
||
2. Строки `osv_entry` этих документов с `group_type = COUNTERPARTY`; `data_type IN (BU, SUM)`.
|
||
3. Строки **не агрегируются**: сто строк по 60.01 у компании — сто строк в своде. `counterparty_name/inn` показываются
|
||
как в файле.
|
||
4. Резолв контрагента (при маппинге результата и повторно при построении свода, если `counterparty_id IS NULL`):
|
||
ИНН → `legal_entity.inn`; иначе точное имя → `legal_entity.name`; иначе `normalize(name)` →
|
||
`legal_entity_alias.normalized_alias`. Несколько кандидатов → NULL (ручное сопоставление).
|
||
`normalize` = lower, trim, убрать пунктуацию/кавычки, убрать ОПФ (ООО, АО, ПАО, ИП…), схлопнуть пробелы.
|
||
5. Признак контрагента: ГСК / ГК / Внешний (см. `04-data-model-guide.md`).
|
||
6. Количество строк свода = количеству отобранных `osv_entry`.
|
||
|
||
**Ручное сопоставление** (алгоритм в DOC-221278): список кандидатов = компании ГСК и ГК; поиск по ИНН/имени/алиасам;
|
||
выбор → проверка дубля `normalized_alias` у другой компании (предложить перенести или отменить) → создать
|
||
`legal_entity_alias` → `osv_entry.counterparty_id = выбранное`; `counterparty_name/inn` не трогать →
|
||
`audit_event(OSV_COUNTERPARTY_MAP)` → автоприменить к другим строкам версии с тем же именем, если `counterparty_id`
|
||
пуст, алиас однозначен и ИНН строки не противоречит.
|
||
|
||
Наблюдение по фикстурам: у «Счастливочной» контрагент «Русский бисквит» идёт **с ИНН 3528052718** в колонке E, а у
|
||
«Русского бисквита» ОСВ без колонки ИНН — контрагент «Пекарня Счастливочная ООО» только текстом → резолв по алиасу.
|
||
|
||
---
|
||
|
||
## §4. Правила элиминации — пары счетов (Прил. 3 ТЗ, DOC-223044, FNR03)
|
||
|
||
Хранятся как данные: `elimination_rule` (+ `elimination_rule_account`), версионируются; версия фиксируется в
|
||
`consolidation_version.elimination_rules_version`. Seed-набор для ББ (`statement_type = balance`, `measure = closing`):
|
||
|
||
| № | Статья А (Дт) | Счета А (closing_debit) | Статья Б (Кт) | Счета Б (closing_credit) | mode | Примечание |
|
||
|---|---|---|---|---|---|---|
|
||
| 1 | 1150 ОС | 03, 76.07.9 (01 — резервный) | 1450 / 1550 Прочие обязательства | 76.07.1 | manual | лизинг/аренда внутри группы; у Б: 76.07.1 − 76.07.5 − 76.07.9. Если у А нет ОСВ 03 — результат в строку «ППА» [открыто] |
|
||
| 2 | 1170 ФВ долгосрочные | 58.01 | 1310 / 1350 УК / добавочный | 80, 83 | manual | **гудвилл**; если у Б нет ОСВ 80/83 — элиминируется 58.01 А; `1310(Б) − 58.01(А) > 0` → строка «Гудвилл» |
|
||
| 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 | |
|
||
|
||
Правило 5 % для пар: суммы равны → маппим; расхождение ≤ 5 % (к меньшей) → берём `min`; > 5 % → подсветка, авто не
|
||
маппится, ждёт корректировки (`ADJUSTMENT`).
|
||
|
||
ОФР (`statement_type = income`, `measure = turnover`), из FNR03 — **[открыто]**: формализовать с методологами:
|
||
|
||
| Статья | Сторона А | Сторона Б | 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 |
|
||
|
||
Дополнительные ручные корректировки ОФР (ТЗ 6.4, `correction.reason_code`): нереализованная выручка, акцизы
|
||
(`osv_entry.is_excisable` для 90), агентское вознаграждение, внутригрупповая амортизация по аренде, проценты по
|
||
внутригрупповым займам, доходы от участия, прочее.
|
||
|
||
---
|
||
|
||
## §5. Автоматическое сопоставление ВГО (DOC-223044)
|
||
|
||
Границы: только `closing_debit` / `closing_credit`; только компании периметра версии; пары счетов — из §4; **1:1**;
|
||
строка участвует в одном сопоставлении целиком.
|
||
|
||
```
|
||
для каждой строки a компании A с counterparty_id = B ∈ периметр, group_type = COUNTERPARTY, data_type ∈ (BU, SUM):
|
||
сторона a = closing_debit, если > 0, иначе closing_credit
|
||
кандидаты = строки b компании B с counterparty_id = A, противоположная сторона,
|
||
(счёт a, счёт b) входит в допустимую пару правила r (для каждого r — отдельный набор)
|
||
1. точное 1:1 (сумма a == сумма b, одна пара счетов) и единственный вариант → матч, обе строки выбывают
|
||
2. несколько точных по РАЗНЫМ правилам → не матчим, вниз таблицы на ручное
|
||
3. нет точного: по каждому правилу — минимальное |a − b|; выбираем правило с минимальным расхождением;
|
||
равное минимальное по разным правилам → не матчим (ручное)
|
||
результат пары: rule, A, B, сумма A, сумма B, deviation_amount = |A−B|, deviation_percent = dev / min(A,B) * 100,
|
||
исходные osv_entry.id обеих сторон
|
||
```
|
||
Результат **не хранится** (DECISION-5) — возвращается как структура для шахматки, корректировок и КФО. Ручное решение по
|
||
паре — `correction` с естественным ключом (`rule_id`, `legal_entity_id`, `counterparty_id`).
|
||
|
||
Открыто: агрегировать ли несколько строк одной компании по одному контрагенту и счёту перед матчингом (FNR03 говорит
|
||
«несколько строк суммируются», DOC-223044 — «одна позиция = одна строка»). Рекомендация: агрегировать по
|
||
(компания, контрагент, счёт, сторона), сохраняя список `osv_entry.id` как детализацию.
|
||
|
||
---
|
||
|
||
## §6. Шахматка и результирующая таблица (DOC-221277)
|
||
|
||
Вход: версия + набор дебетовых счетов (`kind ∈ A, AP`) и кредитовых (`kind ∈ P, AP`); предустановки: «58.03 vs
|
||
67.03 + 66.03», «60 + 62 + 76 vs 60 + 62 + 76», «все счета».
|
||
|
||
Строки/столбцы — компании периметра; диагональ пуста.
|
||
```
|
||
amount_a(A,B) = Σ closing_debit строк A с counterparty_id = B по дебетовым счетам
|
||
amount_b(A,B) = Σ closing_credit строк B с counterparty_id = A по кредитовым счетам
|
||
variance = |amount_a − amount_b|; variance_pct = variance / min(amount_a, amount_b) * 100; > 5 % — подсветка
|
||
ячейка: amount_a / amount_b / (variance)
|
||
```
|
||
Фильтры строк — как в §3 (COUNTERPARTY, BU/SUM, `account_id IS NOT NULL`).
|
||
|
||
Результирующая таблица «компания против группы» по статьям: Дебиторка (Дт 60.02, 60.22, 60.32, 62.01, 62.03, 62.21,
|
||
62.31, 76 кроме 76.07.5/76.07.9), Кредиторка (Кт 60.01, 60.03, 60.21, 60.31, 62.02, 62.22, 62.32, 76 кроме 76.07.1),
|
||
ФВ (Дт 58.01, 58.03), Долгосрочные займы (Кт 67, 67.03), Краткосрочные (Кт 66, 66.03, 66.04); колонка «Итого по группе».
|
||
|
||
---
|
||
|
||
## §7. Логические проверки ББ/ОФР (DOC-221281, FNR03 шаг 1)
|
||
|
||
Выполняются по **каждой колонке-дате** отдельно; ничего не меняют; результат не хранится.
|
||
|
||
ББ: `1700 − 1600 = 0`; `1100 = 1110 + 1150 + 1160 + 1170 + 1180 + 1190` (+1120, 1130, 1140 если есть);
|
||
`1200 = 1210 + 1220 + 1230 + 1240 + 1250 + 1260`; `1300 = 1310 + 1320 + 1340 + 1350 + 1360 + 1370`;
|
||
`1400 = 1410 + 1420 + 1430 + 1450`; `1500 = 1510 + 1520 + 1540 + 1550`; `1600 = 1100 + 1200`; `1700 = 1300 + 1400 + 1500`.
|
||
ОФР: `2100 = 2110 − 2120`; `2200 = 2100 − 2210 − 2220`; `2300 = 2200 + 2310 + 2320 − 2330 + 2340 − 2350`;
|
||
`2400 = 2300 − 2410 + 2420` (знаки 2410/2420/2430 — по ФСБУ 4/2023 уточнить [открыто]).
|
||
Межформенная: `1370(отчётная дата) − 1370(начало года) = 2400(ОФР за период)` — при расхождении КА может акцептовать
|
||
с комментарием (`financial_statement_meta.checks_accepted_*`).
|
||
|
||
Когда: после распознавания; после «Сохранить» правок; при «Подтвердить данные». Непройдено → `document.status =
|
||
NEEDS_CHECK`; пройдено/акцептовано + нажато «Подтвердить» → `CONFIRMED` + `audit_event(DOCUMENT_CONFIRM)`.
|
||
Отрицательные значения в формах — в скобках `(2100)` → `-2100`. Значения с `confidence < 100` подсвечиваются.
|
||
|
||
---
|
||
|
||
## §8. Корректировки, гудвилл, КФО, пороги
|
||
|
||
**Корректировка** (DOC-227278, ТЗ 6.8): запись на пару (правило, компания 1, компания 2) и форму (ББ/ОФР); `debit_line_code
|
||
/ debit_adjustment`, `credit_line_code / credit_adjustment`; распределение суммы одной стороны по нескольким строкам —
|
||
дочерние записи (`parent_correction_id`, глубина 1). Контроль: `Σ debit_adjustment` ≈ `Σ credit_adjustment` (±5 %),
|
||
иначе ВГО распределено не полностью. Комментарий обязателен. История — `audit_event(CORRECTION_*)`, `version_no++`.
|
||
|
||
**Гудвилл** (DOC-259025, ТЗ 6.7): по каждой паре ГО→ДО периметра:
|
||
`FV = Σ closing_debit (58.01, контрагент ДО)`; `UK = 1310 ББ ДО на отчётную дату` (FNR03: Кт 80 + Кт 83, при
|
||
отсутствии — 1310); `share` — ручной ввод; `participation = (1310 [+ 1350?]) × share / 100`;
|
||
`goodwill = FV − participation` (может быть < 0); `include_in_kfo` + комментарий обязателен. Положительный итог —
|
||
строка `GOODWILL` в разделе I КФО.
|
||
|
||
**КФО** (ТЗ 6.4, Прил. 10): по каждому коду строки ББ/ОФР:
|
||
`sum_amount = Σ по компаниям` (значения отчётной колонки ББ / ОФР за период, в тыс. руб.) →
|
||
`eliminated_auto` (по §4–5, пересчёт в тыс. руб.) → `eliminated_manual = Σ correction APPLIED` → `goodwill_adjust` →
|
||
`consolidated_amount`; `vgo_amount = eliminated_auto + eliminated_manual`; разрез `kfo_result_line_company` (`amount`,
|
||
`share_percent` = доля компании в консолидированном показателе). Эталон колонок — `Консолидация_Группа РБ.xlsx`, лист
|
||
`01.10.2024`: «значение на дату | в т. ч. взаимные обязательства | очищенные показатели | доля в показателе по группе».
|
||
|
||
**Пороги** (ТЗ 6.8, FNR03 шаги 4–5), `kfo_result.threshold_*`:
|
||
- ББ: если `1300 < 0.1 × 1600` → Σ расхождений ≤ 1 % × 1600 (ВБ), иначе ≤ 10 % × 1300 (ЧА).
|
||
- ОФР: если `2200 / 2110 < 10 %` или отрицательна → ≤ 1 % × 2110 (выручка), иначе ≤ 10 % × 2200.
|
||
Превышение → `threshold_justification` обязателен (принятие риска), подсветка.
|
||
|
||
**Сравнение с КФО клиента** (`scenario = CONFIRM`): `client_kfo_line` построчно: `client_amount`, `client_vgo_amount`
|
||
(ручной ввод), `bank_amount`, `bank_vgo_amount` (из `kfo_result_line`), `deviation_percent` к наименьшему, причина.
|
||
|
||
---
|
||
|
||
## §9. Статусные модели
|
||
|
||
### 9.1 КФО (`consolidation_status`, DOC-037734 + ТЗ 6.10)
|
||
|
||
| Из | В | Кто | Когда |
|
||
|---|---|---|---|
|
||
| — | INITIATED | КА РФ / КА ГО | создана карточка, зафиксирован периметр, сценарий, подход |
|
||
| INITIATED | DOCUMENTS_PROCESSING | система/КА | загрузка и распознавание |
|
||
| DOCUMENTS_PROCESSING | DATA_CORRECTION | КА | есть документы NEEDS_CHECK |
|
||
| DATA_CORRECTION | CONSOLIDATION_RF | КА РФ | все обязательные документы CONFIRMED; инициатор — РФ |
|
||
| DATA_CORRECTION | CONSOLIDATION_GO | КА ГО | инициатор — ГО |
|
||
| CONSOLIDATION_RF | ADJUSTMENT | система | расхождение ≥ 5 % в парах (подстатус) |
|
||
| CONSOLIDATION_RF | VERIFICATION_RF | КА РФ | «отправить на верификацию» |
|
||
| VERIFICATION_RF | CONSOLIDATION_GO | РКА РФ | подтвердил |
|
||
| VERIFICATION_RF | CONSOLIDATION_RF | РКА РФ | вернул (комментарий обязателен) |
|
||
| CONSOLIDATION_GO | CONSOLIDATION_RF | КА ГО | вернул на доработку (комментарий) |
|
||
| CONSOLIDATION_GO | VERIFICATION_GO | КА ГО | если инициатор — КА ГО, верифицирует РКА ГО [открыто] |
|
||
| CONSOLIDATION_GO / VERIFICATION_GO | VERIFIED | КА ГО / РКА ГО | внесены № и дата решения УОБ; версия read-only; пишется `ex_snapshot` |
|
||
| любой нефинальный | INITIATED | КА | нужен другой периметр/сценарий/комплект |
|
||
| любой нефинальный | CANCELLED | КА | аннулировано |
|
||
| VERIFIED | — | — | только «новая версия» (`previous_version_id`) |
|
||
| из макета | REJECTED, ARCHIVED | | ANL-035 — подтвердить с заказчиком |
|
||
|
||
Каждый переход → `consolidation_status_history` + `audit_event(CONSOLIDATION_STATUS_CHANGE)`.
|
||
|
||
### 9.2 Файл (`file_status`): UPLOADED → PROCESSING → PROCESSED | ERROR; любой → SUPERSEDED (замена файлом целиком,
|
||
`superseded_by_file_id`, `version_no + 1`). Физическое удаление после INITIATED запрещено (ANL-020) — только замена.
|
||
|
||
### 9.3 Документ (`document_status`): создаётся ядром при маппинге результата: все значения `confidence = 100`, проверки
|
||
пройдены, период и контрагент совпали → RECOGNIZED [открыто: нужен ли], иначе NEEDS_CHECK; «Подтвердить данные» → CONFIRMED;
|
||
не разобран (нет шапки) → ERROR.
|
||
|
||
### 9.4 Задание (`recognition_status`): queued → published → processing → done | needs_check | error | timeout → dlq;
|
||
replay: dlq → queued (`attempt + 1`). `max_attempts`: OCR 1, парсер 3; `reply_deadline` OCR 5 мин.
|
||
|
||
---
|
||
|
||
## §10. Роли и матрица прав (ТЗ 8.1, DOC-036960) — seed для `role_permission`
|
||
|
||
| Роль (`role.code`) | DIRECTORY | DOCUMENT | CONSOLIDATION | KFO | EX | EXPORT | USERS | AUDIT |
|
||
|---|---|---|---|---|---|---|---|---|
|
||
| ADMIN | VIEW, EDIT, DELETE, ADMIN (импорт) | — | — | — | VIEW | — | ADMIN | VIEW |
|
||
| CURATOR_GO (Куратор КП ГО) | VIEW, EDIT (ГК: создание, перезакрепление) | — | VIEW (по закреплённым ГК) | VIEW | VIEW | — | — | — |
|
||
| KA_RF | VIEW (свой РФ), EDIT (состав ГСК своей ГК) | VIEW, EDIT (загрузка, правка, подтверждение) | VIEW, EDIT (INITIATED…CONSOLIDATION_RF) | VIEW, EXPORT | VIEW | EXPORT | — | — |
|
||
| RKA (РКА РФ) | VIEW, EDIT (состав ГСК в рамках корректировки) | VIEW | VIEW, EDIT (VERIFICATION_RF) | VIEW, APPROVE | VIEW | EXPORT | — | — |
|
||
| KA_GO | VIEW, EDIT (состав ГСК, без ограничения по филиалу) | VIEW, EDIT | VIEW, EDIT (всё, что KA_RF + CONSOLIDATION_GO) | VIEW, VERIFY (№/дата УОБ), EXPORT | VIEW | EXPORT | — | — |
|
||
| EXPERT | VIEW | VIEW | VIEW | VIEW | VIEW | — | — | — |
|
||
| CLIENT_MANAGER | VIEW (свои клиенты) | VIEW, EDIT (только загрузка по своей ГК) | VIEW (статус) | VIEW | — | — | — | — |
|
||
|
||
Policy-слой (не права): KA_RF/RKA/CLIENT_MANAGER видят только ГК своего `regional_office`; редактирование версии — только в
|
||
допустимом статусе и при отсутствии чужой активной `consolidation_lock`; ЗО (`business_segment`) — фильтр видимости.
|
||
Матрица в ТЗ помечена «будет разработана» (сноска 29) — seed считать черновиком, редактируется администратором в UI.
|
||
|
||
---
|
||
|
||
## §11. Маппинг JSON ИС Сканирование (FNR01/FNR02) → формы
|
||
|
||
Контракт (по ТЗ и фикстурам `aep_*.json`): `file_uid`; `error {code, message} | null`
|
||
(FILE_READ_ERROR, INVALID_FORMAT, INVALID_DOCUMENT, RECOGNITION_FAILED, UNKNOWN_ERROR); `data_ocr[]` по документам:
|
||
`label.form_name` («Бухгалтерский баланс» → BALANCE, «Отчет о финансовых результатах» → INCOME), `organization.full_name`,
|
||
`inn.inn`, `units.measurement`, `date`, `table_structure.rows[]` (`row_index`, `cells[] {column_index, value, confidence}`).
|
||
Строка `rows[0]` — заголовки колонок: cells[0] «Наименование показателя», [1] «Код», [2..] даты колонок.
|
||
`confidence` — в ТЗ доли 0..1, в реальных файлах проценты 0..100 **[открыто]** — нормализовать в `numeric(5,2)` 0–100.
|
||
|
||
Маппинг: `data_ocr[i]` → `document` (BALANCE/INCOME, `locator = pages:…`) + `financial_statement_meta` (ИНН, имя,
|
||
units, report_date, okud) + `financial_statement_column` (из `rows[0].cells[2..]`, `period_date` распарсить из
|
||
«На 30 сентября 2025 г.», `is_current` = первая) + `financial_statement_line` (`cells[1]` код, `cells[0]` имя; только
|
||
строки с кодом) + `financial_statement_value` (число: убрать пробелы/`-`, скобки → минус; `confidence`; `is_recognized`).
|
||
Сверки: ИНН/имя vs `legal_entity` → `document.counterparty_check`; дата vs период версии → `period_check`.
|
||
|
||
---
|
||
|
||
## §12. ББ и ОФР из Excel (tabular-parser, второй тип документа)
|
||
|
||
Источник: ТЗ 6.2 («парсинг ОСВ, бухгалтерского баланса и ОФР (эксель файлов)»), DOC-037734, DOC-221281; формат —
|
||
Приказ 66н / ФСБУ 4/2023, но на практике это и выгрузка из 1С («Регламентированный отчёт»), и шаблон банка
|
||
(`Копия Рекомендуемые формы расшифровок…`, лист `Отчетность` в `Анализ *.xlsx`), и вручную набитая таблица.
|
||
|
||
Принцип тот же, что в §1.0: **структура ищется по содержимому**, результат — тот же `statementDocument` контракта,
|
||
что и у `scan-gateway`, чтобы ядро маппило ББ/ОФР одинаково независимо от источника (`confidence = 100`,
|
||
`is_recognized = true` для табличного пути).
|
||
|
||
1. **Классификация листа**: ячейки с «Бухгалтерский баланс» / «0710001» → BALANCE; «Отчет о финансовых результатах» /
|
||
«Отчет о прибылях и убытках» / «0710002» → INCOME. На одном листе могут быть обе формы подряд (ББ, затем ОФР) —
|
||
разрезать по второму заголовку; в одном файле несколько листов — несколько `document`. Листы ОСВ и форм в одном
|
||
файле — допустимо.
|
||
2. **Колонка кодов строк**: та, где ≥ 60 % непустых значений — четырёхзначные коды из допустимого набора
|
||
(1110–1700 для ББ, 2110–2500 для ОФР, плюс 5xxx для расшифровок). Заголовок «Код» необязателен. Если колонки кодов
|
||
нет — попытаться сопоставить по наименованиям показателей (словарь `rules/statement_lines.yaml`: «Основные средства» →
|
||
1150) с пометкой `line_code_confidence < 100` и warning; агент-аналитик подтвердит.
|
||
3. **Колонка наименований**: ближайшая текстовая слева от кодов; «Пояснения» (1–2 символа/цифры) слева от неё — не
|
||
наименование.
|
||
4. **Колонки-периоды**: ячейки заголовка справа от кодов, матчащие дату («На 31 декабря 2024 г.», «31.12.2024»,
|
||
«2024», «За 9 месяцев 2025 г.», «За январь–сентябрь 2025 г.») → `financial_statement_column` с `period_date`
|
||
(для ОФР — дата конца периода) и `is_current` = самая поздняя. Число колонок — от 1 до 3+. Колонки без даты
|
||
(«Изменение», «%») — в `extra`.
|
||
5. **Значения**: Decimal по правилам §1.2; скобки → минус; «–»/«-»/пусто → NULL. Единица измерения — из шапки
|
||
(«тыс. руб.», «млн руб.», ОКЕИ 384/385) → `header.units`; если не найдена — по порядку величины относительно
|
||
ОСВ той же организации **не угадывать**, оставить NULL (ядро спросит).
|
||
6. **Шапка**: организация, ИНН, дата, ОКПО/ОКОПФ/ОКФС/ОКВЭД — регулярками по области выше таблицы; как в §1.0 п. 6.
|
||
7. **Самопроверка**: формулы §7 по каждой колонке; расхождение > 0 (в единицах формы) → `needs_check` с перечнем
|
||
непройденных соотношений в `metrics.warnings`. Это не замена проверок ядра, а сигнал, что структура угадана неверно
|
||
(например, перепутаны колонки периодов).
|
||
8. Расшифровки к строкам (лист `К балансу`, рекомендуемые формы банка) — в первой очереди **не разбирать**; классифицировать
|
||
как `DISCLOSURE`/`OTHER` и сохранить `locator`.
|
||
|
||
Что это даёт ядру: один маппинг `statementDocument → document + financial_statement_*` для PDF и Excel; экран
|
||
«Проверка распознавания» для Excel-источника показывает слева не страницы, а рендер листа (или просто таблицу) —
|
||
UI-решение вне парсера.
|