SFM/tests/unit/test_report.py
2026-08-28 10:30:20 +03:00

369 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
from datetime import date, datetime
from pathlib import Path
from unittest.mock import patch
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
from app.pipeline.column_registry import COLUMNS
from app.pipeline.mapping import FIXED_REPORT_COLUMNS
from app.pipeline.report import TOP_GROUP_HEADERS, ReportRow, write_report
def test_write_report_streaming_preserves_header_and_formats(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={"СумОперации": "100.50", "ДатаСообщения": "2026-06-16"},
participant_fields={},
)
]
write_report(rows, destination)
workbook = load_workbook(destination)
sheet = workbook["Отчет"]
headers = [cell.value for cell in sheet[2]]
assert headers == list(FIXED_REPORT_COLUMNS)
assert sheet.freeze_panes == "A3"
row_values = [
sheet.cell(row=3, column=index).value
for index in range(1, len(FIXED_REPORT_COLUMNS) + 1)
]
assert "SKO115FZ_01_123456789_20260616_X00001.xml" in row_values
date_col_idx = FIXED_REPORT_COLUMNS.index("Дата сообщения") + 1
assert isinstance(sheet.cell(row=3, column=date_col_idx).value, datetime)
assert sheet.cell(row=3, column=date_col_idx).number_format == "DD.MM.YYYY"
sum_col_idx = FIXED_REPORT_COLUMNS.index("Сумма в валюте проведения") + 1
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:
destination = tmp_path / "report.xlsx"
write_report([], destination)
workbook = load_workbook(destination)
sheet = workbook["Отчет"]
date_col_idx = FIXED_REPORT_COLUMNS.index("Дата сообщения") + 1
date_column_letter = get_column_letter(date_col_idx)
assert date_column_letter in sheet.column_dimensions
assert sheet.column_dimensions[date_column_letter].width >= 12
assert "A" not in sheet.column_dimensions
def test_write_report_contains_top_group_header_row(tmp_path: Path) -> None:
destination = tmp_path / "report.xlsx"
write_report([], destination)
workbook = load_workbook(destination)
sheet = workbook["Отчет"]
assert sheet.cell(row=1, column=1).value == "Служебные поля"
merged_ranges = {str(rng) for rng in sheet.merged_cells.ranges}
expected_ranges = set()
for header in TOP_GROUP_HEADERS:
if header.start_col > len(FIXED_REPORT_COLUMNS):
continue
end_col = min(header.end_col, len(FIXED_REPORT_COLUMNS))
if header.start_col == end_col:
continue
expected_ranges.add(
f"{_column_letter(header.start_col)}1:{_column_letter(end_col)}1"
)
assert merged_ranges == expected_ranges
def test_top_group_headers_follow_registry_report_groups() -> None:
actual_ranges = tuple(
(header.start_col, header.end_col) for header in TOP_GROUP_HEADERS
)
registry_ranges = tuple(
(group.start_index, group.end_index) for group in COLUMNS.report_groups()
)
assert actual_ranges == registry_ranges
def test_write_report_converts_date_string_to_excel_date(tmp_path: Path) -> None:
destination = tmp_path / "report.xlsx"
rows = [
ReportRow(
file_name="f.xml",
record_id="RID",
operation_index=1,
operation_fields={"ДатаСообщения": "23/08/2005"},
participant_fields={},
)
]
write_report(rows, destination)
workbook = load_workbook(destination)
sheet = workbook["Отчет"]
date_col_idx = FIXED_REPORT_COLUMNS.index("Дата сообщения") + 1
cell = sheet.cell(row=3, column=date_col_idx)
assert isinstance(cell.value, datetime)
assert cell.value.date() == date(2005, 8, 23)
assert cell.number_format == "DD.MM.YYYY"
def test_write_report_keeps_invalid_date_as_text_without_date_format(
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={},
)
]
write_report(rows, destination)
workbook = load_workbook(destination)
sheet = workbook["Отчет"]
date_col_idx = FIXED_REPORT_COLUMNS.index("Дата сообщения") + 1
cell = sheet.cell(row=3, column=date_col_idx)
assert cell.value == "не дата"
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 test_write_report_preserves_account_number_as_text(tmp_path: Path) -> None:
destination = tmp_path / "report.xlsx"
fixed_row = {index: "" for index in range(1, len(FIXED_REPORT_COLUMNS) + 1)}
fixed_row[FIXED_REPORT_COLUMNS.index("Номер счета плательщика") + 1] = (
"40702156767000000008"
)
fixed_row[FIXED_REPORT_COLUMNS.index("Номер счета получателя") + 1] = (
4.0702156767e19
)
rows = [
ReportRow(
file_name="f.xml",
record_id="RID",
operation_index=1,
operation_fields={},
participant_fields={},
)
]
with patch("app.pipeline.report.build_fixed_row_by_index", return_value=fixed_row):
write_report(rows, destination)
workbook = load_workbook(destination)
sheet = workbook["Отчет"]
payer_col = FIXED_REPORT_COLUMNS.index("Номер счета плательщика") + 1
receiver_col = FIXED_REPORT_COLUMNS.index("Номер счета получателя") + 1
assert sheet.cell(row=3, column=payer_col).value == "40702156767000000008"
assert sheet.cell(row=3, column=receiver_col).value == "40702156767000000000"
def test_write_report_restores_leading_zeros_for_bik_inn_kpp(tmp_path: Path) -> None:
destination = tmp_path / "report.xlsx"
fixed_row = {index: "" for index in range(1, len(FIXED_REPORT_COLUMNS) + 1)}
fixed_row[109] = "1"
fixed_row[FIXED_REPORT_COLUMNS.index("БИК") + 1] = 41012731
fixed_row[FIXED_REPORT_COLUMNS.index("ИНН") + 1] = 323086051
fixed_row[FIXED_REPORT_COLUMNS.index("КПП (ПРИЗНАК ИСБОЮЛ) /Пр ид-ии ФЛ:") + 1] = (
32601001
)
rows = [
ReportRow(
file_name="f.xml",
record_id="RID",
operation_index=1,
operation_fields={},
participant_fields={},
)
]
with patch("app.pipeline.report.build_fixed_row_by_index", return_value=fixed_row):
write_report(rows, destination)
workbook = load_workbook(destination)
sheet = workbook["Отчет"]
bik_col = FIXED_REPORT_COLUMNS.index("БИК") + 1
inn_col = next(
index
for index, name in enumerate(FIXED_REPORT_COLUMNS, start=1)
if "ИНН" in name
)
kpp_col = FIXED_REPORT_COLUMNS.index("КПП (ПРИЗНАК ИСБОЮЛ) /Пр ид-ии ФЛ:") + 1
assert sheet.cell(row=3, column=bik_col).value == "041012731"
assert sheet.cell(row=3, column=inn_col).value == "0323086051"
assert sheet.cell(row=3, column=kpp_col).value == "032601001"
def test_write_report_preserves_participant_address_column_positions(
tmp_path: Path,
) -> None:
destination = tmp_path / "report.xlsx"
fixed_row = {index: "" for index in range(1, len(FIXED_REPORT_COLUMNS) + 1)}
fixed_row[141] = "г. Благовещенск"
fixed_row[142] = "675520, 643, 10, Благовещенский р-н, Чигири с, Зеленая ул, 1"
fixed_row[143] = "675520"
fixed_row[144] = "643"
fixed_row[145] = "10"
fixed_row[146] = "Благовещенский р"
fixed_row[147] = "Чигири с"
fixed_row[148] = "Зеленая ул"
fixed_row[149] = "1"
fixed_row[150] = ""
fixed_row[151] = ""
rows = [
ReportRow(
file_name="f.xml",
record_id="RID",
operation_index=1,
operation_fields={},
participant_fields={},
)
]
with patch("app.pipeline.report.build_fixed_row_by_index", return_value=fixed_row):
write_report(rows, destination)
workbook = load_workbook(destination)
sheet = workbook["Отчет"]
assert sheet.cell(row=3, column=141).value == "г. Благовещенск"
assert sheet.cell(row=3, column=142).value == (
"675520, 643, 10, Благовещенский р-н, Чигири с, Зеленая ул, 1"
)
assert sheet.cell(row=3, column=143).value == "675520"
assert sheet.cell(row=3, column=144).value == "643"
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(
tmp_path: Path,
) -> None:
destination = tmp_path / "report.xlsx"
fixed_row = {index: "" for index in range(1, len(FIXED_REPORT_COLUMNS) + 1)}
fixed_row[109] = "2"
fixed_row[122] = "1"
rows = [
ReportRow(
file_name="f.xml",
record_id="RID",
operation_index=1,
operation_fields={},
participant_fields={},
)
]
with patch("app.pipeline.report.build_fixed_row_by_index", return_value=fixed_row):
write_report(rows, destination)
workbook = load_workbook(destination)
sheet = workbook["Отчет"]
assert sheet.cell(row=3, column=122).value == "1"
def test_write_report_does_not_assume_kpp_when_participant_type_is_missing(
tmp_path: Path,
) -> None:
destination = tmp_path / "report.xlsx"
fixed_row = {index: "" for index in range(1, len(FIXED_REPORT_COLUMNS) + 1)}
fixed_row[122] = "1"
rows = [
ReportRow(
file_name="f.xml",
record_id="RID",
operation_index=1,
operation_fields={},
participant_fields={},
)
]
with patch("app.pipeline.report.build_fixed_row_by_index", return_value=fixed_row):
write_report(rows, destination)
workbook = load_workbook(destination)
sheet = workbook["Отчет"]
assert sheet.cell(row=3, column=122).value == "1"
def test_write_report_keeps_only_repeat_fields_on_continuation_row(
tmp_path: Path,
) -> None:
destination = tmp_path / "report.xlsx"
rows = [
ReportRow(
file_name="f.xml",
record_id="RID",
operation_index=1,
operation_fields={"КодОперации": "5007"},
participant_fields={
"ТипУчастника": "1",
"УчастникЮЛ.СведЮЛ.ИННЮЛ": "2820000210",
},
),
ReportRow(
file_name="f.xml",
record_id="RID",
operation_index=1,
operation_fields={},
participant_fields={
"УчастникЮЛ.БенефициарЮЛ.ФЛБенефициар.ИННФЛИП": "111111111111"
},
is_continuation=True,
),
]
write_report(rows, destination)
workbook = load_workbook(destination)
sheet = workbook["Отчет"]
assert sheet.cell(row=3, column=19).value == "5007"
assert sheet.cell(row=3, column=120).value == "2820000210"
assert sheet.cell(row=4, column=19).value is None
assert sheet.cell(row=4, column=120).value in (None, "")
assert sheet.cell(row=4, column=162).value == "111111111111"
assert sheet.cell(row=4, column=44).value in (None, "")
assert sheet.cell(row=4, column=45).value in (None, "")
def _column_letter(index: int) -> str:
value = index
result = ""
while value:
value, remainder = divmod(value - 1, 26)
result = chr(65 + remainder) + result
return result