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
+7
View File
@@ -55,6 +55,13 @@ class Company(Base):
# Freizeitausgleich-Konfiguration
overtime_overdraft_allowed: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
overtime_warning_threshold_hours: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
# Überstunden-Kappung
overtime_cap_hours: Mapped[int | None] = mapped_column(Integer, nullable=True)
# Überstunden-Verfall
overtime_expiry_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
overtime_expiry_month: Mapped[int] = mapped_column(Integer, nullable=False, default=3) # März
overtime_expiry_day: Mapped[int] = mapped_column(Integer, nullable=False, default=31) # 31.
overtime_max_carryover_hours: Mapped[int | None] = mapped_column(Integer, nullable=True) # None = alles
# Relationships
users: Mapped[list["User"]] = relationship("User", back_populates="company", lazy="noload")