Geplante Erinnerungen (Feature-Parität mit Urlaubsverwaltung): - APScheduler (AsyncIOScheduler) in der FastAPI-Lifespan; tägliche Jobs ab settings.reminder_hour. Redis-Tageslock gegen Doppelversand bei mehreren Prozessen; jeder Job mit eigener Session + RLS-Bypass. - Drei Jobs (auch einzeln aufrufbar): offene Anträge an Genehmiger, Resturlaub-Verfall-Vorwarnung an Mitarbeiter, fehlende AU an HR. - Pro-User notification_prefs (JSONB, opt-out); GET/PATCH /users/me/notification-prefs + ProfilePage-UI; Vertreter-Mail respektiert die Prefs. - Manueller Trigger POST /companies/me/run-reminders (Admin) – gleiche Logik, firmen-scoped (testbar ohne Warten). - Bugfix: GET-/PATCH-Urlaubskonto (update_balance) nutzte nicht existente Felder (base_days/carried_over_days/ip_address) → korrigiert auf entitled_days/carried_over/ip + company_id; available_days ergänzt. Migration 0037 (users.notification_prefs). 188/188 Tests grün. Deployed 137 + 164. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
28 lines
1.1 KiB
Python
28 lines
1.1 KiB
Python
"""Pro-User-Benachrichtigungseinstellungen (agent-11 PR3).
|
|
|
|
Bekannte Keys + Defaults. Ein leeres `notification_prefs`-dict bedeutet „alle an"
|
|
(opt-out-Modell). Neue Keys defaulten daher automatisch auf aktiv.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
# key -> (Label, Default-aktiv)
|
|
NOTIFICATION_TYPES: dict[str, tuple[str, bool]] = {
|
|
"substitute_assigned": ("Als Vertretung eingetragen", True),
|
|
"pending_approvals": ("Offene Anträge zur Genehmigung (Zusammenfassung)", True),
|
|
"carryover_expiry": ("Resturlaub verfällt bald", True),
|
|
"certificate_overdue": ("Fehlende AU-Bescheinigung (HR)", True),
|
|
}
|
|
|
|
|
|
def pref_enabled(user, key: str) -> bool:
|
|
"""Ob der User Benachrichtigungen vom Typ `key` erhalten möchte."""
|
|
prefs = getattr(user, "notification_prefs", None) or {}
|
|
default = NOTIFICATION_TYPES.get(key, ("", True))[1]
|
|
return bool(prefs.get(key, default))
|
|
|
|
|
|
def normalize_prefs(prefs: dict | None) -> dict:
|
|
"""Nur bekannte Keys übernehmen, auf bool casten."""
|
|
prefs = prefs or {}
|
|
return {k: bool(prefs[k]) for k in NOTIFICATION_TYPES if k in prefs}
|