from typing import List, Tuple from redminelib.resources import Issue from odf.opendocument import load from odf.text import P from odf.table import Table, TableColumn, TableRow, TableCell from .formatter import get_version, hours_to_human, STATUS_TRANSLATION import os def format_odt(issue_hours: List[Tuple[Issue, float]]) -> "OpenDocument": template_path = "template.odt" if not os.path.exists(template_path): raise FileNotFoundError("Шаблон template.odt не найден...") doc = load(template_path) para_style_name = "Standard" # Заголовок header_text = "Кокос Артём Николаевич. Отчет за месяц Июль." header_paragraph = P(stylename=para_style_name, text=header_text) doc.text.addElement(header_paragraph) # Группировка: project → version → [(issue, hours, status_ru)] projects = {} for issue, hours in issue_hours: project = str(issue.project) version = get_version(issue) status_en = str(issue.status) status_ru = STATUS_TRANSLATION.get(status_en, status_en) if project not in projects: projects[project] = {} if version not in projects[project]: projects[project][version] = [] projects[project][version].append((issue, hours, status_ru)) # Создание таблицы table = Table(name="Report") for _ in range(5): table.addElement(TableColumn()) # Заголовки header_row = TableRow() headers = ["Наименование Проекта", "Номер версии*", "Задача", "Статус Готовность*", "Затрачено за отчетный период"] for text in headers: cell = TableCell() p = P(stylename=para_style_name, text=text) cell.addElement(p) header_row.addElement(cell) table.addElement(header_row) # Данные: двухуровневая группировка for project, versions in projects.items(): total_project_rows = sum(len(rows) for rows in versions.values()) first_version_in_project = True for version, rows in versions.items(): row_span_version = len(rows) first_row_in_version = True for issue, hours, status_ru in rows: row = TableRow() # Ячейка "Проект" — только в первой строке всего проекта if first_version_in_project and first_row_in_version: cell_project = TableCell() cell_project.setAttribute("numberrowsspanned", str(total_project_rows)) p = P(stylename=para_style_name, text=project) cell_project.addElement(p) row.addElement(cell_project) # Ячейка "Версия" — только в первой строке каждой версии if first_row_in_version: cell_version = TableCell() cell_version.setAttribute("numberrowsspanned", str(row_span_version)) p = P(stylename=para_style_name, text=version) cell_version.addElement(p) row.addElement(cell_version) first_row_in_version = False else: # Пропускаем — уже объединена pass # Остальные колонки task_cell = TableCell() p = P(stylename=para_style_name, text=f"{issue.id}. {issue.subject}") task_cell.addElement(p) row.addElement(task_cell) status_cell = TableCell() p = P(stylename=para_style_name, text=status_ru) status_cell.addElement(p) row.addElement(status_cell) time_cell = TableCell() p = P(stylename=para_style_name, text=hours_to_human(hours)) time_cell.addElement(p) row.addElement(time_cell) table.addElement(row) first_version_in_project = False doc.text.addElement(table) return doc