import os 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 odf.style import Style, TableColumnProperties, TableCellProperties from .formatter import get_version, hours_to_human, STATUS_TRANSLATION from .utils import get_month_name_from_range def format_odt( issue_hours: List[Tuple[Issue, float]], author: str = "", from_date: str = "", to_date: str = "" ) -> "OpenDocument": template_path = "template.odt" if not os.path.exists(template_path): raise FileNotFoundError("Шаблон template.odt не найден...") doc = load(template_path) para_style_name = "Standard" # Заголовок month_name = get_month_name_from_range(from_date, to_date) header_text = f"{author}. Отчет за месяц {month_name}." header_paragraph = P(stylename=para_style_name, text=header_text) doc.text.addElement(header_paragraph) # Добавляем пустую строку (новый параграф без текста) empty_paragraph = P(stylename=para_style_name, text="") doc.text.addElement(empty_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)) # Создаем стиль для ячеек таблицы cell_style_name = "TableCellStyle" cell_style = Style(name=cell_style_name, family="table-cell") # Устанавливаем отступы (Padding) cell_props = TableCellProperties( padding="0.04in", border="0.05pt solid #000000" ) cell_style.addElement(cell_props) doc.automaticstyles.addElement(cell_style) # Создаем стиль для всей таблицы (опционально, но может понадобиться) table_style_name = "ReportTableStyle" table_style = Style(name=table_style_name, family="table") # Создаем таблицу и применяем стиль table = Table(name="Report", stylename=table_style_name) # Добавляем стили для каждой колонки (ширины) column_widths = ["1.56in", "1.63in", "3.93in", "1.56in", "1.43in"] for i, width in enumerate(column_widths): col_style_name = f"col{i+1}" col_style = Style(name=col_style_name, family="table-column") col_props = TableColumnProperties(columnwidth=width, breakbefore="auto") col_style.addElement(col_props) doc.automaticstyles.addElement(col_style) table.addElement(TableColumn(stylename=col_style)) # Заголовки header_row = TableRow() headers = ["Наименование Проекта", "Номер версии*", "Задача", "Статус Готовность*", "Затрачено за отчетный период"] for text in headers: cell = TableCell(stylename=cell_style_name) 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(stylename=cell_style_name) 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(stylename=cell_style_name) 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(stylename=cell_style_name) p = P(stylename=para_style_name, text=f"{issue.id}. {issue.subject}") task_cell.addElement(p) row.addElement(task_cell) status_cell = TableCell(stylename=cell_style_name) p = P(stylename=para_style_name, text=status_ru) status_cell.addElement(p) row.addElement(status_cell) time_cell = TableCell(stylename=cell_style_name) 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