Fix column 99: assemble foreign card address from structured components
The АдрМестаПриемаВыдача block under СведенияКартаИнБанк (code 1009) has no single АдресСтрока tag, unlike the same-named block under СведенияВнесениеПолучениеНалДС — column 99 was mapped directly onto that nonexistent tag and stayed empty. It now assembles the value from the individual address components (Индекс, КодОКСМ, КодСубъектаПоОКАТО, Район, Пункт, Улица, Дом, Корп, Оф), the same way column 153 already does for registration place. Also disabled allow_short_lookup for this column, since it was previously able to pick up a same-named tag (e.g. Улица) from an unrelated address elsewhere in the file. Added regression tests at both the mapping level and a full parse_xml_content -> build_fixed_row_by_index pipeline level with a synthetic code-1009 XML.
This commit is contained in:
parent
4e11db322d
commit
34bb0f3d29
@ -47,6 +47,7 @@ COL_TRANSFER_STATUS = _index(62)
|
||||
|
||||
# Карты и место операции.
|
||||
COL_TERRITORY_CODE_CARD = _index(96)
|
||||
COL_FOREIGN_CARD_ADDRESS_STRUCTURED = _index(99)
|
||||
COL_CARD_HOLDER_INFO = _index(103)
|
||||
COL_EMPLOYEE_SIGN = _index(104)
|
||||
COL_FOREIGN_BANK_NAME = _index(105)
|
||||
|
||||
@ -31,6 +31,7 @@ from .column_constants import (
|
||||
COL_EXTRA_INFO,
|
||||
COL_FILE_NAME,
|
||||
COL_FOREIGN_BANK_NAME,
|
||||
COL_FOREIGN_CARD_ADDRESS_STRUCTURED,
|
||||
COL_FTR_SIGN,
|
||||
COL_IDENT_FL,
|
||||
COL_INN,
|
||||
@ -290,6 +291,32 @@ def _build_rules(mapping_data: tuple[ColumnMeta, ...]) -> tuple[MappingRule, ...
|
||||
candidates = [
|
||||
f"{base_path}.{tag}" for base_path in base_paths for tag in address_tags
|
||||
]
|
||||
elif index == COL_FOREIGN_CARD_ADDRESS_STRUCTURED:
|
||||
# legacy_099: адрес места приёма/выдачи по карте иностранного
|
||||
# банка (код 1009) приходит в XML разложенным по тем же тегам
|
||||
# АдрМестаПриемаВыдача, что и другие адресные блоки (см.
|
||||
# COL_REG_PLACE_STRUCTURED выше) — АдресСтрока как единого поля
|
||||
# для этого блока в схеме нет, поэтому колонку нужно собирать
|
||||
# из компонентов, а не мапить напрямую на несуществующий тег.
|
||||
base_paths = (
|
||||
"СведенияКартаИнБанк.СведМестоОперация.АдрМестаПриемаВыдача",
|
||||
"СообщОперКО.ИнформЧасть.СведКО.Операция.СведенияКартаИнБанк."
|
||||
"СведМестоОперация.АдрМестаПриемаВыдача",
|
||||
)
|
||||
address_tags = (
|
||||
"Индекс",
|
||||
"КодОКСМ",
|
||||
"КодСубъектаПоОКАТО",
|
||||
"Район",
|
||||
"Пункт",
|
||||
"Улица",
|
||||
"Дом",
|
||||
"Корп",
|
||||
"Оф",
|
||||
)
|
||||
candidates = [
|
||||
f"{base_path}.{tag}" for base_path in base_paths for tag in address_tags
|
||||
]
|
||||
elif index == COL_CP_ISSUER_INN:
|
||||
candidates = [
|
||||
candidate
|
||||
|
||||
@ -1965,10 +1965,10 @@
|
||||
"column_name": "Адрес места приема (выдачи) наличных денежных средств (по структуре полный)",
|
||||
"block": "foreign_card",
|
||||
"report_group": "foreign_card_operation",
|
||||
"xml_tag": "АдрМестаПриемаВыдача",
|
||||
"xml_path": "СообщОперКО/ИнформЧасть/СведКО/Операция/СведенияКартаИнБанк/СведМестоОперация/АдрМестаПриемаВыдача/АдресСтрока",
|
||||
"xml_tag": "Индекс|КодОКСМ|КодСубъектаПоОКАТО|Район|Пункт|Улица|Дом|Корп|Оф",
|
||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/СведенияКартаИнБанк/СведМестоОперация/АдрМестаПриемаВыдача/",
|
||||
"source_scope": "any",
|
||||
"allow_short_lookup": true,
|
||||
"allow_short_lookup": false,
|
||||
"allow_direct_mapping": true,
|
||||
"structured_value": true,
|
||||
"structured_group": "",
|
||||
@ -1977,7 +1977,7 @@
|
||||
"join_with_space": false,
|
||||
"multiplies": "нет",
|
||||
"required": "необязательно",
|
||||
"comment": "текстовый показатель, поле может остаться пустым и не передаваться в XML"
|
||||
"comment": "У блока АдрМестаПриемаВыдача в СведенияКартаИнБанк нет единого тега АдресСтрока (в отличие от одноимённого блока в СведенияВнесениеПолучениеНалДС) — значение собирается из компонентов адреса (см. COL_FOREIGN_CARD_ADDRESS_STRUCTURED в mapping.py), поле может остаться пустым"
|
||||
},
|
||||
{
|
||||
"index": 100,
|
||||
|
||||
@ -1720,6 +1720,48 @@ def test_col99_foreign_card_address_header_renamed() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_col99_foreign_card_address_assembled_from_structured_components() -> None:
|
||||
"""Регрессия: у блока АдрМестаПриемаВыдача в СведенияКартаИнБанк (код
|
||||
1009) нет единого тега АдресСтрока, в отличие от одноимённого блока в
|
||||
СведенияВнесениеПолучениеНалДС — колонка 99 раньше мапилась напрямую на
|
||||
несуществующий АдресСтрока и оставалась пустой. Значение должно
|
||||
собираться из компонентов адреса, разложенных в XML по структуре."""
|
||||
row = build_fixed_row_by_index(
|
||||
file_name="f.xml",
|
||||
record_id="R1",
|
||||
operation_index=1,
|
||||
operation_fields={
|
||||
"КодОперации": "1009",
|
||||
"СведенияКартаИнБанк.СведМестоОперация.АдрМестаПриемаВыдача.КодОКСМ": "840",
|
||||
"СведенияКартаИнБанк.СведМестоОперация.АдрМестаПриемаВыдача.Пункт": "Нью-Йорк",
|
||||
"СведенияКартаИнБанк.СведМестоОперация.АдрМестаПриемаВыдача.Улица": "Уолл-стрит",
|
||||
"СведенияКартаИнБанк.СведМестоОперация.АдрМестаПриемаВыдача.Дом": "11",
|
||||
},
|
||||
participant_fields={},
|
||||
)
|
||||
|
||||
assert row[99] == "840, Нью-Йорк, Уолл-стрит, 11"
|
||||
|
||||
|
||||
def test_col99_foreign_card_address_not_leaked_from_participant_address() -> None:
|
||||
"""Регрессия: allow_short_lookup для колонки 99 должен быть выключен —
|
||||
иначе адрес участника операции (другой блок, тот же тег 'Улица') может
|
||||
случайно попасть в адрес места приёма/выдачи по карте ин. банка."""
|
||||
row = build_fixed_row_by_index(
|
||||
file_name="f.xml",
|
||||
record_id="R1",
|
||||
operation_index=1,
|
||||
operation_fields={
|
||||
"КодОперации": "1009",
|
||||
},
|
||||
participant_fields={
|
||||
"УчастникЮЛ.СведЮЛ.АдрРегЮЛ.Улица": "Совсем другая улица",
|
||||
},
|
||||
)
|
||||
|
||||
assert row[99] == ""
|
||||
|
||||
|
||||
def test_col96_territory_code_card_belongs_to_foreign_card_group() -> None:
|
||||
"""Регрессия: колонка 96 раньше входила в группу заголовков блока
|
||||
'своих' кассовых операций (cash_operation/own_account_cash), из-за чего
|
||||
|
||||
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from app.pipeline.mapping import build_fixed_row_by_index
|
||||
from app.pipeline.parser import _extract_direct_fields, parse_xml_content
|
||||
|
||||
|
||||
@ -358,6 +359,65 @@ def test_mixed_operation_and_eio_repeats_are_additive_not_cartesian() -> None:
|
||||
assert result.rows[0].operation_fields["ОснованиеОп.НомДок"] == "BASE-1; BASE-2"
|
||||
|
||||
|
||||
def test_1009_foreign_card_address_survives_full_pipeline() -> None:
|
||||
"""Сквозная проверка на синтетическом файле: реальный XML -> parse_xml_content
|
||||
-> build_fixed_row_by_index. Адрес места приёма/выдачи по карте
|
||||
иностранного банка (код 1009) должен собраться из компонентов
|
||||
АдрМестаПриемаВыдача в колонку 99, а не потеряться и не подхватить
|
||||
чужой адрес участника операции (тот же тег 'Улица' в другом блоке)."""
|
||||
xml = _base_xml(
|
||||
"""
|
||||
<Операция>
|
||||
<ИдентификаторЗаписи>OP1</ИдентификаторЗаписи>
|
||||
<КодОперации>1009</КодОперации>
|
||||
<СведенияКартаИнБанк>
|
||||
<НомерКарта>5536********1234</НомерКарта>
|
||||
<НаимИнБанк>Bank of Testland</НаимИнБанк>
|
||||
<СведМестоОперация>
|
||||
<ИдТерминал>ATM00042</ИдТерминал>
|
||||
<НаимКО>First Test Bank</НаимКО>
|
||||
<АдрМестаПриемаВыдача>
|
||||
<Индекс>10001</Индекс>
|
||||
<КодОКСМ>840</КодОКСМ>
|
||||
<Пункт>Нью-Йорк</Пункт>
|
||||
<Улица>Уолл-стрит</Улица>
|
||||
<Дом>11</Дом>
|
||||
</АдрМестаПриемаВыдача>
|
||||
</СведМестоОперация>
|
||||
</СведенияКартаИнБанк>
|
||||
<УчастникОп>
|
||||
<Тип>01</Тип>
|
||||
<УчастникЮЛ>
|
||||
<СведЮЛ>
|
||||
<АдрРегЮЛ>
|
||||
<Улица>Совсем другая улица участника</Улица>
|
||||
</АдрРегЮЛ>
|
||||
</СведЮЛ>
|
||||
</УчастникЮЛ>
|
||||
</УчастникОп>
|
||||
</Операция>
|
||||
"""
|
||||
)
|
||||
|
||||
result = parse_xml_content("SKO115FZ_01_123456789_20260616_X00001.xml", xml)
|
||||
assert not result.is_fatal
|
||||
assert len(result.rows) == 1
|
||||
|
||||
row = result.rows[0]
|
||||
fixed_row = build_fixed_row_by_index(
|
||||
file_name="SKO115FZ_01_123456789_20260616_X00001.xml",
|
||||
record_id=row.record_id,
|
||||
operation_index=1,
|
||||
operation_fields=row.operation_fields,
|
||||
participant_fields=row.participant_fields,
|
||||
)
|
||||
|
||||
assert fixed_row[97] == "ATM00042"
|
||||
assert fixed_row[98] == "840"
|
||||
assert fixed_row[99] == "10001, 840, Нью-Йорк, Уолл-стрит, 11"
|
||||
assert fixed_row[100] == "First Test Bank"
|
||||
|
||||
|
||||
def test_repeated_scalar_leaves_are_joined_without_continuation_rows() -> None:
|
||||
operation = ET.fromstring(
|
||||
"""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user