fix errors

This commit is contained in:
Raykov-MS 2026-08-25 14:56:20 +03:00
parent 7aeca7a45a
commit 2e570fb386
6 changed files with 107 additions and 10 deletions

View File

@ -50,6 +50,7 @@ COL_TERRITORY_CODE_CARD = _index(96)
COL_CARD_HOLDER_INFO = _index(103)
COL_EMPLOYEE_SIGN = _index(104)
COL_FOREIGN_BANK_NAME = _index(105)
COL_OPERATION_DOCUMENTS = _index(107)
# Участник.
COL_PARTICIPANT_TYPE = _index(109)

View File

@ -39,6 +39,7 @@ from .column_constants import (
COL_METAL_CODE,
COL_METAL_NAME,
COL_OPERATION_CODE,
COL_OPERATION_DOCUMENTS,
COL_OPERATION_SIGN,
COL_OPERATOR_TYPE,
COL_PARTICIPANT_TYPE,
@ -133,6 +134,17 @@ class StructuredGroupRule:
_INVALID_TAGS = {"", "-", "Источник", "путь", "подразумеваются", "тэга", "нашла"}
_INDEXED_PATH_SEGMENT = re.compile(r"\[\d+\]")
_OPERATION_DOCUMENT_FIELDS = (
"КодДок",
"ИноеНаимДок",
"ДатаДок",
"НомДок",
"СодДок",
)
_OPERATION_DOCUMENT_FIELD = re.compile(
r"(?:^|\.)ОснованиеОп(?:\[(?P<index>\d+)\])?\."
r"(?P<field>КодДок|ИноеНаимДок|ДатаДок|НомДок|СодДок)$"
)
_EIO_BLOCK_NAMES = (
"СведЕИО",
"БенефициарЮЛ",
@ -453,6 +465,11 @@ def build_fixed_row_by_index(
rule.compiled_candidates,
allow_short_lookup=allow_short_lookup,
)
_set(
row,
COL_OPERATION_DOCUMENTS,
_format_operation_documents(operation_lookup),
)
if not is_continuation:
_apply_participant_identity_rules(row, participant_lookup)
# Сначала убираем запрещённые однострочные адреса, чтобы структурная
@ -629,6 +646,35 @@ def _lookup_pick_direct_value(lookup: _PayloadLookup, field_name: str) -> str:
return str(value).strip() if value else ""
def _format_operation_documents(lookup: _PayloadLookup) -> str:
indexed_documents: dict[int, dict[str, str]] = {}
single_document: dict[str, str] = {}
for entry in lookup.entries:
match = _OPERATION_DOCUMENT_FIELD.search(entry.raw_path)
if match is None:
continue
field_name = match.group("field")
value = str(entry.value).strip()
document_index = match.group("index")
if document_index is None:
single_document[field_name] = value
continue
indexed_documents.setdefault(int(document_index), {})[field_name] = value
documents = (
[indexed_documents[index] for index in sorted(indexed_documents)]
if indexed_documents
else [single_document] if single_document else []
)
formatted_documents = []
for position, document in enumerate(documents, start=1):
fields = [
document.get(field_name, "") for field_name in _OPERATION_DOCUMENT_FIELDS
]
formatted_documents.append(f"{position}. {', '.join(fields)}")
return "; ".join(formatted_documents)
def _apply_participant_identity_rules(
row: dict[int, str], participant_lookup: _PayloadLookup
) -> None:

View File

@ -2125,12 +2125,12 @@
"column_name": "Сведения о документе, подтверждающем совершение операции",
"block": "operation_basis",
"report_group": "participant_base",
"xml_tag": "СодДок",
"xml_path": "СообщОперКО/ИнформЧасть/СведКО/Операция/ОснованиеОп/СодДок",
"source_scope": "any",
"allow_short_lookup": true,
"xml_tag": "КодДок|ИноеНаимДок|ДатаДок|НомДок|СодДок",
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/ОснованиеОп/",
"source_scope": "operation",
"allow_short_lookup": false,
"allow_direct_mapping": true,
"structured_value": false,
"structured_value": true,
"structured_group": "",
"structured_role": "",
"structured_order": 0,
@ -3265,12 +3265,12 @@
"column_name": "Иные сведения ЕИО/Бенефициара",
"block": "eio",
"report_group": "eio",
"xml_tag": "",
"xml_path": "",
"source_scope": "any",
"allow_short_lookup": true,
"xml_tag": "СНИЛСФЛ|ПолисНомер|ТелефонНомер",
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/СНИЛСФЛ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/СНИЛСФЛ | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ПолисНомер | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ПолисНомер | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/СведЕИО/ФЛЕИО/ТелефонНомер | /СообщОперКО/ИнформЧасть/СведКО/Операция/УчастникОП/УчастникЮЛ/БенефициарЮЛ/ФЛБенефициар/ТелефонНомер",
"source_scope": "participant",
"allow_short_lookup": false,
"allow_direct_mapping": true,
"structured_value": false,
"structured_value": true,
"structured_group": "",
"structured_role": "",
"structured_order": 0,

View File

@ -201,9 +201,13 @@ def _build_expanded_rows(
def _collapse_indexed_fields(fields: dict[str, str]) -> dict[str, str]:
"""Сохраняет все операционные повторы в одной строке без потери значений."""
collapsed: dict[str, str] = {}
indexed_operation_documents: dict[str, str] = {}
for key, value in fields.items():
if re.match(r"^ОснованиеОп\[\d+\]\.", key):
indexed_operation_documents[key] = value
deindexed_key = re.sub(r"\[\d+\]", "", key)
_append_value(collapsed, deindexed_key, value)
collapsed.update(indexed_operation_documents)
return collapsed

View File

@ -825,6 +825,35 @@ def test_col9_uses_operation_number_from_xml() -> None:
assert row[9] == "ЦФТ-БАНК_1177326777530"
def test_operation_documents_keep_positions_and_join_multiple_documents() -> None:
row = build_fixed_row_by_index(
file_name="f.xml",
record_id="R1",
operation_index=1,
operation_fields={
"ОснованиеОп[0].КодДок": "13",
"ОснованиеОп[0].ДатаДок": "29/01/2026",
"ОснованиеОп[0].НомДок": "253",
"ОснованиеОп[0].СодДок": "Размещение денежных средств",
"ОснованиеОп[1].КодДок": "99",
"ОснованиеОп[1].ИноеНаимДок": "Депозитный договор",
"ОснованиеОп[1].ДатаДок": "29/01/2026",
"ОснованиеОп[1].НомДок": "Р202307/0001/93/Э",
"ОснованиеОп[2].КодДок": "99",
"ОснованиеОп[2].ИноеНаимДок": "Общие условия размещения депозитов",
"ОснованиеОп[2].ДатаДок": "17/11/2020",
"ОснованиеОп[2].НомДок": "Р202307/0001",
},
participant_fields={},
)
assert row[107] == (
"1. 13, , 29/01/2026, 253, Размещение денежных средств; "
"2. 99, Депозитный договор, 29/01/2026, Р202307/0001/93/Э, ; "
"3. 99, Общие условия размещения депозитов, 17/11/2020, Р202307/0001, "
)
def test_structured_cash_address_does_not_include_bank_fields() -> None:
row = build_fixed_row_by_index(
file_name="f.xml",
@ -1090,6 +1119,21 @@ def test_eio_block_is_empty_when_eio_and_beneficiary_are_absent() -> None:
assert row[index] == ""
def test_eio_other_info_combines_snils_and_phone() -> None:
row = build_fixed_row_by_index(
file_name="f.xml",
record_id="R1",
operation_index=1,
operation_fields={},
participant_fields={
"УчастникЮЛ.СведЕИО.ФЛЕИО.СНИЛСФЛ": "123-456-789 00",
"УчастникЮЛ.СведЕИО.ФЛЕИО.ТелефонНомер": "+7 999 123-45-67",
},
)
assert row[164] == "123-456-789 00, +7 999 123-45-67"
def test_participant_and_beneficiary_inn_are_scoped_regardless_of_field_order() -> None:
own_inn = ("УчастникЮЛ.СведЮЛ.ИННЮЛ", "2820000210")
beneficiary_fields = (

View File

@ -324,6 +324,8 @@ def test_operation_repeats_are_collapsed_for_single_participant() -> None:
assert len(result.rows) == 1
assert result.rows[0].operation_fields["ОснованиеОп.НомДок"] == "BASE-1; BASE-2"
assert result.rows[0].operation_fields["ОснованиеОп[0].НомДок"] == "BASE-1"
assert result.rows[0].operation_fields["ОснованиеОп[1].НомДок"] == "BASE-2"
def test_mixed_operation_and_eio_repeats_are_additive_not_cartesian() -> None: