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):
scheduling-request— Initiale Anfrage an Handwerker (mit Slot-Buttons)scheduling-confirmed— Bestaetigung an beide Seiten (mit ICS-Attachment)scheduling-reminder— 24h vorher an Handwerkerscheduling-counter— Gegenvorschlag an Hausverwaltungscheduling-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:
- CalendarEvent.status → ERLEDIGT (automatisch)
- Ticket-Status aktualisieren (wenn verknuepft: HANDWERKER_VOR_ORT → GELOEST)
- Arbeitsprotokoll erstellt (strukturierte Daten, nicht nur Freitext)
- Folgeaktion? Wenn "Folgetermin noetig" → neuer Terminvorschlag generiert
- Material-Tracking → Grundlage fuer Kostenvergleich (Cross-Tenant: "Ventil DN15 kostet im Schnitt 45 EUR")
- Foto-Dokumentation → Nachweis fuer Eigentuemer, Versicherung, BK-Abrechnung
- 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 HandwerkerSchedulingSlot— Verfuegbare ZeitfensterSchedulingInvitation— Einladung an einzelnen HandwerkerServiceInvoice— Handwerker-Rechnung mit KI-AnalyseServiceCompletionReport— 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.mdfuer 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 |