feat: Freizeitausgleich-Lücken geschlossen (Gap 1-3) + konfigurierbare Schwellwerte

Gap-1: Überziehschutz für Überstundenkonto
  - Company.overtime_overdraft_allowed (default: true) – blockiert FZA wenn deaktiviert
  - Company.overtime_warning_threshold_hours (default: 0) – Warnung wenn Konto unter Schwelle fällt
  - warnings[] jetzt in approve_absence Response (AbsenceApproveOut)
  - Migration 0028_overtime_fza_config.py

Gap-2: total_hours wird bei Zeiteintrag-Genehmigung neu berechnet
  - time_service.approve_entry() ruft _recalculate_overtime_balance() auf
  - last_calculated Timestamp wird gesetzt

Gap-3: Stornierung genehmigter FZA-Anträge bucht taken_hours zurück
  - _refund_overtime() Helfer hinzugefügt
  - cancel_absence() erlaubt jetzt HR/Admin auch genehmigte Abwesenheiten zu stornieren
  - DELETE /absences/{id} gibt jetzt AbsenceOut zurück (statt 204)
  - Mitarbeiter können genehmigte FZA-Anträge nicht selbst stornieren (409)

Frontend:
  - CompanySettingsPage: neuer Abschnitt 'Freizeitausgleich' mit Toggle + Schwellwert-Eingabe

Tests: backend/tests/test_fza.py mit 6 Tests (alle 3 Gaps)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 00:08:03 +02:00
parent 0ba16bb6af
commit 345002944e
9 changed files with 619 additions and 19 deletions
+11
View File
@@ -336,6 +336,17 @@ class TimeService:
entry.status = EntryStatus.APPROVED
entry.approved_by = current_user.id
entry.updated_at = datetime.now(timezone.utc)
# Überstundenkonto neuberechnen (Gap-2-Fix)
from app.services.report_service import _recalculate_overtime_balance
schedule = await db.scalar(
select(WorkSchedule)
.where(WorkSchedule.company_id == entry_user.company_id)
.order_by(WorkSchedule.valid_from.desc())
.limit(1)
)
await _recalculate_overtime_balance(entry_user, schedule, db)
return entry
async def reject_entry(