full rule
This commit is contained in:
parent
55f0312929
commit
eee68ab4e2
@ -95,6 +95,7 @@ def _build_rules(mapping_data: list[dict[str, str | int]]) -> tuple[MappingRule,
|
|||||||
"ФИО ( в одной строке)",
|
"ФИО ( в одной строке)",
|
||||||
}
|
}
|
||||||
for row in mapping_data:
|
for row in mapping_data:
|
||||||
|
index = int(row.get("index", 0))
|
||||||
column = str(row.get("column_name", "")).strip()
|
column = str(row.get("column_name", "")).strip()
|
||||||
tag = str(row.get("xml_tag", "")).strip()
|
tag = str(row.get("xml_tag", "")).strip()
|
||||||
path = str(row.get("xml_path", "")).strip()
|
path = str(row.get("xml_path", "")).strip()
|
||||||
@ -133,7 +134,12 @@ def _build_rules(mapping_data: list[dict[str, str | int]]) -> tuple[MappingRule,
|
|||||||
candidate_keys=tuple(direct_candidates),
|
candidate_keys=tuple(direct_candidates),
|
||||||
source=(
|
source=(
|
||||||
"participant"
|
"participant"
|
||||||
if column in participant_columns or column.startswith("ИНН")
|
if (
|
||||||
|
column in participant_columns
|
||||||
|
or column.startswith("ИНН")
|
||||||
|
or index in range(128, 140)
|
||||||
|
or index in range(180, 193)
|
||||||
|
)
|
||||||
else "any"
|
else "any"
|
||||||
),
|
),
|
||||||
allow_direct_mapping=allow_direct_mapping,
|
allow_direct_mapping=allow_direct_mapping,
|
||||||
@ -338,6 +344,7 @@ def build_fixed_row(
|
|||||||
_apply_structured_group_rules(row)
|
_apply_structured_group_rules(row)
|
||||||
_apply_conditional_rules_1_74(row)
|
_apply_conditional_rules_1_74(row)
|
||||||
_apply_conditional_rules_75_123(row)
|
_apply_conditional_rules_75_123(row)
|
||||||
|
_apply_conditional_rules_124_203(row)
|
||||||
for column in FIXED_REPORT_COLUMNS:
|
for column in FIXED_REPORT_COLUMNS:
|
||||||
row.setdefault(column, "")
|
row.setdefault(column, "")
|
||||||
return row
|
return row
|
||||||
@ -551,3 +558,49 @@ def _apply_conditional_rules_75_123(row: dict[str, str]) -> None:
|
|||||||
# 105. Аналогично для наименования иностранного банка.
|
# 105. Аналогично для наименования иностранного банка.
|
||||||
if _row_value(row, 104) == "0" and not _row_value(row, 105):
|
if _row_value(row, 104) == "0" and not _row_value(row, 105):
|
||||||
_set_row_value(row, 105, "Информация отсутствует")
|
_set_row_value(row, 105, "Информация отсутствует")
|
||||||
|
|
||||||
|
|
||||||
|
def _allow_one_line_address(row: dict[str, str]) -> bool:
|
||||||
|
resident_flag = _row_value(row, 110)
|
||||||
|
client_flag = _row_value(row, 111)
|
||||||
|
if not resident_flag and not client_flag:
|
||||||
|
return True
|
||||||
|
return resident_flag in {"0", "9"} or (resident_flag == "1" and client_flag == "0")
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_conditional_rules_124_203(row: dict[str, str]) -> None:
|
||||||
|
participant_type = _row_value(row, 109)
|
||||||
|
|
||||||
|
# 124. Для ФЛ и ИП показатель отсутствует.
|
||||||
|
if participant_type in {"2", "3"}:
|
||||||
|
_set_row_value(row, 124, "")
|
||||||
|
|
||||||
|
# 125. Для ИП СНИЛС не выводится.
|
||||||
|
if participant_type == "3":
|
||||||
|
insurance_value = _row_value(row, 125)
|
||||||
|
if re.fullmatch(r"\d{3}-?\d{3}-?\d{3}\s?\d{2}", insurance_value):
|
||||||
|
_set_row_value(row, 125, "")
|
||||||
|
|
||||||
|
# 140/151/177. Однострочные адреса только для нерезидента,
|
||||||
|
# неопределенного резидентства или резидента-не клиента.
|
||||||
|
if not _allow_one_line_address(row):
|
||||||
|
_set_row_value(row, 141, "")
|
||||||
|
_set_row_value(row, 152, "")
|
||||||
|
_set_row_value(row, 178, "")
|
||||||
|
|
||||||
|
# Правило КодОКСМ/КодОКАТО в адресных блоках:
|
||||||
|
# ОКАТО заполняется только при российском коде страны 643.
|
||||||
|
okato_country_pairs = (
|
||||||
|
(52, 53),
|
||||||
|
(67, 68),
|
||||||
|
(86, 87),
|
||||||
|
(144, 145),
|
||||||
|
(170, 171),
|
||||||
|
)
|
||||||
|
for country_col, okato_col in okato_country_pairs:
|
||||||
|
if _row_value(row, country_col) != "643":
|
||||||
|
_set_row_value(row, okato_col, "")
|
||||||
|
|
||||||
|
# 162. Для ФЛ показатель отсутствует.
|
||||||
|
if participant_type == "2":
|
||||||
|
_set_row_value(row, 163, "")
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
@ -60,11 +61,11 @@ TOP_GROUP_HEADERS: tuple[GroupHeader, ...] = (
|
|||||||
),
|
),
|
||||||
GroupHeader(
|
GroupHeader(
|
||||||
start_col=87,
|
start_col=87,
|
||||||
end_col=121,
|
end_col=126,
|
||||||
title="Участник операции (базовые сведения)",
|
title="Участник операции (базовые сведения)",
|
||||||
),
|
),
|
||||||
GroupHeader(
|
GroupHeader(
|
||||||
start_col=122,
|
start_col=127,
|
||||||
end_col=154,
|
end_col=154,
|
||||||
title="Участник операции (идентификация, документы, адреса)",
|
title="Участник операции (идентификация, документы, адреса)",
|
||||||
),
|
),
|
||||||
@ -148,6 +149,12 @@ class StreamingReportWriter:
|
|||||||
self.sheet.write_datetime(
|
self.sheet.write_datetime(
|
||||||
self._next_data_row, index, value, cell_format
|
self._next_data_row, index, value, cell_format
|
||||||
)
|
)
|
||||||
|
elif self._is_text_identifier_column(column_name):
|
||||||
|
self.sheet.write_string(
|
||||||
|
self._next_data_row,
|
||||||
|
index,
|
||||||
|
self._normalize_identifier_string(value),
|
||||||
|
)
|
||||||
elif cell_format is not None:
|
elif cell_format is not None:
|
||||||
self.sheet.write(self._next_data_row, index, value, cell_format)
|
self.sheet.write(self._next_data_row, index, value, cell_format)
|
||||||
else:
|
else:
|
||||||
@ -209,6 +216,18 @@ class StreamingReportWriter:
|
|||||||
return raw_value, None
|
return raw_value, None
|
||||||
return raw_value, default_format
|
return raw_value, default_format
|
||||||
|
|
||||||
|
def _is_text_identifier_column(self, column_name: str) -> bool:
|
||||||
|
keywords = ("ИНН", "ОГРН", "КПП", "БИК", "СВИФТ")
|
||||||
|
return any(keyword in column_name for keyword in keywords)
|
||||||
|
|
||||||
|
def _normalize_identifier_string(self, value: object) -> str:
|
||||||
|
if value is None:
|
||||||
|
return ""
|
||||||
|
text = str(value).strip()
|
||||||
|
if re.fullmatch(r"\d+\.0+", text):
|
||||||
|
return text.split(".", maxsplit=1)[0]
|
||||||
|
return text
|
||||||
|
|
||||||
def _parse_date_value(self, value: object) -> datetime | None:
|
def _parse_date_value(self, value: object) -> datetime | None:
|
||||||
if isinstance(value, datetime):
|
if isinstance(value, datetime):
|
||||||
return value
|
return value
|
||||||
|
|||||||
@ -436,3 +436,115 @@ def test_patronymic_supports_typo_tag_ocht() -> None:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert row["Отчество"] == "Каримович"
|
assert row["Отчество"] == "Каримович"
|
||||||
|
|
||||||
|
|
||||||
|
def test_col124_cleared_for_physical_person_and_ip() -> None:
|
||||||
|
row_fl = build_fixed_row(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={"ТипФЛЧастнаяПрактика": "1"},
|
||||||
|
participant_fields={"ТипУчастника": "2"},
|
||||||
|
)
|
||||||
|
assert row_fl["Признак филиала: / Тип ФЛЧП"] == ""
|
||||||
|
|
||||||
|
row_ip = build_fixed_row(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={"ТипФЛЧастнаяПрактика": "1"},
|
||||||
|
participant_fields={"ТипУчастника": "3"},
|
||||||
|
)
|
||||||
|
assert row_ip["Признак филиала: / Тип ФЛЧП"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_col125_snils_cleared_for_ip() -> None:
|
||||||
|
row = build_fixed_row(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={},
|
||||||
|
participant_fields={"ТипУчастника": "3", "СНИЛСФЛ": "123-456-789 00"},
|
||||||
|
)
|
||||||
|
assert row["ОМС/СНИЛС/Номер телефона"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_one_line_address_columns_cleared_when_participant_is_resident_client() -> None:
|
||||||
|
row = build_fixed_row(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={
|
||||||
|
"ПризнУчастника": "1",
|
||||||
|
"ПризнКлиент": "1",
|
||||||
|
"АдресСтрока": "г. Москва, ул. Тестовая, д. 1",
|
||||||
|
},
|
||||||
|
participant_fields={},
|
||||||
|
)
|
||||||
|
assert row["Адрес в одной строке"] == ""
|
||||||
|
assert row["Место государственной регистрации ( в одной строке)"] == ""
|
||||||
|
assert (
|
||||||
|
row["Место государственной регистрации ЕИО/Бенефициара (одной строкой)"] == ""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_okato_cleared_when_country_code_is_not_russia() -> None:
|
||||||
|
row = build_fixed_row(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={
|
||||||
|
"КодОКСМ": "840",
|
||||||
|
"КодСубъектаПоОКАТО": "45",
|
||||||
|
},
|
||||||
|
participant_fields={},
|
||||||
|
)
|
||||||
|
assert row["Код ОКАТО"] == ""
|
||||||
|
assert row["ОКАТО"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_col163_ogrn_eio_benef_cleared_for_physical_person() -> None:
|
||||||
|
row = build_fixed_row(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={"ОГРНИП": "123456789012345"},
|
||||||
|
participant_fields={"ТипУчастника": "2"},
|
||||||
|
)
|
||||||
|
assert row["ОГРН ЕИО/Бенефициара"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_dul_sign_for_legal_entity_does_not_use_operation_doc_fields() -> None:
|
||||||
|
row = build_fixed_row(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={"ДокУдЛичн": "451099", "ТипУчастника": "1"},
|
||||||
|
participant_fields={},
|
||||||
|
)
|
||||||
|
assert row["Признак ДУЛ"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_dul_columns_128_134_keep_expected_positions() -> None:
|
||||||
|
row = build_fixed_row(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="R1",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={},
|
||||||
|
participant_fields={
|
||||||
|
"ВидДокКод": "21",
|
||||||
|
"СерияДок": "0104",
|
||||||
|
"НомДок": "476635",
|
||||||
|
"КемВыданДок": "ОВД",
|
||||||
|
"КодПодр": "222-022",
|
||||||
|
"ДатВыдачиДок": "23/08/2005",
|
||||||
|
"ДокУдЛичн": "1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert row["ДУЛ (структура полностью)"] == "1, 21, 0104, 476635, ОВД, 222-022"
|
||||||
|
assert row["Признак ДУЛ"] == "1"
|
||||||
|
assert row["Код вида документа/Наименование"] == "21"
|
||||||
|
assert row["Серия"] == "0104"
|
||||||
|
assert row["Номер"] == "476635"
|
||||||
|
assert row["Орган, выдавший документ"] == "ОВД"
|
||||||
|
assert row["КП"] == "222-022"
|
||||||
|
|||||||
@ -110,6 +110,31 @@ def test_write_report_keeps_invalid_date_as_text_without_date_format(
|
|||||||
assert cell.number_format != "DD.MM.YYYY"
|
assert cell.number_format != "DD.MM.YYYY"
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_report_writes_inn_like_values_as_text(tmp_path: Path) -> None:
|
||||||
|
destination = tmp_path / "report.xlsx"
|
||||||
|
rows = [
|
||||||
|
ReportRow(
|
||||||
|
file_name="f.xml",
|
||||||
|
record_id="RID",
|
||||||
|
operation_index=1,
|
||||||
|
operation_fields={},
|
||||||
|
participant_fields={"ИННЮЛ": 2820000210.0},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
write_report(rows, destination)
|
||||||
|
|
||||||
|
workbook = load_workbook(destination)
|
||||||
|
sheet = workbook["Отчет"]
|
||||||
|
inn_col_idx = next(
|
||||||
|
index
|
||||||
|
for index, name in enumerate(FIXED_REPORT_COLUMNS, start=1)
|
||||||
|
if "ИНН" in name
|
||||||
|
)
|
||||||
|
cell = sheet.cell(row=3, column=inn_col_idx)
|
||||||
|
assert cell.value == "2820000210"
|
||||||
|
|
||||||
|
|
||||||
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