ОСВ: реквизит «Контрагенты.ИНН» и в шапке варианта 1 (#32)
Строка блока заголовка «Контрагенты.ИНН» под «Контрагенты» — тот же реквизит, что элемент списка субконто варианта 2: в group_schema не попадает, у владельца has_inn, строки склеиваются glue_inn_attributes. Общая часть вариантов — _attach_inn_attribute и _positions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bb78f81094
commit
02ad8be018
@ -172,8 +172,8 @@ ParseOutcome`. Структура ОСВ ищется **по содержимо
|
||||
`COUNTERPARTY`; договоры и прочее не трогаются. Правила — `osv_headers.yaml`: `counterparty_inn_suffix`, `inn_only_row`,
|
||||
`inn_kpp_cell`.
|
||||
|
||||
**Реквизит «Контрагенты.ИНН»** (OQ-162, #32): в шапке варианта 2 элемент `<группировка>.ИНН` («субконто: Контрагенты,
|
||||
Контрагенты.ИНН») — не группировка, а реквизит: в `group_schema` его нет, у владельца `has_inn = true`
|
||||
**Реквизит «Контрагенты.ИНН»** (OQ-162, #32): элемент `<группировка>.ИНН` в шапке — в варианте 2 («субконто: Контрагенты,
|
||||
Контрагенты.ИНН») или строкой блока заголовка в варианте 1 — не группировка, а реквизит: в `group_schema` его нет, у владельца `has_inn = true`
|
||||
(`header.GroupSchema.positions` держит его позицию для классификатора). 1С выводит ИНН строкой под контрагентом, с теми же
|
||||
суммами; `classify.glue_inn_attributes` отдаёт ИНН контрагенту (извлечение — как из колонки ИНН, R-24), строку убирает,
|
||||
её детей поднимает на уровень; предупреждение «ИНН … отнесён к контрагенту в строке …». Пустая ячейка (физлицо без ИНН) —
|
||||
|
||||
@ -481,16 +481,6 @@ def _first_group_name(sheet: Sheet, header: TableHeader) -> str | None:
|
||||
return sheet.text_at(header.group_label_rows[0], header.text_col) or None
|
||||
|
||||
|
||||
def _variant1_groups(sheet: Sheet, header: TableHeader, rules: Rules) -> list[OsvGroup]:
|
||||
groups: list[OsvGroup] = []
|
||||
for index, row in enumerate(header.group_label_rows):
|
||||
label = sheet.text_at(row, header.text_col)
|
||||
# Первая группировка — всегда ACCOUNT, как бы она ни называлась («Счет», «Подразделение», «Субконто»)
|
||||
group_type: GroupType = "ACCOUNT" if index == 0 else rules.groups.classify(label)
|
||||
groups.append(_group(group_type, label, has_inn=_row_has_inn(sheet, row, rules)))
|
||||
return groups or [_group("ACCOUNT")]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class InnAttribute:
|
||||
"""Реквизит ИНН группировки контрагентов («Контрагенты.ИНН», OQ-162): 1С выводит его строкой под строкой
|
||||
@ -524,32 +514,58 @@ def _inn_owner(item: str, groups: list[OsvGroup], rules: Rules) -> int | None:
|
||||
return None
|
||||
|
||||
|
||||
def _attach_inn_attribute(item: str, groups: list[OsvGroup], slots: list[int | InnAttribute], rules: Rules) -> bool:
|
||||
"""`item` — реквизит ИНН одной из предыдущих группировок контрагентов: владелец получает `has_inn`, позиция строк —
|
||||
`InnAttribute`; `True`. Иначе ничего не меняется, `False`."""
|
||||
owner = _inn_owner(item, groups, rules)
|
||||
if owner is None:
|
||||
return False
|
||||
groups[owner] = _group("COUNTERPARTY", groups[owner].name, has_inn=True)
|
||||
slots.append(InnAttribute(item))
|
||||
return True
|
||||
|
||||
|
||||
def _positions(groups: list[OsvGroup], slots: list[int | InnAttribute]) -> list[OsvGroup | InnAttribute]:
|
||||
"""Позиции строк: индекс группировки в `groups` или реквизит ИНН (OQ-162)."""
|
||||
return [slot if isinstance(slot, InnAttribute) else groups[slot] for slot in slots]
|
||||
|
||||
|
||||
def _variant1_groups(sheet: Sheet, header: TableHeader, rules: Rules) -> GroupSchema:
|
||||
groups: list[OsvGroup] = []
|
||||
slots: list[int | InnAttribute] = []
|
||||
for index, row in enumerate(header.group_label_rows):
|
||||
label = sheet.text_at(row, header.text_col)
|
||||
if index > 0 and _attach_inn_attribute(label, groups, slots, rules):
|
||||
continue
|
||||
# Первая группировка — всегда ACCOUNT, как бы она ни называлась («Счет», «Подразделение», «Субконто»)
|
||||
group_type: GroupType = "ACCOUNT" if index == 0 else rules.groups.classify(label)
|
||||
groups.append(_group(group_type, label, has_inn=_row_has_inn(sheet, row, rules)))
|
||||
slots.append(len(groups) - 1)
|
||||
if not groups:
|
||||
groups, slots = [_group("ACCOUNT")], [0]
|
||||
return GroupSchema(groups, _positions(groups, slots), 1)
|
||||
|
||||
|
||||
def _variant2_groups(sheet: Sheet, header: TableHeader, rules: Rules, items: list[str]) -> GroupSchema:
|
||||
groups = [_group("ACCOUNT", _first_group_name(sheet, header))]
|
||||
# Позиция строки: индекс группировки в `groups` или реквизит ИНН (OQ-162)
|
||||
slots: list[int | InnAttribute] = [0]
|
||||
inn_pending = header.inn_col is not None
|
||||
for item in items:
|
||||
owner = _inn_owner(item, groups, rules)
|
||||
if owner is not None:
|
||||
groups[owner] = _group("COUNTERPARTY", groups[owner].name, has_inn=True)
|
||||
slots.append(InnAttribute(item))
|
||||
if _attach_inn_attribute(item, groups, slots, rules):
|
||||
continue
|
||||
group_type = rules.groups.classify(item)
|
||||
has_inn = inn_pending and group_type == "COUNTERPARTY"
|
||||
inn_pending = inn_pending and not has_inn
|
||||
groups.append(_group(group_type, item, has_inn=has_inn))
|
||||
slots.append(len(groups) - 1)
|
||||
positions = [slot if isinstance(slot, InnAttribute) else groups[slot] for slot in slots]
|
||||
return GroupSchema(groups, positions, 2)
|
||||
return GroupSchema(groups, _positions(groups, slots), 2)
|
||||
|
||||
|
||||
def build_group_levels(sheet: Sheet, header: TableHeader, rules: Rules) -> GroupSchema:
|
||||
"""Группировки (первая — ACCOUNT), позиции строк и вариант шапки (1 — строки блока, 2 — «субконто: …»)."""
|
||||
items = _subkonto_items(_head_cells(sheet, header), rules)
|
||||
if items is None:
|
||||
groups = _variant1_groups(sheet, header, rules)
|
||||
return GroupSchema(groups, list(groups), 1)
|
||||
return _variant1_groups(sheet, header, rules)
|
||||
return _variant2_groups(sheet, header, rules, items)
|
||||
|
||||
|
||||
|
||||
@ -179,3 +179,35 @@ def test_unrecognised_inn_text_is_not_glued(rules: Rules) -> None:
|
||||
assert not ok
|
||||
assert "нерез." in _values(document)
|
||||
assert any("не распознан" in warning for warning in warnings), warnings
|
||||
|
||||
|
||||
# --- Шапка варианта 1: группировки — строки блока заголовка («Счет / Контрагенты / Контрагенты.ИНН») ---
|
||||
|
||||
|
||||
def _variant1_sheet(*rows: list[object]) -> Sheet:
|
||||
head: list[list[object]] = [
|
||||
["ООО Зерновая"],
|
||||
["Оборотно-сальдовая ведомость по счету 60 за 9 месяцев 2025 г."],
|
||||
[],
|
||||
["Выводимые данные: БУ (данные бухгалтерского учета)"],
|
||||
[],
|
||||
["Счет", M("Сальдо на начало периода", 2), M("Обороты за период", 2), M("Сальдо на конец периода", 2)],
|
||||
["Контрагенты", "Дебет", "Кредит", "Дебет", "Кредит", "Дебет", "Кредит"],
|
||||
["Контрагенты.ИНН"],
|
||||
]
|
||||
return sheet_from_rows([*head, *rows])
|
||||
|
||||
|
||||
def test_variant1_inn_attribute_row_is_glued(rules: Rules) -> None:
|
||||
sheet = _variant1_sheet(r(0, "60.01", A), r(1, "А2 БИЗНЕС ООО", A), r(2, 2370012036, A), [L(0), "Итого", *A])
|
||||
|
||||
document, _, ok = _parse(sheet, rules)
|
||||
|
||||
assert document.header.header_variant == 1
|
||||
assert [(g.type, g.name, g.has_inn) for g in document.group_schema] == [
|
||||
("ACCOUNT", "Счет", None),
|
||||
("COUNTERPARTY", "Контрагенты", True),
|
||||
]
|
||||
assert ok
|
||||
assert _values(document) == ["60.01", "А2 БИЗНЕС ООО", "Итого"]
|
||||
assert document.rows[1].counterparty_inn == "2370012036"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user