This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
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
|
||||
Reference in New Issue
Block a user