feat: Live-Stempel-Uhr, Break-UI, Balance-Widget, Approval-Queue + PDF-Export (WeasyPrint)

Frontend (TimeTrackingPage):
- Live-Arbeitsuhr (HH:MM:SS) während eingestempelt
- Break-Start/End-Buttons mit laufender Pausenuhr
- Wochen-Balance-Widget (gearbeitet / erwartet / überstunden)
- Approval-Queue Tab für Manager/HR/Admin (pending entries genehmigen/ablehnen)

Backend (Reports):
- weasyprint>=61.0 in requirements.txt
- 3 neue PDF-Export-Tests (Zeit, Abwesenheit, Überstunden)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 11:59:32 +02:00
parent ada1b51f33
commit 62ef6c2a11
6 changed files with 1327 additions and 139 deletions
+44
View File
@@ -308,3 +308,47 @@ def test_to_xlsx_with_data():
result = svc.to_xlsx(rows, sheet_name="Test")
assert isinstance(result, bytes)
assert len(result) > 1000 # XLSX ist ZIP-basiert
# ── PDF Export ─────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_export_time_pdf(client: AsyncClient, report_headers):
today = date.today()
resp = await client.get(
"/api/v1/reports/time/export",
params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "pdf"},
headers=report_headers,
)
assert resp.status_code == 200
assert "pdf" in resp.headers["content-type"]
assert "attachment" in resp.headers.get("content-disposition", "")
assert len(resp.content) > 1000
@pytest.mark.asyncio
async def test_export_absence_pdf(client: AsyncClient, report_headers):
today = date.today()
resp = await client.get(
"/api/v1/reports/absences/export",
params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "pdf"},
headers=report_headers,
)
assert resp.status_code == 200
assert "pdf" in resp.headers["content-type"]
assert "attachment" in resp.headers.get("content-disposition", "")
assert len(resp.content) > 1000
@pytest.mark.asyncio
async def test_export_overtime_pdf(client: AsyncClient, report_headers):
today = date.today()
resp = await client.get(
"/api/v1/reports/overtime/export",
params={"date_from": str(today.replace(day=1)), "date_to": str(today), "format": "pdf"},
headers=report_headers,
)
assert resp.status_code == 200
assert "pdf" in resp.headers["content-type"]
assert "attachment" in resp.headers.get("content-disposition", "")
assert len(resp.content) > 1000