Skip to content

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 (plannedconfirmeden_routecompleted/cancelled), starts_at/ends_at, timezone (Default Europe/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 je weekday (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), mit resolved_at.

Provider-Sync (016): siehe 13-integrationen-google-microsoft-storagesync_connections, external_event_links, sync_events, sync_runs.

Geschäftslogik

  • Statusmaschine (src/lib/scheduling/status-machine.ts): erzwingt erlaubte Übergänge planned→{confirmed,cancelled}, confirmed→{en_route,cancelled}, en_route→{completed,cancelled}; completed/cancelled sind terminal.
  • Konflikterkennung (conflict-service.ts): bei Anlage/Änderung prüft detectAndStoreAppointmentConflicts Doppelbuchung & Kapazitätsüberlastung je zugeteiltem User, schreibt offene Konflikte und löst alte (resolved_at).
  • Kapazität (capacity-service.ts): Soll-Kapazität aus user_work_schedules minus Abwesenheiten/zugeteilte Minuten.
  • come/go-Termine sind Punkt-Events und triggern direkt die Personalzeit-Erfassung (processCalendarCome / processCalendarGo) – Brücke zur Zeiterfassung.

API-Endpunkte

EndpointFunktion
GET/POST /api/appointmentsListe / anlegen (inkl. Konfliktprüfung + Outbound-Sync)
POST /api/appointments/[id]/statusStatuswechsel (Statusmaschine)
POST /api/appointments/[id]/notificationsTermin-Benachrichtigung auslösen
GET /api/appointments/capacityKapazitäts-/Auslastungsdaten
GET /api/team-boards/calendarAggregierte 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.ts pushen interne Termine zu verbundenen Providern; external_event_links hält die Zuordnung.
  • Inbound (verknüpfte Events): Webhooks (/api/{google,microsoft}/calendar/watch/webhook) und Cron calendar-sync rufen reconcileDeletedProviderEventsForContext auf — holen das Provider-Event und schreiben Zeiten/Titel zurück auf appointments (+ user_absences / leave_requests bei 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 / normalizeMicrosoftEvents mappen auf inklusiv 23:59:59 Europe/Berlin — sonst erscheint der Termin einen Tag zu lang.

Bekannte Befunde / offene Punkte

  • Zeitzonen-Default ist Europe/Berlin, App-Locale teils de-AT – bei AT-Mandanten konsistent halten.
  • Provider-Sync ist als Beta einzustufen (Webhook-Erneuerung, Delta-Token-Recovery, Konfliktstrategie internal_wins/provider_wins/manual_review vorhanden, aber betriebsreif nur mit Monitoring – siehe sync_runs).
  • Inbound gilt für bereits verknüpfte Events (external_event_links). Reine Fremdtermine ohne Work7-Link werden nicht als Appointment angelegt.
  • Konflikttyp outside_schedule ist im Schema vorgesehen; im conflict-service werden v. a. double_booking/capacity_overload erzeugt.

Verwandte Notes

Work7 · Software für Handwerksbetriebe