- Draw a grid on the two header rows only, data cells stay borderless. - Align column names to the top so the start of a long name stays visible. - Narrow the 14 code columns to the length of their values; the rule matches by column name, so identical indicators in every block get equal widths. - Halve both payment system name columns (32 -> 16). - Give "Характеристика операции" the same width as "Назначение платежа". - Drop the group title above "Статус перевода": it duplicated the column name. The group cell stays in place so the header grid is not broken. - Pass the cell format to write_string. Identifier columns (БИК, ИНН, ОГРН, КПП, СВИФТ, account numbers) were written without one, so text wrapping and vertical alignment never applied to them - this is why БИК had no line break. The width padding and the header rotation are single constants: the customer has not confirmed yet whether "N знаков" means the value length (implemented) or an amount to subtract, and whether narrow headers should be rotated.
574 lines
20 KiB
Python
574 lines
20 KiB
Python
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_date_and_other_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" in sheet.column_dimensions
|
||
assert sheet.column_dimensions["A"].width >= 12
|
||
|
||
|
||
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 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 test_write_report_draws_grid_only_in_header_rows(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["Отчет"]
|
||
group_cell = sheet.cell(row=1, column=1)
|
||
header_cell = sheet.cell(row=2, column=1)
|
||
data_cell = sheet.cell(row=3, column=1)
|
||
|
||
assert group_cell.border.bottom.style == "thin"
|
||
assert header_cell.border.bottom.style == "thin"
|
||
assert header_cell.border.left.style == "thin"
|
||
# Заказчик просил сетку только на двух строках шапки, данные без рамок.
|
||
assert data_cell.border.bottom.style is None
|
||
|
||
|
||
def test_write_report_aligns_column_names_to_top(tmp_path: Path) -> None:
|
||
destination = tmp_path / "report.xlsx"
|
||
write_report([], destination)
|
||
|
||
workbook = load_workbook(destination)
|
||
sheet = workbook["Отчет"]
|
||
header_cell = sheet.cell(row=2, column=1)
|
||
|
||
assert header_cell.alignment.vertical == "top"
|
||
assert header_cell.alignment.wrap_text is True
|
||
|
||
|
||
def test_write_report_drops_group_title_above_transfer_status(tmp_path: Path) -> None:
|
||
destination = tmp_path / "report.xlsx"
|
||
write_report([], destination)
|
||
|
||
workbook = load_workbook(destination)
|
||
sheet = workbook["Отчет"]
|
||
status_col = FIXED_REPORT_COLUMNS.index("Статус перевода") + 1
|
||
|
||
assert sheet.cell(row=2, column=status_col).value == "Статус перевода"
|
||
assert sheet.cell(row=1, column=status_col).value in (None, "")
|
||
# Рамка у пустой ячейки группы остаётся, чтобы сетка шапки не разрывалась.
|
||
assert sheet.cell(row=1, column=status_col).border.bottom.style == "thin"
|
||
|
||
|
||
def test_write_report_narrows_code_columns_in_every_block(tmp_path: Path) -> None:
|
||
destination = tmp_path / "report.xlsx"
|
||
write_report([], destination)
|
||
|
||
workbook = load_workbook(destination)
|
||
sheet = workbook["Отчет"]
|
||
widths = {}
|
||
for dimension in sheet.column_dimensions.values():
|
||
for index in range(dimension.min, dimension.max + 1):
|
||
widths[index] = dimension.width
|
||
|
||
# Ширина = длина значения + запас под отступы и кнопку автофильтра
|
||
# (см. _WIDTH_PADDING/_MIN_COLUMN_WIDTH в app/pipeline/report.py).
|
||
expected_width_by_characters = {1: 3, 2: 4, 3: 5}
|
||
expected_characters = {
|
||
"Тип информации": 1,
|
||
"ОКАТО": 2,
|
||
"Номер филиала": 2,
|
||
"Валюта": 3,
|
||
"Признак валютной операции": 2,
|
||
"Вид перевода": 1,
|
||
"Тип оператора": 1,
|
||
"Статус перевода": 1,
|
||
"Признак ДУЛ": 1,
|
||
}
|
||
for name, characters in expected_characters.items():
|
||
index = FIXED_REPORT_COLUMNS.index(name) + 1
|
||
expected = expected_width_by_characters[characters]
|
||
assert expected <= widths[index] < expected + 1, name
|
||
|
||
# Одноимённые показатели в разных блоках должны быть одинаковой ширины.
|
||
okato_indexes = [
|
||
index
|
||
for index, name in enumerate(FIXED_REPORT_COLUMNS, start=1)
|
||
if "ОКАТО" in name
|
||
]
|
||
assert len(okato_indexes) > 1
|
||
assert len({round(widths[index], 2) for index in okato_indexes}) == 1
|
||
|
||
document_indexes = [
|
||
index
|
||
for index, name in enumerate(FIXED_REPORT_COLUMNS, start=1)
|
||
if "Код вида документа" in name
|
||
]
|
||
assert len(document_indexes) > 1
|
||
assert len({round(widths[index], 2) for index in document_indexes}) == 1
|
||
|
||
|
||
def test_write_report_halves_payment_system_columns(tmp_path: Path) -> None:
|
||
destination = tmp_path / "report.xlsx"
|
||
write_report([], destination)
|
||
|
||
workbook = load_workbook(destination)
|
||
sheet = workbook["Отчет"]
|
||
widths = {}
|
||
for dimension in sheet.column_dimensions.values():
|
||
for index in range(dimension.min, dimension.max + 1):
|
||
widths[index] = dimension.width
|
||
|
||
payment_system_indexes = [
|
||
index
|
||
for index, name in enumerate(FIXED_REPORT_COLUMNS, start=1)
|
||
if "Наименование плат. системы" in name
|
||
]
|
||
assert len(payment_system_indexes) == 2
|
||
for index in payment_system_indexes:
|
||
assert 16 <= widths[index] < 17
|
||
|
||
|
||
def test_write_report_matches_operation_summary_columns_widths(tmp_path: Path) -> None:
|
||
destination = tmp_path / "report.xlsx"
|
||
write_report([], destination)
|
||
|
||
workbook = load_workbook(destination)
|
||
sheet = workbook["Отчет"]
|
||
widths = {}
|
||
for dimension in sheet.column_dimensions.values():
|
||
for index in range(dimension.min, dimension.max + 1):
|
||
widths[index] = dimension.width
|
||
|
||
names = ("Характеристика операции", "Назначение платежа", "Доп. сведения")
|
||
resolved = {
|
||
round(widths[FIXED_REPORT_COLUMNS.index(name) + 1], 2) for name in names
|
||
}
|
||
assert len(resolved) == 1
|
||
|
||
|
||
def test_write_report_wraps_text_in_identifier_cells(tmp_path: Path) -> None:
|
||
destination = tmp_path / "report.xlsx"
|
||
rows = [
|
||
ReportRow(
|
||
file_name="f.xml",
|
||
record_id="RID",
|
||
operation_index=1,
|
||
operation_fields={"БИКФл": "040813733"},
|
||
participant_fields={},
|
||
)
|
||
]
|
||
write_report(rows, destination)
|
||
|
||
workbook = load_workbook(destination)
|
||
sheet = workbook["Отчет"]
|
||
bik_cell = sheet.cell(row=3, column=FIXED_REPORT_COLUMNS.index("БИК") + 1)
|
||
|
||
assert bik_cell.value == "040813733"
|
||
# Раньше write_string вызывался без формата, поэтому у БИК, ИНН, КПП и
|
||
# номеров счетов не работал ни перенос строки, ни выравнивание.
|
||
assert bik_cell.alignment.wrap_text is True
|
||
assert bik_cell.alignment.vertical == "top"
|
||
|
||
|
||
def _column_letter(index: int) -> str:
|
||
value = index
|
||
result = ""
|
||
while value:
|
||
value, remainder = divmod(value - 1, 26)
|
||
result = chr(65 + remainder) + result
|
||
return result
|