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:
@@ -346,6 +346,34 @@ class CalDavService:
|
||||
select(CaldavUserConfig).where(CaldavUserConfig.user_id == user_id)
|
||||
)
|
||||
|
||||
# ── Hintergrund-Sync (eigene Session) ─────────────────────────────────────
|
||||
# Fire-and-forget aus Request-Handlern darf NICHT die Request-Session
|
||||
# weiterverwenden (wird nach der Response geschlossen; in Tests sogar
|
||||
# sessionweit geteilt → "another operation in progress"). Diese Wrapper
|
||||
# öffnen eine eigene Session, laden die Abwesenheit frisch und committen.
|
||||
|
||||
async def sync_approved_bg(self, absence_id: uuid.UUID) -> None:
|
||||
await self._run_bg(absence_id, self.sync_approved)
|
||||
|
||||
async def sync_removed_bg(self, absence_id: uuid.UUID) -> None:
|
||||
await self._run_bg(absence_id, self.sync_removed)
|
||||
|
||||
async def _run_bg(self, absence_id: uuid.UUID, fn) -> None:
|
||||
from sqlalchemy import text
|
||||
from app.core.database import AsyncSessionLocal
|
||||
|
||||
try:
|
||||
async with AsyncSessionLocal() as db:
|
||||
# Interner Job ohne Tenant-Kontext → RLS-Bypass nötig
|
||||
await db.execute(text("SET LOCAL app.bypass_rls = 'on'"))
|
||||
absence = await db.get(Absence, absence_id)
|
||||
if absence is None:
|
||||
return
|
||||
await fn(absence, db)
|
||||
await db.commit()
|
||||
except Exception as exc: # darf den Request niemals beeinflussen
|
||||
log.warning("CalDAV background sync failed for absence %s: %s", absence_id, exc)
|
||||
|
||||
# ── Sync-Operationen ──────────────────────────────────────────────────────
|
||||
|
||||
async def sync_approved(self, absence: Absence, db: AsyncSession) -> None:
|
||||
|
||||
Reference in New Issue
Block a user