fix сдвиг columns

This commit is contained in:
Raykov-MS 2026-08-28 10:30:20 +03:00
parent 7beeb710ce
commit f49a384e69
5 changed files with 119 additions and 35 deletions

View File

@ -102,13 +102,6 @@ OKATO_COUNTRY_PAIRS: tuple[tuple[int, int], ...] = tuple(
) )
) )
# Текущая перестановка адресного блока при выгрузке в целевой шаблон.
PARTICIPANT_ADDRESS_OUTPUT_SOURCES: dict[int, int] = {
_index(141): _index(142),
**{_index(target): _index(target + 1) for target in range(142, 151)},
_index(151): _index(141),
}
# Бизнес-константы. # Бизнес-константы.
ACCOUNT_PLACEHOLDER = "00000000000000000000" ACCOUNT_PLACEHOLDER = "00000000000000000000"
EXPORT_SUBSIDIARY_COMMENT_CODES = frozenset(str(code) for code in range(6102, 6127)) EXPORT_SUBSIDIARY_COMMENT_CODES = frozenset(str(code) for code in range(6102, 6127))

View File

@ -131,6 +131,7 @@ class StructuredGroupRule:
one_line_columns: tuple[int, ...] one_line_columns: tuple[int, ...]
component_columns: tuple[int, ...] component_columns: tuple[int, ...]
join_with_space: bool = False join_with_space: bool = False
preserve_empty_positions: bool = False
_INVALID_TAGS = {"", "-", "Источник", "путь", "подразумеваются", "тэга", "нашла"} _INVALID_TAGS = {"", "-", "Источник", "путь", "подразумеваются", "тэга", "нашла"}
@ -366,6 +367,7 @@ def _build_structured_group_rules(
one_line_columns=one_line_columns, one_line_columns=one_line_columns,
component_columns=component_columns, component_columns=component_columns,
join_with_space=aggregate.join_with_space, join_with_space=aggregate.join_with_space,
preserve_empty_positions="адрес" in aggregate.name.lower(),
) )
) )
return tuple(sorted(rules, key=lambda rule: rule.structured_column)) return tuple(sorted(rules, key=lambda rule: rule.structured_column))
@ -541,14 +543,18 @@ def _apply_structured_group_rules(row: dict[int, str]) -> None:
_set(row, component_column_index, "") _set(row, component_column_index, "")
continue continue
values = [ component_values = [
_get(row, component_column_index).strip() _get(row, component_column_index).strip()
for component_column_index in rule.component_columns for component_column_index in rule.component_columns
if _get(row, component_column_index).strip()
] ]
if not values: if not any(component_values):
continue continue
values = (
component_values
if rule.preserve_empty_positions
else [value for value in component_values if value]
)
separator = " " if rule.join_with_space else ", " separator = " " if rule.join_with_space else ", "
_set(row, rule.structured_column, separator.join(values)) _set(row, rule.structured_column, separator.join(values))
@ -687,7 +693,7 @@ def _format_operation_documents(lookup: _PayloadLookup) -> str:
document.get(field_name, "") for field_name in _OPERATION_DOCUMENT_FIELDS document.get(field_name, "") for field_name in _OPERATION_DOCUMENT_FIELDS
] ]
fields.insert(1, "") fields.insert(1, "")
formatted_documents.append(f"{position}, {', '.join(fields)};") formatted_documents.append(f"{position}. {', '.join(fields)};")
return "\n".join(formatted_documents) return "\n".join(formatted_documents)

View File

@ -11,12 +11,7 @@ from tempfile import NamedTemporaryFile
import xlsxwriter import xlsxwriter
from .column_constants import ( from .column_constants import COL_KPP, COL_PARTICIPANT_TYPE, PARTICIPANT_TYPE_UL
COL_KPP,
COL_PARTICIPANT_TYPE,
PARTICIPANT_ADDRESS_OUTPUT_SOURCES,
PARTICIPANT_TYPE_UL,
)
from .column_registry import COLUMNS from .column_registry import COLUMNS
from .mapping import FIXED_REPORT_COLUMNS, build_fixed_row_by_index from .mapping import FIXED_REPORT_COLUMNS, build_fixed_row_by_index
@ -136,10 +131,7 @@ class StreamingReportWriter:
column_index: int, column_index: int,
row_by_index: dict[int, str], row_by_index: dict[int, str],
) -> object: ) -> object:
source_index = PARTICIPANT_ADDRESS_OUTPUT_SOURCES.get( return row_by_index.get(column_index, "")
column_index, column_index
)
return row_by_index.get(source_index, "")
def save(self, destination: Path) -> None: def save(self, destination: Path) -> None:
destination.parent.mkdir(parents=True, exist_ok=True) destination.parent.mkdir(parents=True, exist_ok=True)

View File

@ -117,7 +117,7 @@ def test_build_fixed_row_splits_pipe_separated_xml_tag_values() -> None:
"УчастникФЛИП.СведФЛИП.АдрРег.Индекс": "674500", "УчастникФЛИП.СведФЛИП.АдрРег.Индекс": "674500",
}, },
) )
assert row["Адрес по структуре (целый)"] == "674500" assert row["Адрес по структуре (целый)"] == "674500, , , , , , , , "
def test_extract_tag_candidate_keys_supports_comma_separator() -> None: def test_extract_tag_candidate_keys_supports_comma_separator() -> None:
@ -201,7 +201,9 @@ def test_structured_address_is_built_from_right_columns() -> None:
"УчастникФЛИП.СведФЛИП.АдрРег.Дом": "1", "УчастникФЛИП.СведФЛИП.АдрРег.Дом": "1",
}, },
) )
assert row["Адрес по структуре (целый)"] == "674500, 643, Москва, Тверская, 1" assert (
row["Адрес по структуре (целый)"] == "674500, 643, , , Москва, Тверская, 1, , "
)
def test_one_line_address_clears_structured_and_component_columns() -> None: def test_one_line_address_clears_structured_and_component_columns() -> None:
@ -589,7 +591,7 @@ def test_foreign_structure_founder_address_populates_eio_address_block() -> None
) )
assert row[167] == "" assert row[167] == ""
assert row[168] == "101000, 643, Москва" assert row[168] == "101000, 643, , , Москва, , , , "
assert row[169] == "101000" assert row[169] == "101000"
assert row[170] == "643" assert row[170] == "643"
assert row[173] == "Москва" assert row[173] == "Москва"
@ -848,9 +850,9 @@ def test_operation_documents_keep_positions_and_join_multiple_documents() -> Non
) )
assert row[107] == ( assert row[107] == (
"1, 13, , , 29/01/2026, 253, Размещение денежных средств;\n" "1. 13, , , 29/01/2026, 253, Размещение денежных средств;\n"
"2, 99, , Депозитный договор, 29/01/2026, Р202307/0001/93/Э, ;\n" "2. 99, , Депозитный договор, 29/01/2026, Р202307/0001/93/Э, ;\n"
"3, 99, , Общие условия размещения депозитов, 17/11/2020, Р202307/0001, ;" "3. 99, , Общие условия размещения депозитов, 17/11/2020, Р202307/0001, ;"
) )
@ -871,7 +873,7 @@ def test_structured_cash_address_does_not_include_bank_fields() -> None:
}, },
participant_fields={}, participant_fields={},
) )
assert row[50] == "675520, 643, 10, Чигири с, Зеленая ул, 1" assert row[50] == "675520, 643, 10, , Чигири с, Зеленая ул, 1, , "
assert "044525111" not in row[50] assert "044525111" not in row[50]
assert "АО РСХБ" not in row[50] assert "АО РСХБ" not in row[50]
@ -892,11 +894,100 @@ def test_participant_structured_address_stays_in_142_and_components_143_plus() -
}, },
) )
assert row[141] == "" assert row[141] == ""
assert row[142] == "123456, 643, 45, Москва, Тверская, 1" assert row[142] == "123456, 643, 45, , Москва, Тверская, 1, , "
assert row[143] == "123456" assert row[143] == "123456"
assert row[144] == "643" assert row[144] == "643"
def test_shagidova_address_keeps_missing_component_positions() -> None:
row = build_fixed_row_by_index(
file_name="SKO115FZ_01_044525111_20260810_000001.xml",
record_id="R1",
operation_index=1,
operation_fields={"НомерОперация": "ЦФТ-БАНК_1361449333033"},
participant_fields={
"УчастникФЛИП.СведФЛИП.АдрРег.Индекс": "367014",
"УчастникФЛИП.СведФЛИП.АдрРег.КодОКСМ": "643",
"УчастникФЛИП.СведФЛИП.АдрРег.КодСубъектаПоОКАТО": "82",
"УчастникФЛИП.СведФЛИП.АдрРег.Пункт": "Махачкала г.",
"УчастникФЛИП.СведФЛИП.АдрРег.Улица": "Гаджиева А ул",
"УчастникФЛИП.СведФЛИП.АдрРег.Дом": "12 Ж",
"УчастникФЛИП.СведФЛИП.АдрРег.Оф": "29",
},
)
assert row[142] == ("367014, 643, 82, , Махачкала г., Гаджиева А ул, 12 Ж, , 29")
assert tuple(row[index] for index in range(143, 152)) == (
"367014",
"643",
"82",
"",
"Махачкала г.",
"Гаджиева А ул",
"12 Ж",
"",
"29",
)
def test_participant_full_address_keeps_all_nine_components() -> None:
components = {
"Индекс": "367014",
"КодОКСМ": "643",
"КодСубъектаПоОКАТО": "82",
"Район": "Советский р",
"Пункт": "Махачкала г.",
"Улица": "Гаджиева А ул",
"Дом": "12 Ж",
"Корп": "2",
"Оф": "29",
}
row = build_fixed_row_by_index(
file_name="f.xml",
record_id="R1",
operation_index=1,
operation_fields={},
participant_fields={
f"УчастникФЛИП.СведФЛИП.АдрРег.{tag}": value
for tag, value in components.items()
},
)
assert row[142] == ", ".join(components.values())
assert tuple(row[index] for index in range(143, 152)) == tuple(components.values())
def test_eio_address_keeps_missing_component_positions() -> None:
row = build_fixed_row_by_index(
file_name="f.xml",
record_id="R1",
operation_index=1,
operation_fields={},
participant_fields={
"УчастникЮЛ.СведЕИО.ФЛЕИО.АдрРег.Индекс": "367014",
"УчастникЮЛ.СведЕИО.ФЛЕИО.АдрРег.КодОКСМ": "643",
"УчастникЮЛ.СведЕИО.ФЛЕИО.АдрРег.КодСубъектаПоОКАТО": "82",
"УчастникЮЛ.СведЕИО.ФЛЕИО.АдрРег.Пункт": "Махачкала г.",
"УчастникЮЛ.СведЕИО.ФЛЕИО.АдрРег.Улица": "Гаджиева А ул",
"УчастникЮЛ.СведЕИО.ФЛЕИО.АдрРег.Дом": "12 Ж",
"УчастникЮЛ.СведЕИО.ФЛЕИО.АдрРег.Оф": "29",
},
)
assert row[168] == ("367014, 643, 82, , Махачкала г., Гаджиева А ул, 12 Ж, , 29")
assert tuple(row[index] for index in range(169, 178)) == (
"367014",
"643",
"82",
"",
"Махачкала г.",
"Гаджиева А ул",
"12 Ж",
"",
"29",
)
def test_participant_address_does_not_use_citizenship_without_address_block() -> None: def test_participant_address_does_not_use_citizenship_without_address_block() -> None:
row = build_fixed_row_by_index( row = build_fixed_row_by_index(
file_name="f.xml", file_name="f.xml",
@ -930,7 +1021,7 @@ def test_participant_address_country_is_isolated_from_citizenship() -> None:
) )
assert row[126] == "762" assert row[126] == "762"
assert row[142] == "123456, 643, Москва" assert row[142] == "123456, 643, , , Москва, , , , "
assert row[144] == "643" assert row[144] == "643"

View File

@ -229,7 +229,7 @@ def test_write_report_restores_leading_zeros_for_bik_inn_kpp(tmp_path: Path) ->
assert sheet.cell(row=3, column=kpp_col).value == "032601001" assert sheet.cell(row=3, column=kpp_col).value == "032601001"
def test_write_report_shifts_participant_address_block_to_expected_columns( def test_write_report_preserves_participant_address_column_positions(
tmp_path: Path, tmp_path: Path,
) -> None: ) -> None:
destination = tmp_path / "report.xlsx" destination = tmp_path / "report.xlsx"
@ -260,12 +260,14 @@ def test_write_report_shifts_participant_address_block_to_expected_columns(
workbook = load_workbook(destination) workbook = load_workbook(destination)
sheet = workbook["Отчет"] sheet = workbook["Отчет"]
assert sheet.cell(row=3, column=141).value == ( assert sheet.cell(row=3, column=141).value == "г. Благовещенск"
assert sheet.cell(row=3, column=142).value == (
"675520, 643, 10, Благовещенский р-н, Чигири с, Зеленая ул, 1" "675520, 643, 10, Благовещенский р-н, Чигири с, Зеленая ул, 1"
) )
assert sheet.cell(row=3, column=142).value == "675520" assert sheet.cell(row=3, column=143).value == "675520"
assert sheet.cell(row=3, column=143).value == "643" assert sheet.cell(row=3, column=144).value == "643"
assert sheet.cell(row=3, column=144).value == "10" assert sheet.cell(row=3, column=145).value == "10"
assert sheet.cell(row=3, column=146).value == "Благовещенский р"
def test_write_report_does_not_pad_physical_person_identification_as_kpp( def test_write_report_does_not_pad_physical_person_identification_as_kpp(