Files
redmine-reporter/redmine_reporter/formatter_odt.py
Кокос Артем Николаевич 9b260b27fd Use right cells style in ODT table
2026-01-22 10:45:37 +07:00

140 lines
5.8 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.
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)
# Группировка: 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