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:
@@ -141,6 +141,28 @@ class EmailService:
|
||||
"""
|
||||
await self._send(user.email, "Passwort zurücksetzen", _html_wrapper("Passwort zurücksetzen", body), cfg)
|
||||
|
||||
async def send_substitute_notification(
|
||||
self, substitute: "User", requester: "User", absence, db: AsyncSession
|
||||
) -> None:
|
||||
"""Informiert die eingetragene Vertretung über eine genehmigte Abwesenheit."""
|
||||
cfg = await self._load_smtp(substitute.company_id, db)
|
||||
start = absence.start_date.strftime("%d.%m.%Y")
|
||||
end = absence.end_date.strftime("%d.%m.%Y")
|
||||
zeitraum = start if start == end else f"{start} – {end}"
|
||||
body = f"""
|
||||
<h1>Du wurdest als Vertretung eingetragen</h1>
|
||||
<p>Hallo {substitute.first_name},</p>
|
||||
<p><strong>{requester.full_name}</strong> ist im Zeitraum <strong>{zeitraum}</strong>
|
||||
abwesend und hat dich als Vertretung benannt.</p>
|
||||
<a href="{settings.frontend_url}/absences" class="btn">Abwesenheiten ansehen</a>
|
||||
"""
|
||||
await self._send(
|
||||
substitute.email,
|
||||
f"Vertretung für {requester.full_name} ({zeitraum})",
|
||||
_html_wrapper("Vertretung", body),
|
||||
cfg,
|
||||
)
|
||||
|
||||
async def send_test(self, cfg: SmtpConfig, to: str) -> None:
|
||||
"""Test-E-Mail direkt mit übergebenem Konfigurationsobjekt."""
|
||||
body = f"""
|
||||
|
||||
Reference in New Issue
Block a user