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:
@@ -278,26 +278,36 @@ async def get_absence(
|
||||
return AbsenceOut.model_validate(absence)
|
||||
|
||||
|
||||
@router.delete("/absences/{absence_id}", status_code=204)
|
||||
@router.delete("/absences/{absence_id}", response_model=AbsenceOut)
|
||||
async def cancel_absence(
|
||||
absence_id: UUID,
|
||||
current_user: CurrentUser,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Eigenen ausstehenden Antrag stornieren."""
|
||||
await absence_service.cancel_absence(absence_id, current_user, db)
|
||||
"""Antrag stornieren.
|
||||
Eigene PENDING-Anträge: alle Rollen.
|
||||
APPROVED-Anträge: nur HR/COMPANY_ADMIN/SUPER_ADMIN (mit Rückbuchung von FZA-Stunden).
|
||||
"""
|
||||
absence = await absence_service.cancel_absence(absence_id, current_user, db)
|
||||
await db.commit()
|
||||
return AbsenceOut.model_validate(absence)
|
||||
|
||||
|
||||
@router.post("/absences/{absence_id}/approve", response_model=AbsenceOut)
|
||||
class AbsenceApproveOut(AbsenceOut):
|
||||
warnings: list[str] = []
|
||||
|
||||
|
||||
@router.post("/absences/{absence_id}/approve", response_model=AbsenceApproveOut)
|
||||
async def approve_absence(
|
||||
absence_id: UUID,
|
||||
current_user: User = require_role(*_manager_roles),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
absence = await absence_service.approve_absence(absence_id, current_user, db)
|
||||
absence, warnings = await absence_service.approve_absence(absence_id, current_user, db)
|
||||
await db.commit()
|
||||
return AbsenceOut.model_validate(absence)
|
||||
out = AbsenceApproveOut.model_validate(absence)
|
||||
out.warnings = warnings
|
||||
return out
|
||||
|
||||
|
||||
@router.post("/absences/{absence_id}/reject", response_model=AbsenceOut)
|
||||
|
||||
Reference in New Issue
Block a user