Compare commits

..

No commits in common. "bf3d3f12e9d14e12b27558e3576256c81b08f686" and "1d46f8cc6d8dcd1ed7b68a6afa1dbb98fa6c227e" have entirely different histories.

6 changed files with 7 additions and 137 deletions

View File

@ -47,7 +47,6 @@ 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)

View File

@ -31,7 +31,6 @@ 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,
@ -291,32 +290,6 @@ 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

View File

@ -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": false,
"allow_short_lookup": true,
"allow_direct_mapping": true,
"structured_value": true,
"structured_group": "",
@ -1977,7 +1977,7 @@
"join_with_space": false,
"multiplies": "нет",
"required": "необязательно",
"comment": "У блока АдрМестаПриемаВыдача в СведенияКартаИнБанк нет единого тега АдресСтрока (в отличие от одноимённого блока в СведенияВнесениеПолучениеНалДС) — значение собирается из компонентов адреса (см. COL_FOREIGN_CARD_ADDRESS_STRUCTURED в mapping.py), поле может остаться пустым"
"comment": "текстовый показатель, поле может остаться пустым и не передаваться в XML"
},
{
"index": 100,

View File

@ -28,7 +28,7 @@ def test_registry_covers_all_report_columns_without_gaps() -> None:
def test_registry_assigns_card_columns_missing_from_original_plan() -> None:
assert COLUMNS.block("foreign_card") == tuple(range(96, 107))
assert COLUMNS.block("foreign_card") == tuple(range(97, 107))
def test_registry_explicitly_preserves_structured_lookup_compatibility() -> None:
@ -75,14 +75,14 @@ def test_registry_splits_money_and_participant_header_groups() -> None:
(
"own_account_cash",
79,
95,
96,
"Сведения о внесении наличных денежных средств на свой банковский счет "
"или о получении наличных денежных средств со своего банковского счета "
"у одного оператора по переводу денежных средств",
),
(
"foreign_card_operation",
96,
97,
106,
"Сведения об операции с использованием платежной карты иностранного банка",
),

View File

@ -1720,48 +1720,6 @@ 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), из-за чего

View File

@ -2,7 +2,6 @@ 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
@ -359,65 +358,6 @@ 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(
"""