chore: initialize public repository
CI / checks (push) Has been cancelled

This commit is contained in:
maddin
2026-03-22 12:55:55 +00:00
commit 9794362f39
143 changed files with 19832 additions and 0 deletions
+237
View File
@@ -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