Fix Errors
This commit is contained in:
parent
44f23947a3
commit
1d46f8cc6d
@ -64,7 +64,6 @@ from .column_constants import (
|
|||||||
COL_SUSPENSION_BASIS,
|
COL_SUSPENSION_BASIS,
|
||||||
COL_SUSPICIOUS_ID,
|
COL_SUSPICIOUS_ID,
|
||||||
COL_TERRITORY_CODE,
|
COL_TERRITORY_CODE,
|
||||||
COL_TERRITORY_CODE_CARD,
|
|
||||||
COL_TRANSFER_STATUS,
|
COL_TRANSFER_STATUS,
|
||||||
COL_TRANSFER_TYPE,
|
COL_TRANSFER_TYPE,
|
||||||
COL_UNUSUAL_CODES,
|
COL_UNUSUAL_CODES,
|
||||||
@ -864,11 +863,11 @@ def _apply_business_rules(
|
|||||||
_rule_item_type(row, codes)
|
_rule_item_type(row, codes)
|
||||||
_rule_territory_codes(row, codes)
|
_rule_territory_codes(row, codes)
|
||||||
|
|
||||||
_rule_payer_account(row, transfer_type)
|
_rule_payer_account(row, transfer_type, codes)
|
||||||
_rule_esp_identifiers(row, operation_sign)
|
_rule_esp_identifiers(row, operation_sign)
|
||||||
_rule_payer_bank(row, operator_type)
|
_rule_payer_bank(row, operator_type)
|
||||||
_rule_recipient_bank(row, operator_type)
|
_rule_recipient_bank(row, operator_type)
|
||||||
_rule_correspondent_accounts(row, operator_type, transfer_type)
|
_rule_correspondent_accounts(row, operator_type, transfer_type, codes)
|
||||||
_rule_recipient_account(row, transfer_type)
|
_rule_recipient_account(row, transfer_type)
|
||||||
_rule_transfer_status(row, transfer_type)
|
_rule_transfer_status(row, transfer_type)
|
||||||
|
|
||||||
@ -978,14 +977,32 @@ def _rule_item_type(row: dict[int, str], codes: set[str]) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _rule_territory_codes(row: dict[int, str], codes: set[str]) -> None:
|
def _rule_territory_codes(row: dict[int, str], codes: set[str]) -> None:
|
||||||
"""Правила 36 и 96: код территории только для операции 5016."""
|
"""Правило 36: код территории (КодТерИнГос) — только для операции 5016.
|
||||||
|
|
||||||
|
Колонка 96 (КодТерИнБанк, признак 96) сюда НЕ относится — это другой
|
||||||
|
одноимённый показатель официальной спецификации (п.59), обязательный
|
||||||
|
для блока СведенияКартаИнБанк (код операции 1009), а не для 5016.
|
||||||
|
Раньше обе колонки ошибочно чистились по одному условию (TERRITORY_CODE
|
||||||
|
not in codes), из-за чего колонка 96 всегда обнулялась для 1009-строк —
|
||||||
|
именно 5016-код там никогда не бывает. Для колонки 96 отдельная очистка
|
||||||
|
не нужна: движок маппинга и так подставляет в неё значение только тогда,
|
||||||
|
когда в XML реально есть СведенияКартаИнБанк/КодТерИнБанк.
|
||||||
|
"""
|
||||||
if TERRITORY_CODE not in codes:
|
if TERRITORY_CODE not in codes:
|
||||||
_set(row, COL_TERRITORY_CODE, "")
|
_set(row, COL_TERRITORY_CODE, "")
|
||||||
_set(row, COL_TERRITORY_CODE_CARD, "")
|
|
||||||
|
|
||||||
|
|
||||||
def _rule_payer_account(row: dict[int, str], transfer_type: str) -> None:
|
def _rule_payer_account(
|
||||||
"""Правило 38: счёт плательщика зависит от вида перевода."""
|
row: dict[int, str], transfer_type: str, codes: set[str]
|
||||||
|
) -> None:
|
||||||
|
"""Правило 38: счёт плательщика зависит от вида перевода.
|
||||||
|
|
||||||
|
Для операций с кодом 5016 (п.6 правок) заглушку не подставляем — такого
|
||||||
|
блока (номера счетов) для 5016 в спецификации нет.
|
||||||
|
"""
|
||||||
|
if TERRITORY_CODE in codes:
|
||||||
|
_set(row, COL_PAYER_ACCOUNT, "")
|
||||||
|
return
|
||||||
if transfer_type in {"1", "2", "3", "10"}:
|
if transfer_type in {"1", "2", "3", "10"}:
|
||||||
if not _get(row, COL_PAYER_ACCOUNT):
|
if not _get(row, COL_PAYER_ACCOUNT):
|
||||||
_set(row, COL_PAYER_ACCOUNT, ACCOUNT_PLACEHOLDER)
|
_set(row, COL_PAYER_ACCOUNT, ACCOUNT_PLACEHOLDER)
|
||||||
@ -1014,9 +1031,18 @@ def _rule_recipient_bank(row: dict[int, str], operator_type: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _rule_correspondent_accounts(
|
def _rule_correspondent_accounts(
|
||||||
row: dict[int, str], operator_type: str, transfer_type: str
|
row: dict[int, str], operator_type: str, transfer_type: str, codes: set[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Правила 44-45: корсчета зависят от оператора и вида перевода."""
|
"""Правила 44-45: корсчета зависят от оператора и вида перевода.
|
||||||
|
|
||||||
|
Для операций с кодом 5016 (п.6 правок) заглушку не подставляем — такого
|
||||||
|
блока (номера счетов) для 5016 в спецификации нет.
|
||||||
|
"""
|
||||||
|
if TERRITORY_CODE in codes:
|
||||||
|
_set(row, COL_PAYER_BANK_ACCOUNT, "")
|
||||||
|
_set(row, COL_RECIPIENT_BANK_ACCOUNT, "")
|
||||||
|
return
|
||||||
|
|
||||||
if not operator_type and not transfer_type:
|
if not operator_type and not transfer_type:
|
||||||
_set(row, COL_PAYER_BANK_ACCOUNT, "")
|
_set(row, COL_PAYER_BANK_ACCOUNT, "")
|
||||||
_set(row, COL_RECIPIENT_BANK_ACCOUNT, "")
|
_set(row, COL_RECIPIENT_BANK_ACCOUNT, "")
|
||||||
|
|||||||
@ -948,7 +948,7 @@
|
|||||||
"xml_tag": "ИдТерминал",
|
"xml_tag": "ИдТерминал",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/СведенияПереводыДс/СведПриемНалДС/ИдТерминал",
|
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/СведенияПереводыДс/СведПриемНалДС/ИдТерминал",
|
||||||
"source_scope": "any",
|
"source_scope": "any",
|
||||||
"allow_short_lookup": true,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
"structured_value": false,
|
"structured_value": false,
|
||||||
"structured_group": "",
|
"structured_group": "",
|
||||||
@ -1903,10 +1903,10 @@
|
|||||||
"index": 96,
|
"index": 96,
|
||||||
"column_id": "legacy_096",
|
"column_id": "legacy_096",
|
||||||
"column_name": "Код территории",
|
"column_name": "Код территории",
|
||||||
"block": "cash_operation",
|
"block": "foreign_card",
|
||||||
"report_group": "own_account_cash",
|
"report_group": "foreign_card_operation",
|
||||||
"xml_tag": "КодТерИнГос",
|
"xml_tag": "КодТерИнБанк",
|
||||||
"xml_path": "/СообщОперКО/ИнформЧасть/СведКО/Операция/СведенияВнесениеПолучениеНалДС/КодТерИнГос",
|
"xml_path": "СообщОперКО/ИнформЧасть/СведКО/Операция/СведенияКартаИнБанк/КодТерИнБанк",
|
||||||
"source_scope": "any",
|
"source_scope": "any",
|
||||||
"allow_short_lookup": false,
|
"allow_short_lookup": false,
|
||||||
"allow_direct_mapping": true,
|
"allow_direct_mapping": true,
|
||||||
@ -1917,7 +1917,7 @@
|
|||||||
"join_with_space": false,
|
"join_with_space": false,
|
||||||
"multiplies": "нет",
|
"multiplies": "нет",
|
||||||
"required": "необязательно",
|
"required": "необязательно",
|
||||||
"comment": "Числовой показатель, поле может остаться пустым и не поступать в XML"
|
"comment": "Обязательный показатель для блока СведенияКартаИнБанк (код операции 1009) согласно официальной спецификации (п.59) — код территории иностранного банка, эмитировавшего карту. Ранее был ошибочно замаплен на несуществующий путь СведенияВнесениеПолучениеНалДС/КодТерИнГос (другой одноимённый показатель, п.41, применим только к коду 5016) и относился к блоку cash_operation, из-за чего колонка визуально прилипала к предыдущей группе и всегда была пустой."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"index": 97,
|
"index": 97,
|
||||||
@ -1962,7 +1962,7 @@
|
|||||||
{
|
{
|
||||||
"index": 99,
|
"index": 99,
|
||||||
"column_id": "legacy_099",
|
"column_id": "legacy_099",
|
||||||
"column_name": "Адрес места приема наличных денежных средств (по структуре полный)",
|
"column_name": "Адрес места приема (выдачи) наличных денежных средств (по структуре полный)",
|
||||||
"block": "foreign_card",
|
"block": "foreign_card",
|
||||||
"report_group": "foreign_card_operation",
|
"report_group": "foreign_card_operation",
|
||||||
"xml_tag": "АдрМестаПриемаВыдача",
|
"xml_tag": "АдрМестаПриемаВыдача",
|
||||||
@ -3642,7 +3642,7 @@
|
|||||||
{
|
{
|
||||||
"index": 183,
|
"index": 183,
|
||||||
"column_id": "legacy_183",
|
"column_id": "legacy_183",
|
||||||
"column_name": "Номер",
|
"column_name": "Серия",
|
||||||
"block": "eio",
|
"block": "eio",
|
||||||
"report_group": "eio",
|
"report_group": "eio",
|
||||||
"xml_tag": "СерияДок",
|
"xml_tag": "СерияДок",
|
||||||
@ -4079,4 +4079,4 @@
|
|||||||
"required": "обязательно",
|
"required": "обязательно",
|
||||||
"comment": "Данные могут оставаться пустыми"
|
"comment": "Данные могут оставаться пустыми"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -43,6 +43,39 @@ TOP_GROUP_HEADERS: tuple[GroupHeader, ...] = tuple(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_WIDE_TEXT_KEYWORDS = (
|
||||||
|
"Наименование",
|
||||||
|
"Адрес",
|
||||||
|
"Назначение платежа",
|
||||||
|
"Доп. сведения",
|
||||||
|
"Дополнительн",
|
||||||
|
"Сведения",
|
||||||
|
"Иные сведения",
|
||||||
|
"ФИО",
|
||||||
|
"Место государственной регистрации",
|
||||||
|
"Орган, выдавший документ",
|
||||||
|
"Сведения миграционной карты",
|
||||||
|
)
|
||||||
|
_NARROW_KEYWORDS = (
|
||||||
|
"Код",
|
||||||
|
"БИК",
|
||||||
|
"ИНН",
|
||||||
|
"ОГРН",
|
||||||
|
"КПП",
|
||||||
|
"Индекс",
|
||||||
|
"Дом",
|
||||||
|
"Корпус",
|
||||||
|
"Офис",
|
||||||
|
"Квартира",
|
||||||
|
"Серия",
|
||||||
|
"Тип",
|
||||||
|
"Признак",
|
||||||
|
"ОКАТО",
|
||||||
|
"Валюта",
|
||||||
|
"СВИФТ",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StreamingReportWriter:
|
class StreamingReportWriter:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.columns = list(FIXED_REPORT_COLUMNS)
|
self.columns = list(FIXED_REPORT_COLUMNS)
|
||||||
@ -74,12 +107,29 @@ class StreamingReportWriter:
|
|||||||
)
|
)
|
||||||
self._date_format = self.workbook.add_format({"num_format": "DD.MM.YYYY"})
|
self._date_format = self.workbook.add_format({"num_format": "DD.MM.YYYY"})
|
||||||
self._sum_format = self.workbook.add_format({"num_format": "#,##0.00"})
|
self._sum_format = self.workbook.add_format({"num_format": "#,##0.00"})
|
||||||
|
# Дефолтный формат для обычных текстовых ячеек — раньше такие ячейки
|
||||||
|
# писались вообще без формата (cell_format is None), поэтому перенос
|
||||||
|
# текста применялся только к шапке, а не к данным.
|
||||||
|
self._text_format = self.workbook.add_format(
|
||||||
|
{"text_wrap": True, "valign": "top"}
|
||||||
|
)
|
||||||
self._column_formats = [self._resolve_format(column) for column in self.columns]
|
self._column_formats = [self._resolve_format(column) for column in self.columns]
|
||||||
self._build_group_header_row()
|
self._build_group_header_row()
|
||||||
|
# В режиме constant_memory строка "уходит" на диск сразу после того,
|
||||||
|
# как начата следующая — поэтому set_row для строки 0 нужно вызывать
|
||||||
|
# ДО построения строки 1, иначе вызов молча ничего не сделает.
|
||||||
|
self.sheet.set_row(0, 22)
|
||||||
self._build_header_row()
|
self._build_header_row()
|
||||||
for column_index, column_format in enumerate(self._column_formats):
|
self.sheet.set_row(1, 46)
|
||||||
if column_format is self._date_format:
|
for column_index, column_name in enumerate(self.columns):
|
||||||
self.sheet.set_column(column_index, column_index, 12)
|
column_format = self._column_formats[column_index]
|
||||||
|
self.sheet.set_column(
|
||||||
|
column_index,
|
||||||
|
column_index,
|
||||||
|
self._resolve_column_width(column_name, column_format),
|
||||||
|
)
|
||||||
|
# Шапка из двух строк уже настроена выше (до перехода на следующую
|
||||||
|
# строку — см. комментарий про constant_memory).
|
||||||
# Фиксируем обе строки шапки.
|
# Фиксируем обе строки шапки.
|
||||||
self.sheet.freeze_panes(2, 0)
|
self.sheet.freeze_panes(2, 0)
|
||||||
self.sheet.autofilter(1, 0, 1, len(self.columns) - 1)
|
self.sheet.autofilter(1, 0, 1, len(self.columns) - 1)
|
||||||
@ -170,7 +220,22 @@ class StreamingReportWriter:
|
|||||||
return self._date_format
|
return self._date_format
|
||||||
if "Сумм" in column_name or "Сумма" in column_name:
|
if "Сумм" in column_name or "Сумма" in column_name:
|
||||||
return self._sum_format
|
return self._sum_format
|
||||||
return None
|
return self._text_format
|
||||||
|
|
||||||
|
def _resolve_column_width(self, column_name: str, column_format) -> float:
|
||||||
|
if column_format is self._date_format:
|
||||||
|
return 12
|
||||||
|
if column_format is self._sum_format:
|
||||||
|
return 14
|
||||||
|
if "Номер счета" in column_name:
|
||||||
|
return 20
|
||||||
|
if "Номер платежной карты" in column_name or "Номер банкомата" in column_name:
|
||||||
|
return 16
|
||||||
|
if any(keyword in column_name for keyword in _WIDE_TEXT_KEYWORDS):
|
||||||
|
return 32
|
||||||
|
if any(keyword in column_name for keyword in _NARROW_KEYWORDS):
|
||||||
|
return 11
|
||||||
|
return 14
|
||||||
|
|
||||||
def _normalize_cell_value(
|
def _normalize_cell_value(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@ -441,7 +441,8 @@ def test_operator_type_conditions_clear_irrelevant_bank_fields() -> None:
|
|||||||
assert row["Номер счета банка получателя"] == ""
|
assert row["Номер счета банка получателя"] == ""
|
||||||
|
|
||||||
|
|
||||||
def test_col96_territory_code_cleared_when_operation_is_not_5016() -> None:
|
def test_col36_territory_code_5016_cleared_when_operation_is_not_5016() -> None:
|
||||||
|
"""Правило 36: КодТерИнГос (п.41 спецификации) — только для кода 5016."""
|
||||||
row = build_fixed_row(
|
row = build_fixed_row(
|
||||||
file_name="f.xml",
|
file_name="f.xml",
|
||||||
record_id="R1",
|
record_id="R1",
|
||||||
@ -452,7 +453,26 @@ def test_col96_territory_code_cleared_when_operation_is_not_5016() -> None:
|
|||||||
},
|
},
|
||||||
participant_fields={},
|
participant_fields={},
|
||||||
)
|
)
|
||||||
assert row["Код территории"] == ""
|
assert row["Код территории (5016)"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_col96_territory_code_card_filled_for_1009_regardless_of_5016() -> None:
|
||||||
|
"""Правило 59 спецификации: КодТерИнБанк — для кода 1009 (карта ин. банка),
|
||||||
|
не зависит от того, есть ли в записи код 5016. Раньше колонка ошибочно
|
||||||
|
чистилась тем же условием, что и колонка 36 (код 5016), и всегда
|
||||||
|
оставалась пустой для 1009-записей."""
|
||||||
|
row = build_fixed_row(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={
|
||||||
|
"КодОперации": "1009",
|
||||||
|
"СведенияКартаИнБанк.КодТерИнБанк": "410",
|
||||||
|
},
|
||||||
|
participant_fields={},
|
||||||
|
)
|
||||||
|
assert row["Код территории"] == "410"
|
||||||
|
assert row["Код территории (5016)"] == ""
|
||||||
|
|
||||||
|
|
||||||
def test_col103_and_105_fill_info_absent_when_no_employee_and_empty() -> None:
|
def test_col103_and_105_fill_info_absent_when_no_employee_and_empty() -> None:
|
||||||
@ -1602,3 +1622,115 @@ def test_col119_is_not_included_in_structured_fio_or_cleared_by_one_line_fio() -
|
|||||||
)
|
)
|
||||||
assert row_with_one_line_fio[119] == ""
|
assert row_with_one_line_fio[119] == ""
|
||||||
assert row_with_one_line_fio[122] == ""
|
assert row_with_one_line_fio[122] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_col48_atm_number_not_leaked_from_foreign_card_block() -> None:
|
||||||
|
"""Регрессия: legacy_048 (AV, cash_receipt) раньше подхватывал ИдТерминал
|
||||||
|
из совсем другого блока (СведенияКартаИнБанк, foreign_card) через
|
||||||
|
короткий поиск по голому имени тега. Реальный кейс из отчёта заказчика:
|
||||||
|
операция 1009, ИдТерминал только в СведенияКартаИнБанк.СведМестоОперация.
|
||||||
|
"""
|
||||||
|
row = build_fixed_row_by_index(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={
|
||||||
|
"КодОперации": "1009",
|
||||||
|
"СведенияКартаИнБанк.СведМестоОперация.ИдТерминал": "S1AM9302",
|
||||||
|
},
|
||||||
|
participant_fields={},
|
||||||
|
)
|
||||||
|
assert row[97] == "S1AM9302" # CS, foreign_card — должно быть тут
|
||||||
|
assert row[48] == "" # AV, cash_receipt — тут быть не должно
|
||||||
|
assert row[82] == "" # CD, cash_operation — тоже не должно
|
||||||
|
|
||||||
|
|
||||||
|
def test_col38_44_45_payer_accounts_blank_for_5016() -> None:
|
||||||
|
"""Правка п.6: для кода операции 5016 номера счетов (плательщика и
|
||||||
|
корсчетов) не подставляются заглушкой из нулей — такого блока для 5016
|
||||||
|
в спецификации нет. Номер счёта получателя (46), если он реально пришёл
|
||||||
|
в XML, не трогаем."""
|
||||||
|
row = build_fixed_row_by_index(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={
|
||||||
|
"КодОперации": "5016",
|
||||||
|
"ВидПереводДС": "1",
|
||||||
|
"ТипОператорДС": "2",
|
||||||
|
"НомерСчетПолучатель": "40817810106240020211",
|
||||||
|
},
|
||||||
|
participant_fields={},
|
||||||
|
)
|
||||||
|
assert row[38] == "" # Номер счета плательщика
|
||||||
|
assert row[44] == "" # Номер счета банка плательщика
|
||||||
|
assert row[45] == "" # Номер счета банка получателя
|
||||||
|
assert row[46] == "40817810106240020211" # реальные данные не трогаем
|
||||||
|
|
||||||
|
|
||||||
|
def test_col38_44_payer_accounts_still_get_placeholder_without_5016() -> None:
|
||||||
|
"""Убеждаемся, что правка п.6 не задела остальные виды перевода —
|
||||||
|
заглушка по-прежнему подставляется, если 5016 нет в кодах операции."""
|
||||||
|
row = build_fixed_row_by_index(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={
|
||||||
|
"КодОперации": "5007",
|
||||||
|
"ВидПереводДС": "1",
|
||||||
|
"ТипОператорДС": "2",
|
||||||
|
},
|
||||||
|
participant_fields={},
|
||||||
|
)
|
||||||
|
assert row[38] == "00000000000000000000"
|
||||||
|
assert row[44] == "00000000000000000000"
|
||||||
|
|
||||||
|
|
||||||
|
def test_col183_eio_series_column_named_correctly() -> None:
|
||||||
|
"""Регрессия: колонка 183 (СерияДок ЕИО/Бенефициара) раньше называлась
|
||||||
|
"Номер", хотя реально тянет серию документа — при этом соседняя, 184-я,
|
||||||
|
тоже называлась "Номер" (и действительно тянет номер). Проверяем и
|
||||||
|
заголовок, и что данные при этом были и остаются на своих местах."""
|
||||||
|
assert FIXED_REPORT_COLUMNS[182] == "Серия"
|
||||||
|
assert FIXED_REPORT_COLUMNS[183] == "Номер"
|
||||||
|
|
||||||
|
row = build_fixed_row_by_index(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={},
|
||||||
|
participant_fields={
|
||||||
|
"УчастникЮЛ.БенефициарЮЛ.ФЛБенефициар.СведДокУдЛичн.СерияДок": "4018",
|
||||||
|
"УчастникЮЛ.БенефициарЮЛ.ФЛБенефициар.СведДокУдЛичн.НомДок": "044762",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert row[183] == "4018" # Серия
|
||||||
|
assert row[184] == "044762" # Номер
|
||||||
|
|
||||||
|
|
||||||
|
def test_col99_foreign_card_address_header_renamed() -> None:
|
||||||
|
"""Заказчик попросил переименовать заголовок колонки 99 (адрес по
|
||||||
|
структуре, блок СведенияКартаИнБанк, код 1009) — было "Адрес места
|
||||||
|
приема наличных денежных средств (по структуре полный)", стало с учётом
|
||||||
|
того, что операция может быть как приёмом, так и выдачей наличных."""
|
||||||
|
assert (
|
||||||
|
FIXED_REPORT_COLUMNS[98]
|
||||||
|
== "Адрес места приема (выдачи) наличных денежных средств "
|
||||||
|
"(по структуре полный)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_col96_territory_code_card_belongs_to_foreign_card_group() -> None:
|
||||||
|
"""Регрессия: колонка 96 раньше входила в группу заголовков блока
|
||||||
|
'своих' кассовых операций (cash_operation/own_account_cash), из-за чего
|
||||||
|
визуально 'прилипала' не к своему блоку. Теперь должна входить в ту же
|
||||||
|
report_group, что и соседние колонки блока СведенияКартаИнБанк (97+)."""
|
||||||
|
from app.pipeline.column_registry import COLUMNS
|
||||||
|
|
||||||
|
columns_by_index = {meta.index: meta for meta in COLUMNS.ordered_columns()}
|
||||||
|
assert columns_by_index[96].block == "foreign_card"
|
||||||
|
assert columns_by_index[96].report_group == columns_by_index[97].report_group
|
||||||
|
|
||||||
|
groups = COLUMNS.report_groups()
|
||||||
|
foreign_card_group = next(g for g in groups if g.start_index <= 97 <= g.end_index)
|
||||||
|
assert foreign_card_group.start_index == 96
|
||||||
|
|||||||
@ -45,7 +45,7 @@ def test_write_report_streaming_preserves_header_and_formats(tmp_path: Path) ->
|
|||||||
assert sheet.cell(row=3, column=sum_col_idx).number_format == "#,##0.00"
|
assert sheet.cell(row=3, column=sum_col_idx).number_format == "#,##0.00"
|
||||||
|
|
||||||
|
|
||||||
def test_write_report_expands_only_date_columns(tmp_path: Path) -> None:
|
def test_write_report_expands_date_and_other_columns(tmp_path: Path) -> None:
|
||||||
destination = tmp_path / "report.xlsx"
|
destination = tmp_path / "report.xlsx"
|
||||||
write_report([], destination)
|
write_report([], destination)
|
||||||
|
|
||||||
@ -56,7 +56,11 @@ def test_write_report_expands_only_date_columns(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
assert date_column_letter in sheet.column_dimensions
|
assert date_column_letter in sheet.column_dimensions
|
||||||
assert sheet.column_dimensions[date_column_letter].width >= 12
|
assert sheet.column_dimensions[date_column_letter].width >= 12
|
||||||
assert "A" not in sheet.column_dimensions
|
# Теперь ширина задаётся не только у дат — у всех колонок, по группам
|
||||||
|
# (узкие коды/ИНН/БИК, широкие текстовые поля вроде наименований банков
|
||||||
|
# и адресов, остальные — среднего размера).
|
||||||
|
assert "A" in sheet.column_dimensions
|
||||||
|
assert sheet.column_dimensions["A"].width >= 12
|
||||||
|
|
||||||
|
|
||||||
def test_write_report_contains_top_group_header_row(tmp_path: Path) -> None:
|
def test_write_report_contains_top_group_header_row(tmp_path: Path) -> None:
|
||||||
@ -359,6 +363,44 @@ def test_write_report_keeps_only_repeat_fields_on_continuation_row(
|
|||||||
assert sheet.cell(row=4, column=45).value in (None, "")
|
assert sheet.cell(row=4, column=45).value in (None, "")
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_report_wraps_text_in_data_cells(tmp_path: Path) -> None:
|
||||||
|
destination = tmp_path / "report.xlsx"
|
||||||
|
rows = [
|
||||||
|
ReportRow(
|
||||||
|
file_name="SKO115FZ_01_123456789_20260616_X00001.xml",
|
||||||
|
record_id="RID-1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={},
|
||||||
|
participant_fields={},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
write_report(rows, destination)
|
||||||
|
|
||||||
|
workbook = load_workbook(destination)
|
||||||
|
sheet = workbook["Отчет"]
|
||||||
|
file_name_cell = sheet.cell(row=3, column=1)
|
||||||
|
assert file_name_cell.alignment.wrap_text is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_report_widens_bank_name_and_address_columns(tmp_path: Path) -> None:
|
||||||
|
destination = tmp_path / "report.xlsx"
|
||||||
|
write_report([], destination)
|
||||||
|
|
||||||
|
workbook = load_workbook(destination)
|
||||||
|
sheet = workbook["Отчет"]
|
||||||
|
bank_col_idx = FIXED_REPORT_COLUMNS.index("Наименование банка плательщика") + 1
|
||||||
|
bank_col_letter = get_column_letter(bank_col_idx)
|
||||||
|
inn_col_idx = next(
|
||||||
|
index
|
||||||
|
for index, name in enumerate(FIXED_REPORT_COLUMNS, start=1)
|
||||||
|
if "ИНН" in name
|
||||||
|
)
|
||||||
|
inn_col_letter = get_column_letter(inn_col_idx)
|
||||||
|
|
||||||
|
assert sheet.column_dimensions[bank_col_letter].width >= 28
|
||||||
|
assert sheet.column_dimensions[inn_col_letter].width < 20
|
||||||
|
|
||||||
|
|
||||||
def _column_letter(index: int) -> str:
|
def _column_letter(index: int) -> str:
|
||||||
value = index
|
value = index
|
||||||
result = ""
|
result = ""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user