feat: Überstunden-Kappung + Jahresverfall pro Firma konfigurierbar

Backend:
- Company: overtime_cap_hours, overtime_expiry_enabled/month/day,
  overtime_max_carryover_hours
- OvertimeBalance: last_expiry_applied_at
- Migration 0031: neue Spalten in companies + overtime_balances
- _recalculate_overtime_balance: Kappung direkt nach Berechnung
- apply_overtime_expiry_if_needed(): lazy Verfall beim Balance-Abruf
- GET /absences/overtime-balance: prüft + wendet Verfall automatisch an
- POST /absences/overtime-balance/apply-expiry: manueller Trigger (Admin)

Frontend:
- CompanySettingsPage: neuer Block 'Überstunden-Konto'
  - Toggle Kappungsgrenze + Stunden-Input
  - Toggle Jahresverfall + Stichtag (Tag/Monat) + max. Übertrag
  - 'Verfall anwenden'-Button für Admins

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 22:48:30 +02:00
parent 23b45881a1
commit 23ba7f1762
8 changed files with 364 additions and 1 deletions
+1
View File
@@ -34,6 +34,7 @@ class OvertimeBalance(Base):
total_hours: Mapped[Decimal] = mapped_column(Numeric(8, 2), default=Decimal("0"))
taken_hours: Mapped[Decimal] = mapped_column(Numeric(8, 2), default=Decimal("0"))
last_calculated: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
last_expiry_applied_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()