Files
TimeTracker/app/utils/summary_report_pdf.py
T
Dries Peeters b4486a627f fix: CI tests, code quality, and duplicate DB indexes
- Webhook models: remove duplicate index definitions so db.create_all()
  no longer raises 'index already exists' (columns already have index=True)
- ImportService: fix circular import by late-importing ClientService,
  ProjectService, TimeTrackingService in __init__
- reports: fix F823 by renaming unpack variable _ to _entry_count to avoid
  shadowing gettext _ in export_task_excel()
- Code quality: add .flake8 with extend-ignore so flake8 CI passes;
  simplify pyproject.toml isort config (drop unsupported options)
- Format: run black and isort on app/
- tests: restore minimal app fixture in test_import_export_models
2026-03-15 10:51:52 +01:00

144 lines
4.7 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.
"""
Summary report PDF export one-page PDF with today/week/month hours and top projects.
Uses ReportLab (same as time_entries_pdf).
"""
from io import BytesIO
from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle
# Colors (aligned with time_entries_pdf)
BRAND_COLOR = colors.HexColor("#1e3a5f")
HEADER_BG = colors.HexColor("#1e3a5f")
HEADER_FG = colors.HexColor("#ffffff")
ROW_ALT_BG = colors.HexColor("#f0f4f8")
GRID_LIGHT = colors.HexColor("#dde3ea")
MUTED_TEXT = colors.HexColor("#64748b")
MARGIN = 1.5 * cm
PAGE_SIZE = A4
def build_summary_report_pdf(today_hours, week_hours, month_hours, project_stats):
"""
Build a one-page Summary Report PDF.
Args:
today_hours: float
week_hours: float
month_hours: float
project_stats: list of dicts with keys "project" (object with .name) and "hours" (float)
Returns:
bytes: PDF file content
"""
buffer = BytesIO()
doc = SimpleDocTemplate(
buffer,
pagesize=PAGE_SIZE,
leftMargin=MARGIN,
rightMargin=MARGIN,
topMargin=MARGIN,
bottomMargin=MARGIN,
)
elements = []
# Title
title_style = ParagraphStyle(
"SummaryTitle",
fontName="Helvetica-Bold",
fontSize=18,
leading=22,
textColor=BRAND_COLOR,
)
elements.append(Paragraph("Summary Report", title_style))
elements.append(Spacer(1, 6))
# Stats row: Today | Week | Month
stats_data = [
["Today's Hours", "Week's Hours", "Month's Hours"],
[f"{today_hours:.2f} h", f"{week_hours:.2f} h", f"{month_hours:.2f} h"],
]
stats_table = Table(
stats_data,
colWidths=[5 * cm, 5 * cm, 5 * cm],
rowHeights=[0.7 * cm, 0.9 * cm],
)
stats_table.setStyle(
TableStyle(
[
("BACKGROUND", (0, 0), (-1, 0), HEADER_BG),
("TEXTCOLOR", (0, 0), (-1, 0), HEADER_FG),
("ALIGN", (0, 0), (-1, -1), TA_CENTER),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, 0), 10),
("FONTNAME", (0, 1), (-1, 1), "Helvetica"),
("FONTSIZE", (0, 1), (-1, 1), 12),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 8),
("BOTTOMPADDING", (0, 0), (-1, -1), 8),
("GRID", (0, 0), (-1, -1), 0.5, GRID_LIGHT),
]
)
)
elements.append(stats_table)
elements.append(Spacer(1, 12))
# Top projects table
sub_title_style = ParagraphStyle(
"SubTitle",
fontName="Helvetica-Bold",
fontSize=12,
leading=14,
textColor=BRAND_COLOR,
)
elements.append(Paragraph("Top Projects (Last 30 Days)", sub_title_style))
elements.append(Spacer(1, 6))
if project_stats:
table_data = [["Project", "Total Hours"]]
for stat in project_stats:
project_name = stat.get("project")
name = getattr(project_name, "name", str(project_name)) if project_name else ""
hours = stat.get("hours", 0)
table_data.append([name, f"{hours:.2f}"])
col_widths = [12 * cm, 4 * cm]
projects_table = Table(table_data, colWidths=col_widths, repeatRows=1)
projects_table.setStyle(
TableStyle(
[
("BACKGROUND", (0, 0), (-1, 0), HEADER_BG),
("TEXTCOLOR", (0, 0), (-1, 0), HEADER_FG),
("ALIGN", (0, 0), (0, -1), TA_LEFT),
("ALIGN", (1, 0), (1, -1), "RIGHT"),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, 0), 10),
("FONTSIZE", (0, 1), (-1, -1), 9),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 6),
("BOTTOMPADDING", (0, 0), (-1, -1), 6),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [ROW_ALT_BG, colors.white]),
("GRID", (0, 0), (-1, -1), 0.5, GRID_LIGHT),
]
)
)
elements.append(projects_table)
else:
no_data_style = ParagraphStyle(
"NoData",
fontName="Helvetica",
fontSize=10,
leading=12,
textColor=MUTED_TEXT,
)
elements.append(Paragraph("No project data for the last 30 days.", no_data_style))
doc.build(elements)
return buffer.getvalue()