ОСВ: реквизит «Контрагенты.ИНН» и в шапке варианта 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`,
|
`COUNTERPARTY`; договоры и прочее не трогаются. Правила — `osv_headers.yaml`: `counterparty_inn_suffix`, `inn_only_row`,
|
||||||
`inn_kpp_cell`.
|
`inn_kpp_cell`.
|
||||||
|
|
||||||
**Реквизит «Контрагенты.ИНН»** (OQ-162, #32): в шапке варианта 2 элемент `<группировка>.ИНН` («субконто: Контрагенты,
|
**Реквизит «Контрагенты.ИНН»** (OQ-162, #32): элемент `<группировка>.ИНН` в шапке — в варианте 2 («субконто: Контрагенты,
|
||||||
Контрагенты.ИНН») — не группировка, а реквизит: в `group_schema` его нет, у владельца `has_inn = true`
|
Контрагенты.ИНН») или строкой блока заголовка в варианте 1 — не группировка, а реквизит: в `group_schema` его нет, у владельца `has_inn = true`
|
||||||
(`header.GroupSchema.positions` держит его позицию для классификатора). 1С выводит ИНН строкой под контрагентом, с теми же
|
(`header.GroupSchema.positions` держит его позицию для классификатора). 1С выводит ИНН строкой под контрагентом, с теми же
|
||||||
суммами; `classify.glue_inn_attributes` отдаёт ИНН контрагенту (извлечение — как из колонки ИНН, R-24), строку убирает,
|
суммами; `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
|
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)
|
@dataclass(frozen=True)
|
||||||
class InnAttribute:
|
class InnAttribute:
|
||||||
"""Реквизит ИНН группировки контрагентов («Контрагенты.ИНН», OQ-162): 1С выводит его строкой под строкой
|
"""Реквизит ИНН группировки контрагентов («Контрагенты.ИНН», OQ-162): 1С выводит его строкой под строкой
|
||||||
@ -524,32 +514,58 @@ def _inn_owner(item: str, groups: list[OsvGroup], rules: Rules) -> int | None:
|
|||||||
return 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:
|
def _variant2_groups(sheet: Sheet, header: TableHeader, rules: Rules, items: list[str]) -> GroupSchema:
|
||||||
groups = [_group("ACCOUNT", _first_group_name(sheet, header))]
|
groups = [_group("ACCOUNT", _first_group_name(sheet, header))]
|
||||||
# Позиция строки: индекс группировки в `groups` или реквизит ИНН (OQ-162)
|
|
||||||
slots: list[int | InnAttribute] = [0]
|
slots: list[int | InnAttribute] = [0]
|
||||||
inn_pending = header.inn_col is not None
|
inn_pending = header.inn_col is not None
|
||||||
for item in items:
|
for item in items:
|
||||||
owner = _inn_owner(item, groups, rules)
|
if _attach_inn_attribute(item, groups, slots, rules):
|
||||||
if owner is not None:
|
|
||||||
groups[owner] = _group("COUNTERPARTY", groups[owner].name, has_inn=True)
|
|
||||||
slots.append(InnAttribute(item))
|
|
||||||
continue
|
continue
|
||||||
group_type = rules.groups.classify(item)
|
group_type = rules.groups.classify(item)
|
||||||
has_inn = inn_pending and group_type == "COUNTERPARTY"
|
has_inn = inn_pending and group_type == "COUNTERPARTY"
|
||||||
inn_pending = inn_pending and not has_inn
|
inn_pending = inn_pending and not has_inn
|
||||||
groups.append(_group(group_type, item, has_inn=has_inn))
|
groups.append(_group(group_type, item, has_inn=has_inn))
|
||||||
slots.append(len(groups) - 1)
|
slots.append(len(groups) - 1)
|
||||||
positions = [slot if isinstance(slot, InnAttribute) else groups[slot] for slot in slots]
|
return GroupSchema(groups, _positions(groups, slots), 2)
|
||||||
return GroupSchema(groups, positions, 2)
|
|
||||||
|
|
||||||
|
|
||||||
def build_group_levels(sheet: Sheet, header: TableHeader, rules: Rules) -> GroupSchema:
|
def build_group_levels(sheet: Sheet, header: TableHeader, rules: Rules) -> GroupSchema:
|
||||||
"""Группировки (первая — ACCOUNT), позиции строк и вариант шапки (1 — строки блока, 2 — «субконто: …»)."""
|
"""Группировки (первая — ACCOUNT), позиции строк и вариант шапки (1 — строки блока, 2 — «субконто: …»)."""
|
||||||
items = _subkonto_items(_head_cells(sheet, header), rules)
|
items = _subkonto_items(_head_cells(sheet, header), rules)
|
||||||
if items is None:
|
if items is None:
|
||||||
groups = _variant1_groups(sheet, header, rules)
|
return _variant1_groups(sheet, header, rules)
|
||||||
return GroupSchema(groups, list(groups), 1)
|
|
||||||
return _variant2_groups(sheet, header, rules, items)
|
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 not ok
|
||||||
assert "нерез." in _values(document)
|
assert "нерез." in _values(document)
|
||||||
assert any("не распознан" in warning for warning in warnings), warnings
|
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