Globales Event-Notify-System
Das Event-Notify-System ist die zentrale Infrastruktur fuer Echtzeit-Benachrichtigungen
in der xynap Platform. Es verbindet Backend-Events mit dem Frontend und unterstuetzt
mehrere Kanaele (In-App, E-Mail, Telegram, Browser-Push, Sprachausgabe).
Architektur
┌──────────────┐ emit_event() ┌──────────────┐
│ Backend │ ──────────────────→ │ Event Bus │
│ (Service) │ │ (Pub/Sub) │
└──────────────┘ └──────┬───────┘
│
┌────────────────────┼─────────────────────┐
▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌──────────────┐
│ Workflow │ │ WebSocket │ │ Dispatcher │
│ Engine │ │ Broadcast │ │ (E-Mail/TG) │
└───────────┘ └──────┬───────┘ └──────────────┘
│
┌──────┴───────┐
│ Frontend │
│ EventStream │
└──────┬───────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌──────────────┐
│ Bell + │ │ Call Notifier │ │ Browser │
│ Badge + │ │ (Modal + │ │ Notification │
│ Dropdown │ │ Sprache) │ │ (Desktop) │
└───────────┘ └──────────────┘ └──────────────┘
Kanaele
| Kanal |
Beschreibung |
Trigger |
| app |
In-App Notification (DB + UI) |
create_notification() |
| email |
E-Mail via SMTP |
Dispatcher |
| telegram |
Telegram Bot-API |
Dispatcher |
| browser |
Desktop-Notification (Web Push) |
Frontend bei notification.created |
| speech |
Sprachausgabe (Web Speech API) |
Frontend bei call.incoming |
Event-Typen
System-Events
| Event |
Payload |
Beschreibung |
notification.created |
uuid, title, body, category, severity, ... |
Neue In-App Notification |
call.incoming |
caller_id, did_number, customer, ... |
Eingehender Anruf (SIP) |
mail.new_message |
folder, message |
Neue E-Mail |
mail.folder_update |
folder |
Mail-Ordner geaendert |
support.session.created |
session_code, channel |
Neue Support-Anfrage |
file.uploaded |
path, name |
Datei hochgeladen |
Notification-Kategorien
| Kategorie |
Beispiele |
system |
Systemstatus, Updates, Wartung |
payment |
Zahlungseingang, Zahlungserinnerung |
dunning |
Mahneskalation |
crm |
Kunden-Events, Aufgaben |
security |
Login, 2FA, verdaechtige Aktivitaeten |
mail |
Neue E-Mails (optional) |
Severity-Level
| Level |
Farbe |
Verwendung |
info |
Blau |
Allgemeine Information |
success |
Gruen |
Erfolgreiche Aktion |
warning |
Gelb |
Warnung, Aufmerksamkeit noetig |
error |
Rot |
Fehler, sofortiges Handeln |
Backend: Notification erstellen
from app.core.notifications.service import create_notification
# Einfache Notification
notif = await create_notification(
db, user_id=42,
title="Neuer Kunde angelegt",
body="Kunde 'Mustermann GmbH' wurde erfolgreich erstellt.",
category="crm",
severity="success",
icon="building",
entity_type="customer",
entity_id="uuid-...",
action_url="/crm/customers/uuid-...",
)
# → Speichert in DB
# → Emittiert notification.created via WebSocket an User 42
# → Frontend zeigt sofort Badge + Animation + Sound
Tenant-Admins benachrichtigen
from app.core.notifications.service import notify_tenant_admins
await notify_tenant_admins(
db, tenant_id=1,
title="Rechnung ueberfaellig",
body="Rechnung INV-2026-001 ist seit 14 Tagen ueberfaellig.",
category="dunning",
severity="warning",
)
Eigenes Event emittieren
from app.core.events import emit_event
await emit_event("order.completed", {
"user_id": user.id,
"order_id": order.id,
"total": 299.00,
}, source="shop", db=db)
# → Wird via Wildcard-Handler an WebSocket weitergeleitet
# → Workflow-Engine kann darauf reagieren
Frontend: Events empfangen
Notification Center (Composable)
import { useNotificationCenter } from '@/shell/composables/useNotificationCenter'
const {
notifications, // ref<AppNotification[]>
unreadCount, // ref<number>
hasUnread, // computed<boolean>
bellAnimating, // ref<boolean> — true waehrend Shake-Animation
incomingCalls, // ref<IncomingCall[]>
showCallNotifier, // ref<boolean>
markRead, // (notif) => Promise
markAllRead, // () => Promise
deleteNotification,// (notif) => Promise
dismissCall, // (callUuid) => void
} = useNotificationCenter()
Eigene Event-Handler registrieren
import { useEventStream } from '@/shell/composables/useEventStream'
const events = useEventStream()
// Exakter Match
events.on('order.completed', (event) => {
console.log('Bestellung abgeschlossen:', event.data)
})
// Wildcard (alle order.* Events)
events.on('order.*', (event) => {
console.log('Order-Event:', event.type, event.data)
})
REST API
Notifications abrufen
GET /api/v1/notifications?limit=20&unread_only=true&category=payment
Zusammenfassung
GET /api/v1/notifications/summary
→ { "total": 42, "unread": 3 }
Als gelesen markieren
PUT /api/v1/notifications/{uuid}/read
POST /api/v1/notifications/read-all
Loeschen
DELETE /api/v1/notifications/{uuid}
Call Notifier
Bei eingehenden Anrufen (call.incoming Event):
- Modal erscheint mit Treffer-Liste (aehnlich Spotlight-Suche)
- Klingelton (425 Hz, deutsches Freizeichen-Muster)
- Sprachausgabe via Web Speech API: "Eingehender Anruf von Kunde Mustermann GmbH, Nummer K-10042"
- Kunden-Link: Direkter Sprung zum Kunden-Detail
- Auto-Dismiss: Nach 30 Sekunden verschwindet der Klingelton
Caller-Lookup
Das Backend fuehrt automatisch einen Kunden-Lookup anhand der CallerID durch:
- Vergleicht letzte 10 Ziffern mit gespeicherten Telefonnummern
- Bei Treffer: Kundenname, Kundennummer, Locale im Event-Payload
- Frontend zeigt Treffer-Badge (gruen: "Kunde") oder (grau: "Unbekannt")
Datenmodell
notifications (Tabelle)
| Feld |
Typ |
Beschreibung |
| id |
BigInt |
Primary Key |
| uuid |
String(36) |
Unique Identifier |
| user_id |
BigInt |
FK → users |
| tenant_id |
BigInt |
Optional |
| type |
String(50) |
Event-Typ |
| channel |
String(20) |
app/email/telegram |
| category |
String(30) |
system/payment/dunning/... |
| severity |
String(20) |
info/warning/error/success |
| icon |
String(50) |
Lucide Icon-Name |
| title |
String(255) |
Titel |
| body |
Text |
Nachricht |
| data |
JSON |
Zusaetzliche Daten |
| entity_type |
String(30) |
z.B. "invoice", "customer" |
| entity_id |
String(36) |
UUID der verknuepften Entitaet |
| action_url |
String(500) |
Navigation-Ziel |
| read_at |
DateTime |
Gelesen-Zeitpunkt |
| created_at |
DateTime |
Erstellungszeitpunkt |
notification_preferences (Tabelle)
| Feld |
Typ |
Beschreibung |
| user_id |
BigInt |
FK → users |
| event_type |
String(100) |
z.B. "payment.*" |
| channel |
String(20) |
Kanal |
| enabled |
Boolean |
Aktiviert |