238 lines
7.7 KiB
Python
238 lines
7.7 KiB
Python
import json
|
|
from datetime import date
|
|
from io import BytesIO
|
|
|
|
from openpyxl import Workbook
|
|
from reportlab.lib.pagesizes import A4, landscape
|
|
from reportlab.pdfgen import canvas
|
|
|
|
from app.services.calculations import minutes_to_hhmm
|
|
from app.services.targets import monday_of
|
|
|
|
|
|
def create_excel_export(rows: list[dict], week_summaries: list[dict], totals: dict, title: str) -> bytes:
|
|
workbook = Workbook()
|
|
sheet = workbook.active
|
|
sheet.title = "Tage"
|
|
|
|
headers = [
|
|
"Datum",
|
|
"Wochentag",
|
|
"KW",
|
|
"Start",
|
|
"Ende",
|
|
"Pause (min)",
|
|
"Brutto",
|
|
"Netto",
|
|
"Stundenausgleich",
|
|
"Sonderstatus",
|
|
"Wochen-Soll",
|
|
"Wochen-Delta",
|
|
"Notiz",
|
|
]
|
|
sheet.append(headers)
|
|
|
|
for row in rows:
|
|
sheet.append(
|
|
[
|
|
row["date"].isoformat(),
|
|
row["weekday_name"],
|
|
row["iso_week"],
|
|
row["start_time"] or "",
|
|
row["end_time"] or "",
|
|
row["break_minutes"],
|
|
minutes_to_hhmm(row["gross_minutes"]),
|
|
minutes_to_hhmm(row["net_minutes"]),
|
|
minutes_to_hhmm(row["overtime_adjustment_minutes"]),
|
|
row["special_status_label"] or "",
|
|
minutes_to_hhmm(row["weekly_target_minutes"]),
|
|
minutes_to_hhmm(row["weekly_delta_minutes"]),
|
|
row["notes"] or "",
|
|
]
|
|
)
|
|
|
|
for col in ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M"]:
|
|
sheet.column_dimensions[col].width = 16
|
|
|
|
summary = workbook.create_sheet("Wochen")
|
|
summary_headers = ["KW-Start", "KW-Ende", "Ist", "Soll", "Delta"]
|
|
summary.append(summary_headers)
|
|
|
|
for item in week_summaries:
|
|
summary.append(
|
|
[
|
|
item["week_start"].isoformat(),
|
|
item["week_end"].isoformat(),
|
|
minutes_to_hhmm(item["ist_minutes"]),
|
|
minutes_to_hhmm(item["soll_minutes"]),
|
|
minutes_to_hhmm(item["delta_minutes"]),
|
|
]
|
|
)
|
|
|
|
summary.append([])
|
|
summary.append(["Gesamt", "", minutes_to_hhmm(totals["ist_minutes"]), "", minutes_to_hhmm(totals["delta_minutes"])])
|
|
|
|
meta = workbook.create_sheet("Meta")
|
|
meta.append([title])
|
|
meta.append([f"Zeitraum: {totals['from_date'].isoformat()} bis {totals['to_date'].isoformat()}"])
|
|
|
|
output = BytesIO()
|
|
workbook.save(output)
|
|
return output.getvalue()
|
|
|
|
|
|
def create_pdf_export(rows: list[dict], week_summaries: list[dict], totals: dict, title: str) -> bytes:
|
|
output = BytesIO()
|
|
pdf = canvas.Canvas(output, pagesize=landscape(A4))
|
|
width, height = landscape(A4)
|
|
|
|
y = height - 35
|
|
pdf.setFont("Helvetica-Bold", 13)
|
|
pdf.drawString(24, y, title)
|
|
y -= 18
|
|
|
|
pdf.setFont("Helvetica", 10)
|
|
pdf.drawString(24, y, f"Zeitraum: {totals['from_date'].isoformat()} bis {totals['to_date'].isoformat()}")
|
|
y -= 24
|
|
|
|
pdf.setFont("Helvetica-Bold", 8)
|
|
pdf.drawString(24, y, "Datum")
|
|
pdf.drawString(88, y, "Tag")
|
|
pdf.drawString(124, y, "KW")
|
|
pdf.drawString(154, y, "Start")
|
|
pdf.drawString(198, y, "Ende")
|
|
pdf.drawString(242, y, "Pause")
|
|
pdf.drawString(286, y, "Brutto")
|
|
pdf.drawString(338, y, "Netto")
|
|
pdf.drawString(390, y, "Ausgl.")
|
|
pdf.drawString(436, y, "Status")
|
|
pdf.drawString(490, y, "Soll")
|
|
pdf.drawString(542, y, "W-Delta")
|
|
pdf.drawString(610, y, "Notiz")
|
|
y -= 12
|
|
|
|
pdf.setFont("Helvetica", 8)
|
|
for row in rows:
|
|
if y < 40:
|
|
pdf.showPage()
|
|
y = height - 30
|
|
pdf.setFont("Helvetica", 8)
|
|
|
|
note = (row["notes"] or "").strip()
|
|
if len(note) > 18:
|
|
note = f"{note[:15]}..."
|
|
|
|
pdf.drawString(24, y, row["date"].isoformat())
|
|
pdf.drawString(88, y, row["weekday_short"])
|
|
pdf.drawString(124, y, str(row["iso_week"]))
|
|
pdf.drawString(154, y, row["start_time"] or "-")
|
|
pdf.drawString(198, y, row["end_time"] or "-")
|
|
pdf.drawString(242, y, str(row["break_minutes"]))
|
|
pdf.drawString(286, y, minutes_to_hhmm(row["gross_minutes"]))
|
|
pdf.drawString(338, y, minutes_to_hhmm(row["net_minutes"]))
|
|
pdf.drawString(390, y, minutes_to_hhmm(row["overtime_adjustment_minutes"]))
|
|
pdf.drawString(436, y, row["special_status_label"] or "-")
|
|
pdf.drawString(490, y, minutes_to_hhmm(row["weekly_target_minutes"]))
|
|
pdf.drawString(542, y, minutes_to_hhmm(row["weekly_delta_minutes"]))
|
|
pdf.drawString(610, y, note)
|
|
y -= 11
|
|
|
|
y -= 12
|
|
pdf.setFont("Helvetica-Bold", 10)
|
|
pdf.drawString(24, y, "Wochenzusammenfassung")
|
|
y -= 14
|
|
|
|
pdf.setFont("Helvetica", 9)
|
|
for item in week_summaries:
|
|
if y < 40:
|
|
pdf.showPage()
|
|
y = height - 30
|
|
pdf.setFont("Helvetica", 9)
|
|
|
|
line = (
|
|
f"{item['week_start'].isoformat()} - {item['week_end'].isoformat()} | "
|
|
f"Ist {minutes_to_hhmm(item['ist_minutes'])} | "
|
|
f"Soll {minutes_to_hhmm(item['soll_minutes'])} | "
|
|
f"Delta {minutes_to_hhmm(item['delta_minutes'])}"
|
|
)
|
|
pdf.drawString(24, y, line)
|
|
y -= 12
|
|
|
|
y -= 10
|
|
pdf.setFont("Helvetica-Bold", 10)
|
|
pdf.drawString(
|
|
24,
|
|
y,
|
|
f"Gesamt Ist: {minutes_to_hhmm(totals['ist_minutes'])} | Gesamt Delta: {minutes_to_hhmm(totals['delta_minutes'])}",
|
|
)
|
|
|
|
pdf.save()
|
|
return output.getvalue()
|
|
|
|
|
|
def create_backup_export(payload: dict) -> bytes:
|
|
return json.dumps(payload, ensure_ascii=False, indent=2).encode("utf-8")
|
|
|
|
|
|
def build_export_rows(
|
|
days: list[date],
|
|
entries_by_date: dict,
|
|
week_target_map: dict[date, int],
|
|
week_ist_map: dict[date, int],
|
|
week_delta_map: dict[date, int],
|
|
special_status_map: dict[date, str] | None = None,
|
|
overtime_adjustment_map: dict[date, int] | None = None,
|
|
) -> list[dict]:
|
|
weekday_names = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"]
|
|
weekday_short = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]
|
|
|
|
special_status_map = special_status_map or {}
|
|
overtime_adjustment_map = overtime_adjustment_map or {}
|
|
special_status_labels = {
|
|
"holiday": "Feiertag",
|
|
"sick": "Krankheit",
|
|
}
|
|
|
|
rows: list[dict] = []
|
|
for day in days:
|
|
entry = entries_by_date.get(day)
|
|
week_start = monday_of(day)
|
|
weekly_target = week_target_map[week_start]
|
|
weekly_delta = week_delta_map[week_start]
|
|
|
|
if entry:
|
|
gross = entry.end_minutes - entry.start_minutes
|
|
net = gross - entry.break_minutes
|
|
start_time = f"{entry.start_minutes // 60:02d}:{entry.start_minutes % 60:02d}"
|
|
end_time = f"{entry.end_minutes // 60:02d}:{entry.end_minutes % 60:02d}"
|
|
break_minutes = entry.break_minutes
|
|
notes = entry.notes
|
|
else:
|
|
gross = 0
|
|
net = 0
|
|
start_time = None
|
|
end_time = None
|
|
break_minutes = 0
|
|
notes = None
|
|
|
|
rows.append(
|
|
{
|
|
"date": day,
|
|
"weekday_name": weekday_names[day.weekday()],
|
|
"weekday_short": weekday_short[day.weekday()],
|
|
"iso_week": day.isocalendar()[1],
|
|
"start_time": start_time,
|
|
"end_time": end_time,
|
|
"break_minutes": break_minutes,
|
|
"gross_minutes": gross,
|
|
"net_minutes": net,
|
|
"overtime_adjustment_minutes": overtime_adjustment_map.get(day, 0),
|
|
"special_status_label": special_status_labels.get(special_status_map.get(day, "")),
|
|
"weekly_target_minutes": weekly_target,
|
|
"weekly_delta_minutes": weekly_delta,
|
|
"notes": notes,
|
|
}
|
|
)
|
|
|
|
return rows
|