from __future__ import annotations from dataclasses import dataclass from datetime import datetime from pathlib import Path from typing import Iterable from openpyxl import Workbook from openpyxl.styles import Alignment, Font, PatternFill from openpyxl.utils import get_column_letter @dataclass class ProtocolRow: file_name: str status: str message: str operations_total: int rows_written: int launcher: str processed_at: datetime def write_protocol(rows: Iterable[ProtocolRow], destination: Path) -> None: row_list = list(rows) workbook = Workbook() details = workbook.active details.title = "Протокол" details.freeze_panes = "A2" columns = [ "Имя файла", "Статус", "Комментарий", "Операций", "Строк отчета", "Запустил", "Время обработки", ] _write_header(details, columns) for row_index, row in enumerate(row_list, start=2): values = [ row.file_name, row.status, row.message, row.operations_total, row.rows_written, row.launcher, row.processed_at.strftime("%Y-%m-%d %H:%M:%S"), ] for col_index, value in enumerate(values, start=1): details.cell(row=row_index, column=col_index, value=value) _auto_width(details, columns) _write_summary(workbook, row_list) destination.parent.mkdir(parents=True, exist_ok=True) workbook.save(destination) def _write_summary(workbook: Workbook, rows: list[ProtocolRow]) -> None: summary = workbook.create_sheet("Итог") total = len(rows) success = sum(1 for row in rows if row.status == "success") partial = sum(1 for row in rows if row.status == "partial") errors = sum(1 for row in rows if row.status == "error") metrics = [ ("Всего файлов", total), ("Успешно", success), ("Частично", partial), ("Ошибки", errors), ] for index, (name, value) in enumerate(metrics, start=1): summary.cell(row=index, column=1, value=name).font = Font(bold=True) summary.cell(row=index, column=2, value=value) summary.column_dimensions[get_column_letter(1)].width = 24 summary.column_dimensions[get_column_letter(2)].width = 14 def _write_header(sheet, columns: list[str]) -> None: fill = PatternFill(start_color="D9E1F2", end_color="D9E1F2", fill_type="solid") for index, name in enumerate(columns, start=1): cell = sheet.cell(row=1, column=index, value=name) cell.font = Font(bold=True) cell.alignment = Alignment(horizontal="center", vertical="center") cell.fill = fill def _auto_width(sheet, columns: list[str]) -> None: for index, name in enumerate(columns, start=1): width = max(14, min(70, len(name) + 6)) sheet.column_dimensions[get_column_letter(index)].width = width