Zum Inhalt

Xolib Kalender — Vollstaendiges Konzept

Der Kalender ist das Nervensystem der Hausverwaltung. Alles was intern und extern passiert, fliesst hier zusammen.


1. Vision

Eine Hausverwaltung koordiniert taeglich dutzende Akteure: eigene Mitarbeiter, externe Handwerker, Mieter, Eigentuemer, Behoerden, Versorger. Heute passiert das per Telefon, E-Mail, Excel und Kopf-Kalender. Xolib macht den Kalender zur zentralen Organisationsschnittstelle:

  • Intern: Wer ist wann wo? Welcher Mitarbeiter hat noch Kapazitaet?
  • Extern: Welcher Handwerker hat wann Zeit? Automatische Terminabstimmung.
  • Automatisch: KI-Agenten tragen Termine selbststaendig ein (Fristen, Wartungen, Eskalationen).
  • Querverlinkt: Jeder Termin gehoert zu einem Objekt, einer Einheit, einem Ticket, einem Mieter.

Wettbewerbsvorteil: Kein System am Markt bietet automatisierte Handwerker-Terminabstimmung mit Self-Service-Slots. Das ist ein Feature, das Hausverwaltungen sofort verstehen und wollen.

Data Moat: Jeder Termin erzeugt Daten — Reaktionszeiten von Handwerkern, Abschlussquoten, No-Shows, bevorzugte Zeitfenster. Cross-Tenant: "Handwerker Firma X hat durchschnittlich 3 Tage Vorlauf" wird systemweites Wissen.


2. Akteure und ihre Beduerfnisse

2.1 Hausverwaltung (OWNER_ADMIN / ADMIN)

Tagesablauf: - Morgens: Wochenplanung — Wer muss wohin? Welche Fristen laufen ab? - Untertags: Handwerker-Koordination — Wer hat Zeit fuer Reparatur in Schillerstr. 45? - Abends: Naechste Woche vorbereiten — WEG-Versammlungen, Begehungen, Ablesungen

Braucht: - Wochenansicht mit allen Mitarbeitern nebeneinander (Ressourcen-View) - Objektansicht: Was passiert diese Woche an meinen Objekten? - Fristenwarnung: BK-Abrechnung in 30 Tagen faellig, Zertifikat laeuft ab - Ein-Klick-Handwerker-Anfrage: "Sende Terminvorschlaege an 3 Installateure" - Kapazitaetsanzeige: Mitarbeiter X ist diese Woche zu 90% ausgelastet

2.2 Mitarbeiter / Hausmeister (HAUSHELD_WORKER)

Tagesablauf: - Morgens: "Was muss ich heute machen?" — Meine Termine, meine Objekte - Unterwegs: Mobile Ansicht, Navigation zum naechsten Objekt - Abschluss: Termin als erledigt markieren, Foto hochladen

Braucht: - Tagesansicht mit nur eigenen Terminen - Adresse/Route zum naechsten Objekt - "Erledigt"-Button mit optionalem Kommentar - Push-Benachrichtigung bei neuem Termin

2.3 Externer Handwerker / Dienstleister

Problem heute: Hausverwaltung ruft an → Handwerker hat gerade keinen Kalender da → Rueckruf vergessen → 3 Tage spaeter nochmal anrufen. Oder E-Mail-Ping-Pong mit 5 Terminvorschlaegen.

Xolib-Loesung: Self-Service Terminbuchung

1. Hausverwaltung erstellt Terminanfrage (was, wo, Dringlichkeit)
2. System sendet automatisch E-Mail an 1-3 Handwerker:
   "Reparatur Heizung, Hansaring 12, EG links. Bitte waehlen Sie einen Termin:"
   [Mo 10:00] [Di 14:00] [Mi 09:00] [Anderen Termin vorschlagen]
3. Handwerker klickt auf Link → Landingpage mit Slots
4. Handwerker waehlt Slot → Termin wird automatisch bestaetigt
5. Beide Seiten erhalten Bestaetigungsmail + ICS-Datei
6. Termin erscheint im Xolib-Kalender, verlinkt mit Ticket
7. 24h vorher: automatische Erinnerung an Handwerker
8. Nach Termin: Hausverwaltung bewertet Handwerker (fuettert Score)

Braucht: - Keine App, kein Login — nur E-Mail-Link - Einfache Slot-Auswahl (Mobile-optimiert) - ICS-Datei fuer eigenen Kalender (Outlook, Google, Apple) - Erinnerungsmail 24h vorher - Option: "Keiner der Termine passt — anderen vorschlagen"

2.4 Mieter (TENANT)

Braucht: - Sieht nur Termine, die eigenes Objekt/Einheit betreffen - Wartungsankuendigung: "Am 15.03. kommt der Heizungsmonteur zwischen 10-12 Uhr" - Besichtigungstermin: "Wohnungsbesichtigung am 20.03. um 14:00" - Zaehlerablesung: "Bitte am 25.03. zwischen 9-11 Uhr anwesend sein" - Uebergabe: "Schluesseluebergabe am 01.04. um 10:00 im Buero" - NICHT sehen: Interne Termine, Kosten, andere Mieter, Score-Details

2.5 Eigentuemer (OWNER)

Braucht: - WEG-Versammlungen: Wann, wo, Tagesordnung - Begehungen: Naechste Objektbegehung - Wartungsplan: Wann ist die naechste Heizungswartung geplant? - Handwerker-Einsaetze: Was wurde an meinem Objekt gemacht?

2.6 KI-Agenten (SYSTEM_AI)

Agenten erstellen Termine automatisch basierend auf Events:

Trigger Agent Kalender-Aktion
Ticket eskaliert → Handwerker noetig Ingenieur HANDWERKER_TERMIN erstellen, Handwerker-Anfrage senden
Heizungswartung 12 Monate her Ingenieur WARTUNG vorschlagen
BK-Abrechnung Frist in 60 Tagen Finanzwaechter FRIST erstellen
Mietvertrag Kuendigungsfrist naht Vertragsagent FRIST erstellen
Zertifikat laeuft in 90 Tagen ab Rechtswatcher FRIST erstellen
Buergschaft Gewerbe laeuft ab Gewerbe-Spezialist FRIST erstellen
WEG-Versammlung erstellt WEG-Spezialist WEG_VERSAMMLUNG erstellen
Zaehlerablesung geplant Energieagent ABLESUNG erstellen
Neuer Mieter zieht ein Kommunikator MIETER_TERMIN (Uebergabe) erstellen

3. Ansichten (Views)

3.1 Wochenansicht (Standard-View)

           Mo 16.03    Di 17.03    Mi 18.03    Do 19.03    Fr 20.03
          ──────────  ──────────  ──────────  ──────────  ──────────
 08:00    │          │          │          │          │          │
 09:00    │ Heizung- │          │ Besicht. │          │ Begehung │
 10:00    │ wartung  │          │ Schiller │ Ablesung │ Am Stadt-│
 11:00    │ Hansar.  │          │ str. 45  │ Hansar.  │ graben 8 │
 12:00    │          │          │          │          │          │
 13:00    │          │ Team-    │          │          │          │
 14:00    │          │ Meeting  │ WEG-     │          │          │
 15:00    │          │          │ Versam.  │          │          │
 16:00    │          │          │          │          │          │
  • Farbkodierung nach Termintyp
  • Klick auf Termin → Detail-Panel rechts
  • Drag & Drop zum Verschieben (spaeter)
  • Heute-Linie (aktuelle Uhrzeit als rote horizontale Linie)

3.2 Monatsansicht

  • Klassisches Kalenderblatt
  • Jeder Tag zeigt bis zu 3 Termindots, danach "+X weitere"
  • Klick auf Tag → Tagesdetail-Overlay
  • Fristentermine rot hervorgehoben
  • Farblegende am unteren Rand

3.3 Ressourcen-Ansicht (NEU — Killer-Feature)

           Mueller     Schmidt     Hausm. Ali   Firma Heiz.  Firma Rohr
          (Admin)     (Admin)     (Hausm.)     (Extern)     (Extern)
          ──────────  ──────────  ──────────  ──────────  ──────────
 Mo       │ Buero    │ Besicht. │ Hansar.12 │ Wartung  │          │
          │          │ Sch.45   │ Sch.45    │ Stadgr.  │          │
 Di       │ WEG-Vers│          │ Hansar.12 │          │ Rohr-    │
          │          │ Buero    │ Sch.45    │          │ reinig.  │
 Mi       │          │          │ Stadgr.   │          │          │
  • Spalten = Personen (Mitarbeiter + zugewiesene Handwerker)
  • Zeilen = Tage der Woche (oder Stunden des Tages)
  • Sofort sichtbar: Wer ist ueberlastet? Wer hat noch Kapazitaet?
  • Kapazitaets-Bar unter jedem Namen: ████████░░ 80%
  • Farbkodierung: Gruen (<60%), Gelb (60-80%), Rot (>80%)
  • Nicht zugewiesene Termine in separater "Offen"-Spalte

3.4 Objekt-Ansicht (NEU)

           Hansaring 12    Schillerstr. 45   Am Stadtgraben 8
          ──────────────  ────────────────  ─────────────────
 Diese    │ Mo Heizung  │ Mi Besicht.    │ Fr Begehung     │
 Woche    │ Di Hausmstr │ Do Rohrspuel.  │                 │
          │             │                │                 │
 Naechste │ Ablesung    │                │ WEG-Versamml.   │
 Woche    │             │                │                 │
  • Spalten = Objekte (filterbar)
  • Zeilen = Zeitraeume (diese Woche / naechste Woche / Monat)
  • Zeigt alle Aktivitaeten pro Objekt auf einen Blick
  • Eigentuemer-Portal zeigt nur diese Ansicht fuer eigene Objekte

3.5 Liste (Filter-Power-View)

  • Alle Filter kombinierbar: Typ + Status + Objekt + Person + Zeitraum + Suche
  • Sortierbar nach Datum, Typ, Status
  • Bulk-Aktionen: Alle markierten als "Erledigt" setzen
  • Export: CSV, ICS (alle gefilterten Termine als Kalender-Abo)

4. Handwerker-Terminabstimmung (Deep Dive)

4.1 Flow: Hausverwaltung → Handwerker

┌──────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  1. HV erstellt  │     │  2. System sen- │     │  3. Handwerker  │
│  Terminanfrage   │────>│  det E-Mail mit │────>│  klickt Link,   │
│  (was/wo/wann)   │     │  Slot-Auswahl   │     │  waehlt Slot    │
└──────────────────┘     └─────────────────┘     └─────────────────┘
                                                         │
┌──────────────────┐     ┌─────────────────┐             │
│  5. Termin im    │     │  4. Beide er-   │             │
│  Kalender mit    │<────│  halten Bestae- │<────────────┘
│  Ticket-Link     │     │  tigung + ICS   │
└──────────────────┘     └─────────────────┘

4.2 Terminanfrage erstellen (Admin-UI)

Neuer Termintyp oder Button: "Handwerker anfragen"

Formular: - Was: Kurzbeschreibung ("Heizung defekt, kein Warmwasser") - Wo: Objekt + Einheit + Zugangsinformation ("Schluessel bei Frau Mueller, 2. OG") - Dringlichkeit: Normal (innerhalb 7 Tage) / Dringend (innerhalb 48h) / Notfall (heute) - Zeitfenster: 3-5 Slots vorschlagen (z.B. Mo 9-12, Di 14-17, Mi 9-12) - Handwerker: Aus ServiceProvider-Liste waehlen (1-3 Firmen parallel anfragen) - Verknuepfung: Ticket-ID (wenn aus Ticket heraus erstellt) - Budget-Limit: Optional ("Kostenvoranschlag ab 500 EUR")

4.3 E-Mail an Handwerker

Betreff: Terminanfrage — Heizungsreparatur, Hansaring 12

Guten Tag Firma Mustermann,

wir benoetigen Ihre Unterstuetzung fuer folgendes Anliegen:

  Reparatur: Heizung defekt, kein Warmwasser
  Objekt: Hansaring 12, EG links (Wohnung 01a)
  Zugang: Schluessel bei Frau Mueller, 2. OG rechts
  Dringlichkeit: Normal (innerhalb 7 Tage)

Bitte waehlen Sie einen der folgenden Termine:

  [Mo 17.03. | 09:00–12:00]  ← Klickbarer Button/Link
  [Di 18.03. | 14:00–17:00]
  [Mi 19.03. | 09:00–12:00]

  [Keiner passt — anderen Termin vorschlagen]

Die Terminbestaetigung erhalten Sie sofort per E-Mail inkl. ICS-Datei.

Mit freundlichen Gruessen
Mustermann Hausverwaltung GmbH
via Xolib

4.4 Slot-Auswahl Landingpage (Handwerker-Seite)

  • Kein Login erforderlich — signierter Link (JWT-Token in URL, 7 Tage gueltig)
  • Mobile-optimiert (Handwerker sind unterwegs)
  • Zeigt: Was, Wo, Dringlichkeit, Slots als grosse klickbare Karten
  • Bei Klick auf Slot:
  • Bestaetigung: "Termin bestaetigt: Mo 17.03. 09:00–12:00"
  • Download ICS-Datei
  • "Zum Kalender hinzufuegen" Deep-Links (Google Calendar, Outlook, Apple)
  • Alternative: "Keiner passt" → Freitext-Feld fuer Gegenvorschlag → E-Mail an HV
  • Slot bereits von anderem Handwerker gewaehlt → "Dieser Termin ist leider nicht mehr verfuegbar"

4.5 Technische Umsetzung

Neues Prisma Model:

model SchedulingRequest {
  id              String   @id @default(cuid())
  tenantId        String
  tenant          Tenant   @relation(fields: [tenantId], references: [id])

  // Was
  title           String
  description     String?
  urgency         SchedulingUrgency @default(NORMAL)

  // Wo
  propertyId      String?
  property        Property? @relation(fields: [propertyId], references: [id])
  unitId          String?
  unit            Unit?    @relation(fields: [unitId], references: [id])
  accessInfo      String?  // Zugangsinfo fuer Handwerker

  // Verknuepfungen
  ticketId        String?
  ticket          Ticket?  @relation(fields: [ticketId], references: [id])
  calendarEventId String?  // Wird gesetzt wenn Termin bestaetigt
  calendarEvent   CalendarEvent? @relation(fields: [calendarEventId], references: [id])

  // Status
  status          SchedulingRequestStatus @default(OFFEN)
  budgetLimit     Float?

  // Meta
  createdById     String
  createdBy       User     @relation(fields: [createdById], references: [id])
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt

  // Relations
  slots           SchedulingSlot[]
  invitations     SchedulingInvitation[]

  @@index([tenantId])
  @@index([ticketId])
  @@index([status])
}

model SchedulingSlot {
  id          String   @id @default(cuid())
  requestId   String
  request     SchedulingRequest @relation(fields: [requestId], references: [id], onDelete: Cascade)

  startDate   DateTime
  endDate     DateTime
  isAvailable Boolean  @default(true) // false wenn bereits gewaehlt

  // Wer hat diesen Slot gewaehlt?
  selectedById    String?
  selectedBy      SchedulingInvitation? @relation(fields: [selectedById], references: [id])
  selectedAt      DateTime?

  @@index([requestId])
}

model SchedulingInvitation {
  id              String   @id @default(cuid())
  requestId       String
  request         SchedulingRequest @relation(fields: [requestId], references: [id], onDelete: Cascade)

  serviceProviderId String
  serviceProvider ServiceProvider @relation(fields: [serviceProviderId], references: [id])

  // Auth
  token           String   @unique // JWT fuer signierte URL
  expiresAt       DateTime

  // Status
  status          InvitationStatus @default(GESENDET)
  sentAt          DateTime?
  viewedAt        DateTime?
  respondedAt     DateTime?

  // Antwort
  selectedSlot    SchedulingSlot?  // Welchen Slot hat er gewaehlt?
  counterProposal String?          // "Keiner passt" → Freitext

  @@index([requestId])
  @@index([token])
  @@index([serviceProviderId])
}

enum SchedulingUrgency {
  NORMAL       // Innerhalb 7 Tage
  DRINGEND     // Innerhalb 48h
  NOTFALL      // Heute
}

enum SchedulingRequestStatus {
  OFFEN        // Erstellt, noch keine Antwort
  ANGEFRAGT    // E-Mails versendet
  BESTAETIGT   // Handwerker hat Slot gewaehlt
  ABGELEHNT    // Alle Handwerker abgelehnt
  STORNIERT    // Von HV storniert
  ERLEDIGT     // Termin war, abgeschlossen
}

enum InvitationStatus {
  GESENDET     // E-Mail raus
  ANGESEHEN    // Link geoeffnet
  BESTAETIGT   // Slot gewaehlt
  ABGELEHNT    // "Keiner passt"
  ABGELAUFEN   // Token expired
}

Neue API-Endpoints:

POST   /api/v1/calendar/scheduling          — Terminanfrage erstellen + Slots + Einladungen
GET    /api/v1/calendar/scheduling          — Liste aller Anfragen (mit Status)
GET    /api/v1/calendar/scheduling/[id]     — Detail einer Anfrage
PATCH  /api/v1/calendar/scheduling/[id]     — Status aendern / stornieren
DELETE /api/v1/calendar/scheduling/[id]     — Anfrage loeschen

GET    /api/v1/calendar/booking/[token]     — Oeffentliche Seite: Slots anzeigen (kein Login)
POST   /api/v1/calendar/booking/[token]     — Oeffentliche Seite: Slot waehlen (kein Login)
POST   /api/v1/calendar/booking/[token]/counter — Gegenvorschlag senden

E-Mail-Templates (Resend):

  1. scheduling-request — Initiale Anfrage an Handwerker (mit Slot-Buttons)
  2. scheduling-confirmed — Bestaetigung an beide Seiten (mit ICS-Attachment)
  3. scheduling-reminder — 24h vorher an Handwerker
  4. scheduling-counter — Gegenvorschlag an Hausverwaltung
  5. scheduling-expired — Anfrage abgelaufen, keine Antwort

ICS-Generierung:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Xolib//Hausverwaltung//DE
BEGIN:VEVENT
DTSTART:20260317T090000
DTEND:20260317T120000
SUMMARY:Heizungsreparatur - Hansaring 12
LOCATION:Hansaring 12, Koeln
DESCRIPTION:Reparatur Heizung defekt. Zugang: Schluessel bei Frau Mueller 2.OG
ORGANIZER:CN=Mustermann HV:mailto:info@mustermann-hv.de
END:VEVENT
END:VCALENDAR

4.6 Parallele Handwerker-Anfragen

Wenn HV 3 Handwerker gleichzeitig anfragt: - Alle 3 erhalten E-Mail mit den gleichen Slots - First come, first served: Wer zuerst waehlt, bekommt den Termin - Die anderen erhalten: "Dieser Auftrag wurde bereits vergeben. Vielen Dank." - Oder: Modus "Alle bestaetigen" — mehrere Handwerker koennen verschiedene Slots waehlen (z.B. verschiedene Gewerke)

4.7 Responsiveness-Tracking (Data Moat)

Jede Handwerker-Interaktion erzeugt messbare Daten:

Reaktionszeit-Score:

Einladung gesendet → Einladung geoeffnet → Slot gewaehlt
     sentAt              viewedAt              respondedAt

Daraus berechnen wir pro Handwerker: - Durchschnittliche Reaktionszeit (sentAt → respondedAt) - Oeffnungsrate (wie oft wird die E-Mail ueberhaupt geoeffnet) - Annahmerate (wie oft wird ein Slot gewaehlt vs. "keiner passt") - Zuverlaessigkeitsrate (angenommene Termine vs. No-Shows)

Automatische Priorisierung:

Wenn Handwerker X in den letzten 5 Anfragen:
  - 3x nicht innerhalb von 48h geantwortet
  - 1x Termin abgesagt
  → Prioritaet sinkt: Wird bei naechster Anfrage nicht als Erster gefragt

Wenn Handwerker Y:
  - 5x innerhalb von 4h geantwortet
  - 0 No-Shows
  → Prioritaet steigt: Wird kuenftig als Erster vorgeschlagen

Das System lernt, welche Handwerker zuverlaessig sind — ohne dass die Hausverwaltung das manuell pflegen muss. Cross-Tenant: "Firma Mueller antwortet bei allen 12 Hausverwaltungen im Schnitt in 3.2h" wird systemweites Wissen.

Eskalationslogik: - Stufe 1: Anfrage an bevorzugten Handwerker (bester Score) - Nach 24h ohne Antwort → Stufe 2: Automatisch 2 weitere Handwerker anfragen - Nach 48h ohne Antwort → Stufe 3: Benachrichtigung an Admin + alle verfuegbaren Handwerker des Gewerks

4.8 Auftragsabschluss & Rueckmeldung

Nach dem Termin muss der Handwerker den Auftrag abschliessen — ueber den gleichen Link (kein Login):

Handwerker-Abschluss-Formular (Mobile-optimiert):

Auftrag: Heizungsreparatur, Hansaring 12

Status:
  [x] Vollstaendig erledigt
  [ ] Teilweise erledigt — Folgetermin noetig
  [ ] Nicht durchfuehrbar — Grund:

Was wurde gemacht:
  [Freitext: "Ventil ausgetauscht, Druck auf 1.5 bar eingestellt"]

Offene Punkte:
  [Freitext: "Thermostat sollte in 2 Wochen nochmal geprueft werden"]

Material verwendet:
  [Freitext: "1x Heizungsventil DN15, 2x Dichtung"]

Foto der Arbeit: [Upload] (optional, empfohlen)

Geschaetzte Arbeitszeit: [2.5h]

[Auftrag abschliessen]

Was das System daraus macht:

  1. CalendarEvent.status → ERLEDIGT (automatisch)
  2. Ticket-Status aktualisieren (wenn verknuepft: HANDWERKER_VOR_ORT → GELOEST)
  3. Arbeitsprotokoll erstellt (strukturierte Daten, nicht nur Freitext)
  4. Folgeaktion? Wenn "Folgetermin noetig" → neuer Terminvorschlag generiert
  5. Material-Tracking → Grundlage fuer Kostenvergleich (Cross-Tenant: "Ventil DN15 kostet im Schnitt 45 EUR")
  6. Foto-Dokumentation → Nachweis fuer Eigentuemer, Versicherung, BK-Abrechnung
  7. Arbeitszeit → Vergleichswert ("Heizungsreparatur dauert im Schnitt 1.8h")

Data Moat aus Auftragsabschluss:

Datenpunkt Cross-Tenant Lerneffekt
Arbeitszeit pro Gewerk "Rohrverstopfung = avg. 1.2h, Heizungswartung = avg. 2.5h"
Materialkosten "Ventil DN15 Preisspanne: 35-65 EUR" → Preisanomalie erkennen
Folgetermin-Quote "Firma X braucht bei 30% der Auftraege einen Folgetermin"
Foto-Qualitaet Verwalter bewertet Dokumentation → Learning fuer andere
Erledigungsquote "95% beim ersten Besuch erledigt" vs. "nur 60%"

4.9 Rechnungseinreichung ueber das System

Der Handwerker reicht seine Rechnung direkt ueber Xolib ein — kein E-Mail-Ping-Pong:

Flow:

1. Handwerker schliesst Auftrag ab (4.8)
2. System zeigt: "Rechnung einreichen" Button
3. Handwerker laedt PDF/Foto der Rechnung hoch
4. KI analysiert die Rechnung automatisch:
   - Rechnungsnummer, Datum, Betrag
   - Einzelpositionen extrahieren
   - Zuordnung: Objekt, Einheit, Ticket, Auftrag
   - Plausibilitaetspruefung:
     * Passt Betrag zur geschaetzten Arbeitszeit?
     * Sind Materialpreise im normalen Rahmen?
     * Stimmt die MwSt.?
     * Wurde ein Budget-Limit gesetzt und ueberschritten?
5. System zeigt Admin die analysierte Rechnung:
   "Rechnung #2024-0847 | Firma Mueller | 385,50 EUR
    → Zugeordnet: Hansaring 12, Ticket #1247
    → KI-Bewertung: Plausibel (Betrag im Rahmen, Material marktgerecht)
    → [Genehmigen] [Ablehnen] [Rueckfrage]"
6. Admin genehmigt → Rechnung wird als Payment erfasst
7. Optional: Automatische Genehmigung unter Schwellenwert (z.B. < 200 EUR)

Technisch:

model ServiceInvoice {
  id                String   @id @default(cuid())
  tenantId          String
  tenant            Tenant   @relation(fields: [tenantId], references: [id])

  // Verknuepfungen
  schedulingRequestId String?
  schedulingRequest SchedulingRequest? @relation(fields: [schedulingRequestId], references: [id])
  serviceProviderId String
  serviceProvider   ServiceProvider @relation(fields: [serviceProviderId], references: [id])
  calendarEventId   String?
  calendarEvent     CalendarEvent? @relation(fields: [calendarEventId], references: [id])
  ticketId          String?
  ticket            Ticket? @relation(fields: [ticketId], references: [id])
  propertyId        String?
  property          Property? @relation(fields: [propertyId], references: [id])

  // Rechnungsdaten
  invoiceNumber     String?
  invoiceDate       DateTime?
  amount            Float
  taxAmount         Float?
  currency          String   @default("EUR")

  // KI-Analyse
  aiExtractedData   Json?    // Extrahierte Positionen, Betraege
  aiConfidence      Float?   // Wie sicher ist die Zuordnung?
  aiPlausible       Boolean? // Ist der Betrag plausibel?
  aiWarnings        Json?    // Warnungen: Preis zu hoch, Budget ueberschritten

  // Dokument
  documentUrl       String   // Upload-Pfad
  documentType      String   @default("PDF") // PDF, JPG, PNG

  // Status
  status            InvoiceStatus @default(EINGEREICHT)
  approvedById      String?
  approvedBy        User?    @relation(fields: [approvedById], references: [id])
  approvedAt        DateTime?
  rejectionReason   String?
  paymentId         String?  // Verknuepfung mit Payment wenn bezahlt

  // Meta
  submittedAt       DateTime @default(now())
  updatedAt         DateTime @updatedAt

  @@index([tenantId])
  @@index([serviceProviderId])
  @@index([status])
  @@index([ticketId])
}

enum InvoiceStatus {
  EINGEREICHT      // Handwerker hat hochgeladen
  KI_GEPRUEFT      // KI hat analysiert
  GENEHMIGT        // Admin hat freigegeben
  ABGELEHNT        // Admin hat abgelehnt
  RUECKFRAGE       // Admin hat Rueckfrage an Handwerker
  BEZAHLT          // Payment erstellt
}

KI-Rechnungsanalyse (GPT-4o Vision):

// src/lib/ai/invoice-analyzer.ts
async function analyzeInvoice(imageUrl: string, context: InvoiceContext) {
  // GPT-4o analysiert das Rechnungsbild/PDF:
  // 1. OCR + Strukturerkennung
  // 2. Extraktion: Nummer, Datum, Positionen, Betraege, MwSt
  // 3. Zuordnung zum Kontext (Objekt, Ticket, Auftrag)
  // 4. Plausibilitaetspruefung:
  //    - Betrag vs. geschaetzte Arbeitszeit (aus Auftragsabschluss)
  //    - Materialpreise vs. Marktdurchschnitt (Cross-Tenant)
  //    - Budget-Limit Pruefung
  //    - MwSt-Satz korrekt (19% / 7%)
  // 5. Warnungen generieren wenn noetig
}

Auto-Genehmigung (konfigurierbar): - Schwellenwert pro Mandant: z.B. Rechnungen < 200 EUR automatisch genehmigen - Nur bei hoher KI-Confidence (> 0.9) und keine Warnungen - Admin wird trotzdem benachrichtigt (kann nachtraeglich pruefen) - Erstellt automatisch Payment-Eintrag

Data Moat aus Rechnungen:

Datenpunkt Nutzen
Preise pro Gewerk/Region "Heizungswartung in Koeln: 180-320 EUR" → Preisanomalie
Stundensaetze "Installateur avg. 65 EUR/h, Elektriker avg. 72 EUR/h"
Material-Preisvergleich "Ventil DN15 avg. 42 EUR" → Wucher erkennen
Rechnungs-Durchlaufzeit "Firma X reicht innerhalb 3 Tagen ein"
Reklamationsquote "2% der Rechnungen werden beanstandet"

4.10 Handwerker-Bewertung (erweitertes Scoring)

Nach jedem abgeschlossenen Auftragszyklus (Terminanfrage → Termin → Abschluss → Rechnung):

Automatischer Score (berechnet, nicht manuell):

Handwerker-Score = gewichteter Durchschnitt aus:
  Reaktionszeit     (20%) — Wie schnell antwortet er auf Anfragen?
  Zuverlaessigkeit  (25%) — No-Show-Rate, Termintreue
  Erledigungsquote  (20%) — Beim ersten Besuch geloest?
  Preisfairness     (15%) — Rechnungsbetraege vs. Marktdurchschnitt
  Dokumentation     (10%) — Vollstaendige Abschlussberichte?
  Bewertung HV      (10%) — Manuelle Bewertung durch Verwalter

Manuell zusaetzlich: - HV bewertet: Puenktlichkeit (1-5), Qualitaet (1-5), Kommentar - Fuettert den Score, ueberschreibt ihn nicht

Cross-Tenant: - "Firma Mueller: Score 4.7/5 bei 12 Hausverwaltungen, 87 Auftraege" - "Firma Schmidt: Score 3.2/5 — haeufig Folgetermine noetig" - Anonymisiert: Andere HVs sehen nur Score + Statistik, keine Details


5. Rollenbasierte Sichtbarkeit

5.1 Sichtbarkeitsmatrix

Feld / Inhalt OWNER_ADMIN ADMIN HAUSHELD_WORKER TENANT OWNER
Alle Termine x x
Eigene Termine x x x
Termine eigenes Objekt x x x x x
Termin-Details (Kosten, Notizen) x x
Interne Termine x x
Private Termine Ersteller
Handwerker-Kosten x x x
Zugangsinfo x x x
Mieter-Kontaktdaten x x
Scheduling-Anfragen x x

5.2 API-Filterung

GET /api/v1/calendar wird rollenabhaengig:

// OWNER_ADMIN / ADMIN → alle Termine des Mandanten
// HAUSHELD_WORKER → assignedToId === userId ODER propertyId IN zugewiesene Objekte
// TENANT → propertyId === eigenes Objekt AND isPrivate === false AND eventType IN sichtbare Typen
// OWNER → propertyId IN eigene Objekte AND isPrivate === false

Sichtbare Typen fuer TENANT: WARTUNG, BESICHTIGUNG, MIETER_TERMIN, ABLESUNG, BEGEHUNG, HANDWERKER_TERMIN Nicht sichtbar fuer TENANT: INTERN, FRIST, WEG_VERSAMMLUNG (nur OWNER)

5.3 Portal-Integration

Mieter-Portal (/portal/calendar): - Einfache Listenansicht der naechsten relevanten Termine - "Wartung am 17.03. zwischen 10-12 Uhr an Ihrem Objekt" - Push-Benachrichtigung 24h vorher - Keine Erstellung, nur Ansicht

Eigentuemer-Portal (/portal/owner/calendar): - Monats-/Listenansicht fuer eigene Objekte - WEG-Versammlungen hervorgehoben - Handwerker-Einsaetze sichtbar


6. Intelligenz-Schichten

6.1 Kollisionserkennung (Phase 1 — implementiert)

  • Person an 2 Orten gleichzeitig → Warnung bei Erstellung
  • API: GET /api/v1/calendar/conflicts
  • UI: Gelbe Warnbox im Erstellformular

6.2 Kapazitaetsanzeige (Phase 2)

Pro Mitarbeiter/Woche: - Geplante Stunden berechnen (Summe aller nicht-abgesagten Events) - Soll-Stunden: 40h (konfigurierbar pro User) - Auslastung: geplante/soll * 100% - Anzeige: Farbiger Balken unter Mitarbeiter-Name in Ressourcen-View - Warnung bei >80%: "Mitarbeiter Mueller ist diese Woche zu 92% ausgelastet"

6.3 Automatische Fristentermine (Phase 2)

KI-Agenten pruefen regelmaessig:

Quelle Frist-Logik Kalender-Eintrag
Lease.endDate 6 Monate + 3 Monate vorher FRIST "Kuendigungsfrist Mietvertrag"
TaxCertificate.expiryDate 90 + 30 Tage vorher FRIST "Freistellungsbescheinigung laeuft ab"
OperatingCostSettlement 12 Monate nach Abrechnungszeitraum FRIST "BK-Abrechnung faellig"
GewerbeKaution.buergschaftBis 90 Tage vorher FRIST "Buergschaft laeuft ab"
Property.lastBegehung 12 Monate danach BEGEHUNG "Jaehrliche Objektbegehung"
Meter.nextReading basierend auf Ablesung-Intervall ABLESUNG "Zaehlerablesung"
WegVersammlung.date bei Erstellung WEG_VERSAMMLUNG

Diese werden als aiGenerated: true erstellt. Verwalter kann bestaetigen oder loeschen.

6.4 Verfuegbarkeits-Check (Phase 3)

Bevor ein Termin erstellt wird: - Zeige freie Slots des ausgewaehlten Mitarbeiters - Gruen = frei, Grau = belegt - Klick auf freien Slot → Uhrzeit wird uebernommen

6.5 Routenvorschlag (Phase 4)

Wenn ein Mitarbeiter mehrere Termine am selben Tag hat: - Objekt-Adressen geocoden (Google Maps API oder OpenStreetMap) - Optimale Reihenfolge berechnen (TSP / naechster Nachbar) - Vorschlag: "Empfohlene Route: 1. Hansaring → 2. Schillerstr → 3. Stadtgraben (gespart: ~25 min)"

6.6 Handwerker-Empfehlung (Phase 4)

Bei neuer Terminanfrage: - System schlaegt Handwerker vor basierend auf: - Gewerk passt (Heizung → Heizungsbauer) - Bewertung (beste zuerst) - Reaktionszeit (schnellste zuerst) - Verfuegbarkeit (hat letzte 3 Anfragen schnell beantwortet) - Naehe zum Objekt (wenn Adresse bekannt) - "Empfehlung: Firma Mueller (4.8/5, avg. 1.2 Tage Reaktionszeit)"


7. Erinnerungen & Benachrichtigungen

7.1 Erinnerungslogik

Jeder Termin hat reminderMinutes. Ein Background-Job (Agent-Cron) prueft:

Alle Events WHERE reminderSent = false
  AND startDate - reminderMinutes <= NOW
  → Erstelle Notification fuer assignedTo + createdBy + participants
  → Setze reminderSent = true

7.2 Benachrichtigungskanale

Kanal Wann Wer
In-App (Notification Model) Immer Alle mit Portal-Zugang
E-Mail (Resend) Bei Terminbestaetigung, 24h Erinnerung Alle mit E-Mail
ICS-Datei Bei Erstellung/Aenderung Als Attachment in E-Mail
Push (PWA) Bei neuem Termin, Erinnerung Alle mit installierter PWA

7.3 Neue Notifications

// Typen fuer calendar-bezogene Notifications
'calendar.reminder'           // Termin in X Minuten
'calendar.new_assignment'     // Neuer Termin zugewiesen
'calendar.status_changed'     // Status geaendert (bestaetigt/verschoben/abgesagt)
'calendar.conflict_detected'  // Terminkonflikt erkannt
'calendar.scheduling_response'// Handwerker hat auf Anfrage reagiert
'calendar.deadline_approaching'// Frist in X Tagen

8. Querverknuepfungen

8.1 Bestehende Entitaeten → Kalender

Entitaet Kalender-Aktion Richtung
Ticket (HANDWERKER_NOETIG) → HANDWERKER_TERMIN erstellen Ticket → Kalender
ServiceOrder → WARTUNG Termin erstellen ServiceOrder → Kalender
WegVersammlung → WEG_VERSAMMLUNG Termin WEG → Kalender
Lease (neuer Mieter) → MIETER_TERMIN (Uebergabe) Lease → Kalender
HandoverProtocol → MIETER_TERMIN (Rueckgabe) Handover → Kalender
Meter (naechste Ablesung) → ABLESUNG Termin Meter → Kalender
CalendarEvent → verlinkt mit Ticket/Property/Unit Kalender → alles

8.2 Kalender → bestehende Entitaeten (Rueckverknuepfung)

  • Termin als "Erledigt" markiert → kann automatisch Ticket-Status updaten
  • Handwerker-Termin abgeschlossen → ServiceOrder-Status updaten
  • WEG-Versammlung erledigt → Protokoll-Erstellung vorschlagen
  • Begehung erledigt → Property-Score aktualisieren (Trigger)

8.3 UI-Querverknuepfung

  • Ticket-Detailseite: "Zugehoerige Termine" Section
  • Property-Detailseite: "Naechste Termine" Widget
  • Handwerker-Detailseite: "Letzte / Naechste Einsaetze"
  • Dashboard: "Heute 5 Termine, 2 ueberfaellig"

9. Technische Architektur

9.1 Bestehende Models (bereits implementiert)

  • CalendarEvent — Basis-Termin (13 Relationen, polymorphes Design)
  • CalendarParticipant — Teilnehmer (System-User + externe)

9.2 Neue Models (geplant)

  • SchedulingRequest — Terminanfrage an Handwerker
  • SchedulingSlot — Verfuegbare Zeitfenster
  • SchedulingInvitation — Einladung an einzelnen Handwerker
  • ServiceInvoice — Handwerker-Rechnung mit KI-Analyse
  • ServiceCompletionReport — Auftragsabschluss-Bericht

9.3 Neue API-Endpoints (geplant)

GET    /api/v1/calendar/conflicts           — Kollisionspruefung (implementiert)
POST   /api/v1/calendar/scheduling          — Terminanfrage erstellen
GET    /api/v1/calendar/scheduling          — Anfragen-Liste
GET    /api/v1/calendar/booking/[token]     — Oeffentliche Slot-Auswahl (kein Login)
POST   /api/v1/calendar/booking/[token]     — Slot bestaetigen (kein Login)
POST   /api/v1/calendar/booking/[token]/complete — Auftrag abschliessen (kein Login)
POST   /api/v1/calendar/booking/[token]/invoice  — Rechnung einreichen (kein Login)
GET    /api/v1/service-invoices             — Rechnungsliste (Admin)
PATCH  /api/v1/service-invoices/[id]        — Genehmigen / Ablehnen
GET    /api/v1/craftsmen/[id]/score         — Handwerker-Score + Statistiken
GET    /api/v1/my/calendar                  — Mieter-Portal: eigene relevante Termine
GET    /api/v1/owner/calendar               — Eigentuemer-Portal: Objekt-Termine

9.4 ICS-Generator (Utility)

// src/lib/calendar/ics.ts
generateICS(event: CalendarEvent): string
// Erzeugt RFC 5545 kompatible ICS-Datei
// Unterstuetzt: VEVENT, VTIMEZONE, VALARM (Erinnerung)

9.5 Background-Jobs (Agent-Cron Erweiterung)

1. checkReminders()     — alle 5 Min: faellige Erinnerungen → Notifications
2. checkFristen()       — 1x taeglich: Ablaufende Fristen → Kalender-Eintraege
3. checkScheduling()    — alle 10 Min: Abgelaufene Einladungen → Status update
4. sendDailyDigest()    — 07:00 morgens: "Ihre heutigen Termine" E-Mail

10. Implementierungsreihenfolge

Phase 1: Basis-Kalender (AKTUELL — 80% fertig)

  • Prisma Models (CalendarEvent + CalendarParticipant)
  • CRUD API + Participant API
  • Seed-Daten (10 Events)
  • i18n (de + en)
  • Kollisionserkennung API
  • Admin-Seite: Wochen-/Monats-/Listenansicht
  • Erweitertes Formular (Mitarbeiter, Handwerker, Einheit)
  • Rollenfix: HAUSHELD_WORKER in allen Routes (system-weit)
  • TypeScript-Check + Deploy

Phase 2: Intelligente Ansichten

  • Ressourcen-Ansicht (Wer ist wann wo?)
  • Objekt-Ansicht (Was passiert wo?)
  • Kapazitaetsanzeige pro Mitarbeiter
  • Tagesansicht (Detail-View eines Tages)
  • Heute-Linie in Wochenansicht

Phase 3a: Handwerker-Terminabstimmung

  • Prisma Models (SchedulingRequest + Slot + Invitation)
  • API: Scheduling CRUD + oeffentliche Booking-Route
  • E-Mail-Templates (Resend): Anfrage, Bestaetigung, Erinnerung
  • ICS-Generator
  • Handwerker-Landingpage (oeffentlich, mobile-optimiert)
  • Slot-Auswahl + automatische Kalender-Erstellung
  • Parallele Anfragen (first come, first served)
  • Responsiveness-Tracking (Reaktionszeit, Oeffnungsrate, Annahmerate)
  • Eskalationslogik (24h → mehr Handwerker, 48h → Admin-Alert)

Phase 3b: Auftragsabschluss & Rechnungen

  • Auftragsabschluss-Formular (via Token-Link, kein Login)
  • Foto-Upload fuer Arbeitsdokumentation
  • ServiceInvoice Model + Rechnungs-Upload
  • KI-Rechnungsanalyse (GPT-4o Vision: OCR + Plausibilitaet)
  • Admin-Rechnungs-Uebersicht (Genehmigen/Ablehnen/Rueckfrage)
  • Auto-Genehmigung unter Schwellenwert (konfigurierbar)
  • Payment-Erstellung bei Genehmigung
  • Handwerker-Score (automatisch: Reaktion+Zuverlaessigkeit+Preis+Qualitaet)

Phase 4: Erinnerungen & Benachrichtigungen

  • Background-Job: Erinnerungen pruefen → Notifications erstellen
  • E-Mail-Erinnerungen (24h vorher)
  • Daily Digest ("Ihre heutigen Termine")
  • PWA Push-Notifications

Phase 5: Automatische Fristentermine

  • Agent-Actions: Ingenieur, Finanzwaechter, Rechtswatcher → CalendarEvent erstellen
  • Frist-Logik in Agent-Cron integrieren
  • Trigger bei Ticket-Eskalation → Handwerker-Termin vorschlagen
  • Trigger bei neuem Mieter → Uebergabe-Termin

Phase 6: Portal-Integration

  • /api/v1/my/calendar (Mieter-Termine)
  • /api/v1/owner/calendar (Eigentuemer-Termine)
  • Mieter-Portal Kalender-Tab
  • Eigentuemer-Portal Kalender-Tab
  • Rollenbasierte Filterung in API

Phase 7: Querverknuepfung & Rueckkanal

  • Ticket-Detailseite: zugehoerige Termine
  • Property-Seite: naechste Termine Widget
  • Handwerker-Seite: Einsatz-Historie
  • Dashboard: Tages-Zusammenfassung
  • Termin erledigt → Ticket-Status aktualisieren
  • Handwerker-Bewertung nach Abschluss

Phase 8: Erweiterte Intelligenz

  • Verfuegbarkeits-Check (freie Slots anzeigen)
  • Routenvorschlag (Geocoding + TSP)
  • Handwerker-Empfehlung (Rating + Reaktionszeit + Naehe)
  • Kalendar-Abo (ICS-Feed URL pro User/Objekt)

11. KPIs & Data Moat

Messbare Werte

KPI Berechnung Data Moat
Handwerker-Reaktionszeit invitation.sentAt → invitation.respondedAt Cross-Tenant Benchmark
No-Show-Rate Termine mit Status ERLEDIGT vs ABGESAGT vs keine Reaktion Handwerker-Qualitaet
Terminauslastung geplante Stunden / Soll-Stunden pro Mitarbeiter Effizienz-Vergleich
Frist-Einhaltung Wie oft werden Frist-Termine eingehalten? Compliance-Score
Agent-Genauigkeit Wie oft werden KI-generierte Termine bestaetigt vs geloescht? Agent-Learning
Durchschnittliche Vorlaufzeit Termin-Erstellung → Termin-Datum Branchenvergleich

Cross-Tenant Lerneffekte

  • "Heizungswartungen dauern im Durchschnitt 2.5h" → bessere Standard-Dauer
  • "Handwerker-Firma X antwortet innerhalb 4h" → Empfehlungs-Ranking
  • "Die meisten BK-Abrechnungen werden 2 Monate vor Frist erstellt" → optimale Erinnerungszeit
  • "Mieter-Uebergaben dauern durchschnittlich 45 Minuten" → Standard-Slot-Laenge
  • "Installateur-Stundensatz in Koeln: avg. 65 EUR/h" → Preisanomalie erkennen
  • "Ventil DN15: Preisspanne 35-65 EUR" → Wucher bei Materialkosten erkennen
  • "Firma Y braucht bei 30% der Auftraege einen Folgetermin" → Qualitaetsvergleich
  • "Heizungsreparaturen werden zu 85% beim ersten Besuch geloest" → Branchenbenchmark
  • "Durchschnittliche Rechnungs-Durchlaufzeit: 4.2 Tage" → Professionalisierungs-Indikator

Handwerker-Schnittstelle als eigener Data Moat

Die Kombination aus Terminabstimmung + Auftragsabschluss + Rechnungseinreichung erzeugt einen geschlossenen Datenzyklus:

Anfrage → Reaktionszeit → Termin → Arbeitsprotokoll → Rechnung → Bewertung
   ↑                                                                    │
   └────────────────── Lernt fuer naechste Anfrage ←────────────────────┘

Warum das ein Moat ist: 1. Handwerker gewoehnen sich an den Workflow (Switching Cost fuer beide Seiten) 2. Je mehr Auftraege, desto genauer der Handwerker-Score (Netzwerkeffekt) 3. Preisdaten + Arbeitszeiten werden Cross-Tenant Benchmark (kein Einzelkunde kann das) 4. KI-Rechnungsanalyse wird mit jedem Invoice besser (Lerneffekt) 5. Hausverwaltung verliert bei Wechsel: alle Handwerker-Bewertungen + Preis-Historie

Weitergehendes Konzept: Die Handwerker-Schnittstelle wird zum eigenstaendigen Marktplatz mit Mitgliedschafts-Modell und Finanzprodukten. Siehe HANDWERKER-MARKTPLATZ.md fuer das vollstaendige Konzept (Handwerker-Portal, Monetarisierung, Express Pay, Platzierungs-Algorithmus, Revenue-Projektion).


12. Geschaetzter Aufwand

Phase Umfang Aufwand
Phase 1 (Basis) Fertigstellung + Deploy ~2h
Phase 2 (Ansichten) 2 neue Views + Kapazitaet ~4-6h
Phase 3a (Handwerker-Scheduling) Models + API + E-Mail + Landing + Tracking ~8-12h
Phase 3b (Abschluss + Rechnungen) Completion-Report + Invoice + KI-Analyse ~8-10h
Phase 4 (Erinnerungen) Background-Job + E-Mail ~3-4h
Phase 5 (Auto-Fristen) Agent-Integration ~4-6h
Phase 6 (Portal) 2 Portal-Ansichten ~3-4h
Phase 7 (Querverknuepfung) Widgets + Rueckkanal ~4-6h
Phase 8 (Intelligenz) Geocoding + Empfehlung ~6-8h
Gesamt ~43-58h