- #23: add JSONFormatter and XLSXFormatter - add openpyxl dependency for .xlsx export - #22: add --summary flag and calculate_summary() in report_builder - ReportRow now carries raw hours for summary calculations - update CLI help and README with .json/.xlsx formats and --summary - add tests for new formatters and summary computation Closes #22, closes #23
120 lines
4.4 KiB
Python
120 lines
4.4 KiB
Python
from typing import Dict, List, Tuple, cast
|
||
|
||
from redminelib.resources import Issue
|
||
|
||
from .types import ReportRow
|
||
from .utils import get_version, hours_to_human
|
||
|
||
STATUS_TRANSLATION = {
|
||
"New": "В работе",
|
||
"In Progress": "В работе",
|
||
"Feedback": "В работе",
|
||
"Re-opened": "В работе",
|
||
"Code Review": "Решена",
|
||
"Wait Release": "Закрыто",
|
||
"Pending": "Ожидание",
|
||
"Resolved": "Решена",
|
||
"Testing": "Решена",
|
||
"Confirming": "Ожидание",
|
||
"Closed": "Закрыто",
|
||
"Rejected": "Закрыто",
|
||
"Frozen": "Ожидание",
|
||
}
|
||
|
||
|
||
def build_grouped_report(
|
||
issue_hours: List[Tuple[Issue, float]],
|
||
fill_time: bool = True,
|
||
) -> List[ReportRow]:
|
||
"""
|
||
Преобразует список задач с затраченным временем в плоский список строк отчёта,
|
||
с учётом группировки по проекту и версии (пустые ячейки для повторяющихся значений).
|
||
|
||
Предусловие: issue_hours должен быть отсортирован по (project, version).
|
||
Функция выполняет сортировку самостоятельно для защиты от несортированного ввода.
|
||
"""
|
||
|
||
# Защитная сортировка -- гарантирует корректную группировку независимо от порядка на входе
|
||
issue_hours = sorted(issue_hours, key=lambda x: (str(x[0].project), get_version(x[0]), x[0].id))
|
||
|
||
rows: List[ReportRow] = []
|
||
prev_project: str = ""
|
||
prev_version: str = ""
|
||
|
||
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)
|
||
time_text = hours_to_human(hours) if fill_time else ""
|
||
|
||
display_project = project if project != prev_project else ""
|
||
display_version = version if (project != prev_project or version != prev_version) else ""
|
||
|
||
rows.append(
|
||
cast(
|
||
ReportRow,
|
||
{
|
||
"project": project,
|
||
"version": version,
|
||
"display_project": display_project,
|
||
"display_version": display_version,
|
||
"issue_id": issue.id,
|
||
"subject": issue.subject,
|
||
"status_ru": status_ru,
|
||
"time_text": time_text,
|
||
"hours": round(hours, 2),
|
||
},
|
||
)
|
||
)
|
||
|
||
prev_project = project
|
||
prev_version = version
|
||
|
||
return rows
|
||
|
||
|
||
def calculate_summary(rows: List[ReportRow]) -> Dict[str, float]:
|
||
"""Возвращает сводку: общее время, время по проектам и версиям."""
|
||
total = 0.0
|
||
by_project: Dict[str, float] = {}
|
||
by_project_version: Dict[str, float] = {}
|
||
|
||
for r in rows:
|
||
hours = r.get("hours", 0.0)
|
||
total += hours
|
||
by_project[r["project"]] = by_project.get(r["project"], 0.0) + hours
|
||
key = f"{r['project']}::{r['version']}"
|
||
by_project_version[key] = by_project_version.get(key, 0.0) + hours
|
||
|
||
return {
|
||
"total": round(total, 2),
|
||
**{f"project:{k}": round(v, 2) for k, v in by_project.items()},
|
||
**{f"version:{k}": round(v, 2) for k, v in by_project_version.items()},
|
||
}
|
||
|
||
|
||
def group_rows_by_project_and_version(
|
||
rows: List[ReportRow],
|
||
) -> Dict[str, Dict[str, List[ReportRow]]]:
|
||
"""
|
||
Группирует плоский список строк отчёта в иерархию project → version → [rows].
|
||
|
||
Предполагается, что rows уже отсортирован (build_grouped_report это гарантирует).
|
||
Возвращает обычный dict, сохраняющий порядок вставки (Python 3.7+).
|
||
Используется форматтерами HTML и ODT для объединения ячеек.
|
||
"""
|
||
projects: Dict[str, Dict[str, List[ReportRow]]] = {}
|
||
for r in rows:
|
||
project = r["project"]
|
||
version = r["version"]
|
||
|
||
if project not in projects:
|
||
projects[project] = {}
|
||
if version not in projects[project]:
|
||
projects[project][version] = []
|
||
|
||
projects[project][version].append(r)
|
||
|
||
return projects
|