Darstellung
06 – Termine, Kalender & Plantafel
Reifegrad: 🟡 funktional, Teile Beta · Schicht: appointments & Co., src/lib/scheduling/*, src/lib/calendar/*, src/app/plantafel, src/app/calendar, src/app/team-boards, src/app/api/appointments/*
Zweck
Disposition von Aufträgen und Abwesenheiten (Plantafel), Kapazitätsplanung pro Mitarbeiter, Konflikterkennung sowie bidirektionaler Sync mit Google-/Microsoft-Kalendern.
Datenmodell (Migrationen 014–016)
Kern (014):
appointments:title,description,appointment_type∈ {job,absence,come,go} (come/go ergänzt durch Migration 041, siehe 08-zeiterfassung),status(planned→confirmed→en_route→completed/cancelled),starts_at/ends_at,timezone(DefaultEurope/Berlin),project_id,customer_id, ICS-Felder (ics_uid,ics_sequence),source_channel/external_source/external_reference.appointment_assignments: Mitarbeiter-Zuteilung (role,planned_minutes,UNIQUE (appointment_id, user_id)).appointment_status_history: lückenlose Statushistorie.appointment_notifications: ausgehende Benachrichtigungen (email/whatsapp/ics, Status/Provider-ID).
Kapazität & Abwesenheit (015):
user_work_schedules: Wochenarbeitszeit jeweekday(0–6),capacity_minutes.user_absences: Abwesenheitsfenster (verknüpft mit Leave, siehe 07-urlaub-abwesenheit).appointment_conflicts: erkannte Konflikte (double_booking,capacity_overload,outside_schedule), mitresolved_at.
Provider-Sync (016): siehe 13-integrationen-google-microsoft-storage – sync_connections, external_event_links, sync_events, sync_runs.
Geschäftslogik
- Statusmaschine (
src/lib/scheduling/status-machine.ts): erzwingt erlaubte Übergängeplanned→{confirmed,cancelled},confirmed→{en_route,cancelled},en_route→{completed,cancelled};completed/cancelledsind terminal. - Konflikterkennung (
conflict-service.ts): bei Anlage/Änderung prüftdetectAndStoreAppointmentConflictsDoppelbuchung & Kapazitätsüberlastung je zugeteiltem User, schreibt offene Konflikte und löst alte (resolved_at). - Kapazität (
capacity-service.ts): Soll-Kapazität aususer_work_schedulesminus Abwesenheiten/zugeteilte Minuten. - come/go-Termine sind Punkt-Events und triggern direkt die Personalzeit-Erfassung (
processCalendarCome/processCalendarGo) – Brücke zur Zeiterfassung.
API-Endpunkte
| Endpoint | Funktion |
|---|---|
GET/POST /api/appointments | Liste / anlegen (inkl. Konfliktprüfung + Outbound-Sync) |
POST /api/appointments/[id]/status | Statuswechsel (Statusmaschine) |
POST /api/appointments/[id]/notifications | Termin-Benachrichtigung auslösen |
GET /api/appointments/capacity | Kapazitäts-/Auslastungsdaten |
GET /api/team-boards/calendar | Aggregierte Team-Kalenderansicht |
UI
/plantafel– Dispositions-/Plantafelansicht (Mitarbeiter × Zeit)./calendar– Kalenderansicht./team-boards– Team-Board-Sicht (siehe 16-team-team-boards).- Komponenten:
src/components/calendar/*.
Kalender-Sync (Kurzfassung)
- Outbound:
enqueueOutboundAppointmentSync/outbound-push.tspushen interne Termine zu verbundenen Providern;external_event_linkshält die Zuordnung. - Inbound (verknüpfte Events): Webhooks (
/api/{google,microsoft}/calendar/watch/webhook) und Croncalendar-syncrufenreconcileDeletedProviderEventsForContextauf — holen das Provider-Event und schreiben Zeiten/Titel zurück aufappointments(+user_absences/leave_requestsbei Abwesenheit). Löschungen im Provider canceln den Work7-Termin. Parser:provider-inbound.ts. - Wichtig: Webhooks dürfen nicht blind Work7→Provider „repair“-pushten — das hat Outlook-Dauernänderungen überschrieben.
- Realtime-Signale
calendar:google/calendar:microsoftüber die Realtime-Bridge (siehe 15-notifications-realtime). - UI-Merge (
mergeUnifiedEvents/dedupeMirroredAbsences): interne Abwesenheiten gewinnen gegen überlappende Provider-OOF/Spiegel-Events (kein doppelter Balken in Monat/Woche) — deshalb müssen Inbound-Zeiten ins interne Appointment, nicht nur im Provider-Feed liegen. - All-Day: Google/Graph liefern exklusives Ende (21–23 → Enddatum
24).provider-event-times/normalizeMicrosoftEventsmappen auf inklusiv23:59:59Europe/Berlin — sonst erscheint der Termin einen Tag zu lang.
Bekannte Befunde / offene Punkte
- Zeitzonen-Default ist
Europe/Berlin, App-Locale teilsde-AT– bei AT-Mandanten konsistent halten. - Provider-Sync ist als Beta einzustufen (Webhook-Erneuerung, Delta-Token-Recovery, Konfliktstrategie
internal_wins/provider_wins/manual_reviewvorhanden, aber betriebsreif nur mit Monitoring – siehesync_runs). - Inbound gilt für bereits verknüpfte Events (
external_event_links). Reine Fremdtermine ohne Work7-Link werden nicht als Appointment angelegt. - Konflikttyp
outside_scheduleist im Schema vorgesehen; imconflict-servicewerden v. a.double_booking/capacity_overloaderzeugt.