feat: agent-11 PR1 – Vertretung, Storno-Re-Genehmigung, Kommentare
Abwesenheits-Modul abgerundet (Feature-Parität mit Urlaubsverwaltung):
- Vertretung: Overlap-Warnung beim Anlegen, E-Mail an Vertretung bei
Genehmigung, GET /absences/?as_substitute=true, neuer schlanker
GET /users/colleagues (alle Rollen, RLS-gefenced) für die Auswahl;
Vertreter-Dropdown + Anzeige in der Liste.
- Stornierung mit Re-Genehmigung: neuer Status CANCELLATION_REQUESTED,
POST /absences/{id}/request-cancellation; Manager genehmigt/lehnt über
bestehende approve/reject ab (Urlaub + FZA-Rückbuchung via _apply_cancellation).
- Kommentare: Model AbsenceComment (company_id-RLS), GET/POST comments,
System-Kommentare bei Statuswechsel, AbsenceCommentsModal.
- Fix: CalDAV fire-and-forget nutzte die Request-Session weiter (in Tests
geteilt -> "another operation in progress"); jetzt sync_*_bg mit eigener
Session + RLS-Bypass.
Migration 0035. 178/178 Tests grün. Deployed auf 137 + 164.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,12 +12,15 @@ from app.schemas.absence import (
|
||||
AbsenceCreate,
|
||||
AbsenceListResponse,
|
||||
AbsenceOut,
|
||||
AbsenceCommentCreate,
|
||||
AbsenceCommentOut,
|
||||
AbsenceReject,
|
||||
AbsenceUpdate,
|
||||
AbsenceTypeCreate,
|
||||
AbsenceTypeOut,
|
||||
AbsenceTypeUpdate,
|
||||
CalendarEntry,
|
||||
CancellationRequest,
|
||||
CertificateMarkIn,
|
||||
OvertimeBalanceOut,
|
||||
PublicHolidayCreate,
|
||||
@@ -256,11 +259,13 @@ async def list_absences(
|
||||
type_id: UUID | None = Query(None),
|
||||
status: AbsenceStatus | None = Query(None),
|
||||
year: int | None = Query(None),
|
||||
as_substitute: bool = Query(False),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
total, absences = await absence_service.list_absences(
|
||||
current_user.company_id, current_user, db,
|
||||
user_id=user_id, type_id=type_id, status=status, year=year,
|
||||
as_substitute=as_substitute,
|
||||
)
|
||||
return AbsenceListResponse(total=total, items=[AbsenceOut.model_validate(a) for a in absences])
|
||||
|
||||
@@ -325,6 +330,44 @@ async def cancel_absence(
|
||||
return AbsenceOut.model_validate(absence)
|
||||
|
||||
|
||||
@router.post("/absences/{absence_id}/request-cancellation", response_model=AbsenceOut)
|
||||
async def request_cancellation(
|
||||
absence_id: UUID,
|
||||
data: CancellationRequest,
|
||||
current_user: CurrentUser,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Mitarbeiter beantragt Stornierung eines genehmigten Antrags (Manager genehmigt/lehnt ab).
|
||||
|
||||
HR/Admin storniert weiterhin direkt über DELETE."""
|
||||
absence = await absence_service.request_cancellation(absence_id, data.reason, current_user, db)
|
||||
await db.commit()
|
||||
return AbsenceOut.model_validate(absence)
|
||||
|
||||
|
||||
# ── Kommentare ────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/absences/{absence_id}/comments", response_model=list[AbsenceCommentOut])
|
||||
async def list_absence_comments(
|
||||
absence_id: UUID,
|
||||
current_user: CurrentUser,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
return await absence_service.list_comments(absence_id, current_user, db)
|
||||
|
||||
|
||||
@router.post("/absences/{absence_id}/comments", response_model=AbsenceCommentOut, status_code=201)
|
||||
async def add_absence_comment(
|
||||
absence_id: UUID,
|
||||
data: AbsenceCommentCreate,
|
||||
current_user: CurrentUser,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
comment = await absence_service.add_comment(absence_id, data.body, current_user, db)
|
||||
await db.commit()
|
||||
return comment
|
||||
|
||||
|
||||
class AbsenceApproveOut(AbsenceOut):
|
||||
warnings: list[str] = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user