Merge pull request 'AURORA-1714: ОСВ по счёту по контрагентам — блоки субсчетов с кодами контрагентов, метки счёта и периода' (#173) from feat/172-osv-by-counterparty-codes into main

This commit is contained in:
aep 2026-10-27 09:56:33 +03:00
commit 1ba50f3c74

View File

@ -101,6 +101,29 @@ r11 Август - Агро ООО | … ← сра
- Организация — по порядку: хвост «План счетов <организация>» (`header_markers.chart_of_accounts`) → первая не-метка - Организация — по порядку: хвост «План счетов <организация>» (`header_markers.chart_of_accounts`) → первая не-метка
шапки вне строки вида субконто → хвост названия ведомости. шапки вне строки вида субконто → хвост названия ведомости.
**Шапка выгрузки «ОСВ по счёту … по контрагентам» (AURORA-1714, решение владельца 27.10.2026; tabular-parser #71).**
Пример — выгрузка 1С «ОСВ по счету 60 по контрагентам» (в файле нет слов «по счету»):
```
r1 MBC2009 | Оборотно-сальдовая ведомость
r2 15 июля 2025(18:40) | За период | 01.01.25..30.06.25 ← метка и значение в разных ячейках
r3 | Счет Поставщика | 60* ← метка и код счёта в разных ячейках
r4 | Счет Клиента | ← метка без значения
r6 Субконто | Начальное сальдо | …
r8 | | Поставщики ← строка вида субконто внутри таблицы
```
- **Счёт шапки.** Метки `Счет Поставщика` / `Счет Клиента` (`header_markers.account`, данные) — как «по счету»; код — в той же
ячейке после метки либо, если там кода нет, **в следующей ячейке строки**. «60*» — все субсчета счёта: код «60» (знак `*`
кодом не является). Метка без значения пропускается; первая метка со значением определяет счёт шапки.
- **Период.** Метка `За период` (`header_markers.period`) со значением в **соседней ячейке** строки; ячейки метки и
значения организацией не считаются. Диапазон дат `01.01.25..30.06.25` (разделитель `..` — наравне с пробелом/«по»;
`periods.yaml`) разбирается как «с 1 января по конец квартала»; год из двух цифр — 20yy (год конца диапазона). Ожидаемо
для примера: 2025, Q2, нарастающий (§1.5); границы 01.01.2025–30.06.2025.
- **Вид субконто в таблице.** Если первая строка данных состоит из одной текстовой ячейки **вне колонки текста** (в колонке
суммы), а текст — известный словарю групп вид субконто (`osv_groups.yaml`: «Поставщики» — синоним `COUNTERPARTY`, как
«Контрагенты»), строка — не данные: она не получает суммы, предупреждение «нечисловое значение в колонке сумм» не
выдаётся, а вид субконто добавляется в `group_schema` после ACCOUNT так же, как строка вида субконто шапки плоской формы
выше (схема из одного ACCOUNT). Текст в колонке текста («Поставщики» как название контрагента) этим правилом не затрагивается.
Нормализация значений шапки в типы: Нормализация значений шапки в типы:
| Текст в шапке | `group_type` | | Текст в шапке | `group_type` |
@ -241,6 +264,41 @@ AURORA-1661; прикладной сценарий валютных строк
«вид субконто в шапке не назван: строки аналитики счёта <код> приняты за контрагентов по плану счетов — проверьте». «вид субконто в шапке не назван: строки аналитики счёта <код> приняты за контрагентов по плану счетов — проверьте».
Риск: физлица и подотчётные на расчётных счетах без названия вида попадут в «Осталось N» сопоставления (OQ-158). Риск: физлица и подотчётные на расчётных счетах без названия вида попадут в «Осталось N» сопоставления (OQ-158).
**Блоки субсчетов с кодами контрагентов (AURORA-1714, решение владельца 27.10.2026; tabular-parser #71).** Выгрузка 1С
«ОСВ по счету 60 по контрагентам» (плоский лист без outline и отступов, счёт — в шапке, см. §1.1) строит таблицу блоками:
```
60.111 ← субсчёт, ячейки сумм пусты
П005482 | 0 | 108000 | 108000 | 0 | 0 | 0 ← код контрагента, суммы есть
ВЕСТ БИР ООО ← название контрагента, сумм нет
ДП87825;З-139/21-1 | 0 | 108000 | … ← договор контрагента, суммы есть
ИТОГ по 60.111 | … суммы блока … ← итог субсчёта
…
ИТОГ | … суммы листа … ← итог листа
```
1. **Строка-счёт с тремя цифрами в сегменте.** Код вида `60.111`, `60.243` — строка-счёт (ACCOUNT, п. 1 выше), **только если
он начинается со счёта шапки** (`60.xxx` при шапке `60`; сегмент — 1–3 цифры). Без счёта шапки, а также для кода, не
начинающегося со счёта шапки, шаблон п. 1 прежний (сегмент — 1–2 цифры или заглавные буквы). Голый числовой код без
точки, не совпадающий со счётом шапки («441», «199»), при известном счёте шапки счётом не считается: это текст строки
аналитики (код контрагента, номер договора).
2. **«ИТОГ по <субсчёт>»** (`subaccount_sums`, данные) — суммы субсчёта: строка-счёт с таким же кодом, у которой **нет ни одной
суммы**, получает шесть сумм из своей строки «ИТОГ по …»; сама строка «ИТОГ по …» отдельной строкой в `rows` не
выдаётся (контракт не меняется). Субсчёт сверяется «сумма детей = родитель» как обычный родитель (§1.4). Наличие
хотя бы одной такой пары включает п. 3–4 для листа.
3. **Код с суммами → название без сумм → договоры.** Внутри блока субсчёта (между строкой-счётом и следующей строкой-счётом
или итогом):
- строка **с суммами, за которой следует строка без единой суммы** — контрагент: `COUNTERPARTY` (вид субконто — «Контрагенты»
или его синоним из шапки/таблицы, §1.1), `row_value` — код как в файле;
- строка **без сумм**, идущая сразу за такой строкой, — название этого контрагента: **склеивается с ним** (как строка ИНН
с наименованием, OQ-163): в `rows` не попадает, текст — в `counterparty_name`; `counterparty_inn` — по общим правилам
(ИНН в файле нет — пустой ИНН штатен);
- остальные строки **с суммами** до следующего контрагента, субсчёта или итога — **договоры** этого контрагента:
`CONTRACT` (§1.3, п. 3: после COUNTERPARTY дочерние продолжают классифицироваться по схеме; вид договоров здесь не
назван в шапке, поэтому тип задаёт сама раскладка), на уровень глубже контрагента. Суммы контрагента = Σ его договоров.
Иерархия листа: субсчёт → контрагент → договор (стратегия каскада `code_blocks`, `OSV_HIERARCHY_BY_CODE_BLOCKS`).
4. **«ИТОГ» — итог листа.** Строка с текстом ровно «ИТОГ» (`total_row_exact`, наравне с «Итого») — TOTAL; таблица кончается
на ней, а не на первой «ИТОГ по …» (та не начинается с «Итого» и итогом листа не является). Сверка «Итого = Σ строк
уровня 0» (§1.4) идёт по субсчетам.
Нормализация кода счёта (ядро, `domain/osv_mapping.py`): `60.1 → 60.01`, `60.01.1 → 60.01.1`, `76.7.1 → 76.07.1` — Нормализация кода счёта (ядро, `domain/osv_mapping.py`): `60.1 → 60.01`, `60.01.1 → 60.01.1`, `76.7.1 → 76.07.1` —
числовой **второй** сегмент (1–3 цифры) дополняется нулём слева до двух цифр, первый и третий сегменты и буквенные числовой **второй** сегмент (1–3 цифры) дополняется нулём слева до двух цифр, первый и третий сегменты и буквенные
(`76.АВ`, `МЦ.04`) остаются как в файле. `account_code` строки хранится как в результате (`account_code_raw`), (`76.АВ`, `МЦ.04`) остаются как в файле. `account_code` строки хранится как в результате (`account_code_raw`),
@ -329,6 +387,11 @@ OQ-131, PR #13 и 2b): парсер заполняет границы `period_st
`is_cumulative = null`, границы по тексту и предупреждение «не с начала года» / «конец диапазона не совпадает с концом `is_cumulative = null`, границы по тексту и предупреждение «не с начала года» / «конец диапазона не совпадает с концом
квартала»: ядро такой документ к версии не привязывает (OQ-131). квартала»: ядро такой документ к версии не привязывает (OQ-131).
**Диапазон дат с точками и год из двух цифр (AURORA-1714, решение владельца 27.10.2026).** «За период | 01.01.25..30.06.25»
(метка в одной ячейке, диапазон в соседней, §1.1) разбирается как «01.01–30.06» по таблице выше: Q2, нарастающий;
разделитель `..` допустим наравне с пробелом и «по». Год из двух цифр в дате (`.25`) — 20yy; берётся год даты конца
диапазона. Год из четырёх цифр, если есть в тексте, имеет приоритет.
`ASSUMPTION(OQ-02)`: парсер определяет `period_quarter` и `is_cumulative` **только по тексту периода** `ASSUMPTION(OQ-02)`: парсер определяет `period_quarter` и `is_cumulative` **только по тексту периода**
(`rules/periods.yaml`, правила идут от специфичных к общим): квартальная ведомость Q2–Q4 считается не нарастающей; (`rules/periods.yaml`, правила идут от специфичных к общим): квартальная ведомость Q2–Q4 считается не нарастающей;
сверку по данным (`Q3.opening == Q1.opening`) выполняет ядро и может переопределить признак. `period_year` — четыре сверку по данным (`Q3.opening == Q1.opening`) выполняет ядро и может переопределить признак. `period_year` — четыре