@@ -4,6 +4,7 @@ from .console import TableFormatter, CompactFormatter
|
|||||||
from .csv import CSVFormatter
|
from .csv import CSVFormatter
|
||||||
from .markdown import MarkdownFormatter
|
from .markdown import MarkdownFormatter
|
||||||
from .odt import ODTFormatter
|
from .odt import ODTFormatter
|
||||||
|
from .html import HTMLFormatter
|
||||||
|
|
||||||
|
|
||||||
# Словарь для сопоставления расширений файлов с классами форматтеров
|
# Словарь для сопоставления расширений файлов с классами форматтеров
|
||||||
@@ -11,6 +12,7 @@ FORMATTER_MAP: Dict[str, Type[Formatter]] = {
|
|||||||
".odt": ODTFormatter,
|
".odt": ODTFormatter,
|
||||||
".csv": CSVFormatter,
|
".csv": CSVFormatter,
|
||||||
".md": MarkdownFormatter,
|
".md": MarkdownFormatter,
|
||||||
|
".html": HTMLFormatter,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
79
redmine_reporter/formatters/html.py
Normal file
79
redmine_reporter/formatters/html.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
from typing import List, Dict, Any
|
||||||
|
from .base import Formatter
|
||||||
|
from ..types import ReportRow
|
||||||
|
|
||||||
|
|
||||||
|
class HTMLFormatter(Formatter):
|
||||||
|
"""Форматтер для экспорта отчёта в HTML."""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def format(self, rows: List[ReportRow]) -> str:
|
||||||
|
# Сгруппируем данные
|
||||||
|
projects: Dict[str, Dict[str, List[ReportRow]]] = {}
|
||||||
|
for r in rows:
|
||||||
|
proj = r["project"]
|
||||||
|
ver = r["version"]
|
||||||
|
if proj not in projects:
|
||||||
|
projects[proj] = {}
|
||||||
|
if ver not in projects[proj]:
|
||||||
|
projects[proj][ver] = []
|
||||||
|
projects[proj][ver].append(r)
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
'<table border="1" cellpadding="6" cellspacing="0" style="border-collapse: collapse; font-family: Arial, sans-serif;">',
|
||||||
|
" <thead>",
|
||||||
|
" <tr>",
|
||||||
|
" <th>Наименование Проекта</th>",
|
||||||
|
" <th>Номер версии*</th>",
|
||||||
|
" <th>Задача</th>",
|
||||||
|
" <th>Статус Готовность*</th>",
|
||||||
|
" <th>Затрачено за отчетный период</th>",
|
||||||
|
" </tr>",
|
||||||
|
" </thead>",
|
||||||
|
" <tbody>",
|
||||||
|
]
|
||||||
|
|
||||||
|
for project, versions in projects.items():
|
||||||
|
total_project_rows = sum(len(tasks) for tasks in versions.values())
|
||||||
|
first_version_in_project = True
|
||||||
|
|
||||||
|
for version, task_rows in versions.items():
|
||||||
|
row_span_version = len(task_rows)
|
||||||
|
first_row_in_version = True
|
||||||
|
|
||||||
|
for r in task_rows:
|
||||||
|
lines.append(" <tr>")
|
||||||
|
|
||||||
|
# Ячейка "Проект" - только в первой строке проекта
|
||||||
|
if first_version_in_project and first_row_in_version:
|
||||||
|
lines.append(
|
||||||
|
f' <td rowspan="{total_project_rows}" style="vertical-align: top;">{project}</td>'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ячейка "Версия" - только в первой строке версии
|
||||||
|
if first_row_in_version:
|
||||||
|
lines.append(
|
||||||
|
f' <td rowspan="{row_span_version}" style="vertical-align: top;">{version}</td>'
|
||||||
|
)
|
||||||
|
first_row_in_version = False
|
||||||
|
|
||||||
|
# Остальные колонки
|
||||||
|
task_cell = f"{r['issue_id']}. {r['subject']}"
|
||||||
|
lines.append(f" <td>{task_cell}</td>")
|
||||||
|
lines.append(f" <td>{r['status_ru']}</td>")
|
||||||
|
lines.append(f" <td>{r['time_text']}</td>")
|
||||||
|
|
||||||
|
lines.append(" </tr>")
|
||||||
|
|
||||||
|
first_version_in_project = False
|
||||||
|
|
||||||
|
lines.append(" </tbody>")
|
||||||
|
lines.append("</table>")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def save(self, rows: List[ReportRow], output_path: str) -> None:
|
||||||
|
content = self.format(rows)
|
||||||
|
with open(output_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(content)
|
||||||
Reference in New Issue
Block a user