opencrm/README.md

23 KiB
Raw Blame History

OpenCRM

Web-basiertes CRM-System für Kundenverwaltung mit Verträgen (Energie, Telekommunikation, KFZ-Versicherung).

Features

  • Kundenverwaltung: Privat- und Geschäftskunden mit Stammdaten
  • Adressen: Mehrere Liefer-/Melde- und Rechnungsadressen pro Kunde
  • Bankkarten: Mit Ablaufdatum, Aktiv-Status und Dokument-Upload (PDF)
  • Ausweise: Personalausweis, Reisepass, etc. mit Ablaufdatum und Dokument-Upload (PDF)
  • Zähler: Strom-/Gaszähler mit Zählerstandhistorie
  • Rechnungen: Rechnungsverwaltung für Energieverträge mit Dokumenten-Upload
  • Vertrags-Cockpit: Dashboard zur Überwachung offener Aufgaben (fehlende Dokumente, Rechnungen)
  • Verträge:
    • Energie (Strom, Gas)
    • Telekommunikation (DSL, Glasfaser, Mobilfunk, TV)
    • KFZ-Versicherung
  • Folgeverträge: Automatische Datenübernahme bei Anbieterwechsel
  • Vertriebsplattformen: Verwaltbar über WebUI
  • Email-Provisionierung: Automatische E-Mail-Weiterleitung bei Plesk/cPanel/DirectAdmin
  • Berechtigungssystem: Admin, Mitarbeiter, Nur-Lesen, Kundenportal
  • Verschlüsselte Zugangsdaten: Portal-Passwörter AES-256-GCM verschlüsselt
  • Developer-Tools: Datenbank-Browser und interaktives ER-Diagramm

Tech Stack

  • Frontend: React 18, TypeScript, Tailwind CSS, React Query
  • Backend: Node.js, Express 4.x, TypeScript
  • Datenbank: MariaDB
  • ORM: Prisma
  • Auth: JWT mit Rollen-basierter Zugriffskontrolle

Hinweis zu Express 5: Das Projekt verwendet bewusst Express 4.x (nicht 5.x). Express 5 ist seit Jahren in der Beta-Phase und noch nicht offiziell stable. Bei der Installation darauf achten, dass @types/express zur Express-Version passt:

  • Express 4.x → @types/express@^4.17.x
  • Express 5.x → @types/express@^5.x (erst bei offiziellem Release empfohlen)

Voraussetzungen

  • Node.js 18+ (empfohlen: 20+)
  • Docker & Docker Compose
  • npm

Installation

1. Repository klonen

git clone <repository-url>
cd opencrm

2. MariaDB-Datenbank starten

docker-compose up -d

Dies startet einen MariaDB-Container mit:

  • Port: 3306
  • Datenbank: opencrm
  • Root-Passwort: rootpassword
  • Benutzer: opencrm / opencrm123

Warte ca. 10 Sekunden bis die Datenbank bereit ist.

3. Backend einrichten

cd backend

# Dependencies installieren
npm install

# .env-Datei erstellen (falls noch nicht vorhanden)
cp .env.example .env

Die .env-Datei sollte folgende Werte enthalten:

# Database
DATABASE_URL="mysql://root:rootpassword@localhost:3306/opencrm"

# JWT
JWT_SECRET="change-this-to-a-very-long-random-secret-in-production"
JWT_EXPIRES_IN="7d"

# Encryption (for portal credentials) - generate with: openssl rand -hex 32
ENCRYPTION_KEY="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"

# Server
PORT=3001
NODE_ENV=development

4. Datenbank initialisieren

# Prisma Client generieren und Migrationen ausführen
npx prisma migrate dev

# Seed-Daten einspielen (Admin-User, Rollen, Berechtigungen)
npm run db:seed

5. Frontend einrichten

cd ../frontend

# Dependencies installieren
npm install

Anwendung starten

Backend starten (Terminal 1)

cd backend
npm run dev

Das Backend läuft auf http://localhost:3001

Frontend starten (Terminal 2)

cd frontend
npm run dev

Das Frontend läuft auf http://localhost:5173

Erster Login

Nach dem Seed sind folgende Zugangsdaten verfügbar:

Developer-Tools aktivieren

Die Developer-Tools (Datenbankstruktur, ER-Diagramm) sind standardmäßig für Admins verfügbar. Falls der Menüpunkt nicht erscheint:

  1. Einmalig im Browser-Console ausführen:
    fetch('/api/developer/setup', { method: 'POST' })
    
  2. Ausloggen und neu einloggen

Alternativ können Developer-Rechte pro Benutzer vergeben werden:

  • Benutzer bearbeiten > "Entwicklerzugriff" aktivieren

Email-Provisionierung

Das System unterstützt die automatische Erstellung von E-Mail-Weiterleitungen auf Hosting-Servern für Stressfrei-Wechseln Adressen.

Unterstützte Provider

  • Plesk (implementiert)
  • cPanel (vorbereitet)
  • DirectAdmin (vorbereitet)

Konfiguration

  1. EinstellungenEmail-Provisionierung öffnen
  2. Neuen Provider hinzufügen:
    • Name: Bezeichnung (z.B. "Plesk Hauptserver")
    • Typ: Plesk/cPanel/DirectAdmin
    • API-URL: Server-URL (z.B. https://server.de:8443)
    • Benutzername/Passwort: API-Zugangsdaten
    • Domain: E-Mail-Domain (z.B. stressfrei-wechseln.de)
    • Standard-Weiterleitung: Zusätzliche Weiterleitungsadresse (optional)
  3. Provider als "Standard" und "Aktiv" markieren
  4. Verbindung testen

Verwendung

Beim Anlegen einer Stressfrei-Wechseln Adresse im Kundenbereich erscheint die Checkbox "Beim E-Mail-Provider anlegen", wenn:

  • Ein aktiver Standard-Provider konfiguriert ist
  • Der Kunde eine E-Mail-Adresse hat

Bei aktivierter Checkbox wird automatisch:

  1. Geprüft, ob die E-Mail-Adresse bereits existiert
  2. Falls nicht: E-Mail-Adresse beim Provider angelegt mit Weiterleitung an:
    • Kunden-E-Mail-Adresse
    • Standard-Weiterleitungsadresse (falls konfiguriert)

Befehle

Backend

npm run dev          # Entwicklungsserver starten
npm run build        # Produktions-Build erstellen
npm run db:studio    # Prisma Studio (Datenbank-GUI)
npm run db:migrate   # Neue Migration erstellen
npm run db:seed      # Seed erneut ausführen

Frontend

npm run dev      # Entwicklungsserver starten
npm run build    # Produktions-Build erstellen
npm run preview  # Build-Vorschau

Docker

docker-compose up -d       # Container starten
docker-compose down        # Container stoppen
docker-compose down -v     # Container stoppen + Daten löschen
docker-compose logs -f     # Logs anzeigen

Projektstruktur

opencrm/
├── backend/
│   ├── src/
│   │   ├── controllers/    # Request-Handler
│   │   ├── middleware/     # Auth, Validation
│   │   ├── routes/         # API-Endpunkte
│   │   ├── services/       # Business-Logik
│   │   ├── types/          # TypeScript-Typen
│   │   └── index.ts        # Server-Einstiegspunkt
│   ├── prisma/
│   │   ├── schema.prisma   # Datenbank-Schema
│   │   └── seed.ts         # Seed-Daten
│   ├── uploads/            # Hochgeladene Dokumente
│   │   ├── bank-cards/     # Bankkarten-Dokumente
│   │   ├── documents/      # Ausweis-Scans
│   │   ├── invoices/       # Rechnungsdokumente (Strom/Gas)
│   │   ├── business-registrations/    # Gewerbeanmeldungen
│   │   ├── commercial-registers/      # Handelsregisterauszüge
│   │   ├── privacy-policies/          # Datenschutzerklärungen
│   │   ├── cancellation-letters/      # Kündigungsschreiben
│   │   ├── cancellation-confirmations/ # Kündigungsbestätigungen
│   │   └── cancellation-*-options/    # Kündigungsdokumente Optionen
│   └── package.json
├── frontend/
│   ├── src/
│   │   ├── components/     # UI-Komponenten
│   │   ├── pages/          # Seiten
│   │   ├── hooks/          # Custom Hooks
│   │   ├── services/       # API-Client
│   │   ├── types/          # TypeScript-Typen
│   │   └── App.tsx         # Haupt-Komponente
│   └── package.json
├── docker-compose.yml      # MariaDB-Container
└── README.md

Berechtigungen

Rolle Kunden Verträge Benutzer Plattformen Developer
Admin CRUD CRUD CRUD CRUD Optional
Mitarbeiter CRUD CRUD - Lesen -
Mitarbeiter (Lesen) Lesen Lesen - Lesen -
Kunde Eigene Eigene - - -

Troubleshooting

Datenbank-Verbindungsfehler

  1. Prüfe ob Container läuft: docker ps
  2. Prüfe die DATABASE_URL in .env
  3. Warte nach Container-Start ca. 10 Sekunden

Prisma-Fehler

# Prisma Client neu generieren
npx prisma generate

# Schema zur Datenbank pushen (ohne Migration)
npx prisma db push

Port bereits belegt

  • Backend: PORT in .env ändern
  • Frontend: In vite.config.ts anpassen

Developer-Menü fehlt

# In der Browser-Console:
fetch('/api/developer/setup', { method: 'POST' }).then(r => r.json()).then(console.log)
# Danach ausloggen und neu einloggen

Vertragstypen

Standard-Vertragstypen

Folgende Vertragstypen werden bei Installation/Factory-Reset automatisch angelegt:

Code Name Icon Farbe
ELECTRICITY Strom Zap #FFC107
GAS Gas Flame #FF5722
DSL DSL Wifi #2196F3
FIBER Glasfaser Cable #9C27B0
CABLE Kabel Internet (Coax) Cable #00BCD4
MOBILE Mobilfunk Smartphone #4CAF50
TV TV Tv #E91E63
CAR_INSURANCE KFZ-Versicherung Car #607D8B

Hinweis: Vertragstypen können nur von Benutzern mit Entwicklerzugriff geändert werden, da Änderungen auch Anpassungen an den Formularen erfordern.

Vertragstyp-spezifische Felder

Je nach Vertragstyp werden unterschiedliche Felder im Formular angezeigt:

Strom & Gas (ELECTRICITY, GAS)

  • Zähler-Auswahl
  • Jahresverbrauch (kWh/m³)
  • Grundpreis, Arbeitspreis
  • Bonus
  • Vorversorger, Kundennummer beim Vorversorger

Internet (DSL, CABLE, FIBER)

  • Download/Upload (Mbit/s)
  • Router Modell, Seriennummer
  • Installationsdatum
  • Benutzername, Passwort
  • Rufnummern mit SIP-Zugangsdaten
Vertragstyp Zusatzfeld
Glasfaser (FIBER) Home-ID

Mobilfunk (MOBILE)

  • Datenvolumen (GB)
  • Inklusiv-Minuten, Inklusiv-SMS
  • Gerät-Modell, IMEI
  • Multisim-Checkbox
  • SIM-Karten (dynamisch erweiterbar):
    • Rufnummer, SIM-Kartennummer (ICCID)
    • PIN, PUK (verschlüsselt)
    • Multisim-Flag, Hauptkarte-Flag

Hinweis Multisim: Nicht buchbar bei Klarmobil, Congstar, Otelo. Benötigt Freenet oder vergleichbar.

TV

  • Receiver Modell
  • Smartcard-Nummer
  • Paket/Angebot

KFZ-Versicherung (CAR_INSURANCE)

  • Kennzeichen, HSN, TSN, FIN/VIN
  • Fahrzeugtyp, Erstzulassung
  • SF-Klasse (Schadenfreiheitsklasse)
  • Versicherungsart (Haftpflicht/Teilkasko/Vollkasko)
  • Selbstbeteiligungen (Teilkasko, Vollkasko)
  • Versicherungsscheinnummer
  • Vorversicherer

Standard-Anbieter

Folgende Anbieter werden bei Installation/Factory-Reset automatisch angelegt:

Anbieter Portal-URL
Vodafone https://www.vodafone.de/meinvodafone/account/login
Klarmobil https://www.klarmobil.de/login
Otelo https://www.otelo.de/mein-otelo/login
Congstar https://www.congstar.de/login/
Telekom https://www.telekom.de/kundencenter/startseite
O2 https://www.o2online.de/ecare/selfcare
1&1 https://control-center.1und1.de/

Anbieter-spezifische Felder

Einige Felder werden nur bei bestimmten Anbietern angezeigt:

Anbieter Vertragstyp Zusatzfeld
Vodafone DSL, Kabel Internet Aktivierungscode

Hinweis Multisim: Bei Klarmobil, Congstar und Otelo ist Multisim nicht buchbar. Dafür wird Freenet oder ein vergleichbarer Anbieter benötigt.

Rechnungsverwaltung (Energieverträge)

Für Strom- und Gas-Verträge können Rechnungen verwaltet werden, um den Abrechnungsstatus zu tracken.

Rechnungstypen

Typ Beschreibung
Zwischenrechnung (INTERIM) Reguläre Jahresrechnung während der Vertragslaufzeit
Schlussrechnung (FINAL) Endabrechnung nach Vertragskündigung/Deaktivierung
Nicht verfügbar (NOT_AVAILABLE) Rechnung ist nicht mehr zu bekommen (z.B. Anbieter existiert nicht mehr)

Funktionen

  • Rechnungen hinzufügen/bearbeiten/löschen in der Vertragsdetailansicht
  • Dokument-Upload (PDF) - Pflicht, außer bei Typ "Nicht verfügbar"
  • Statusanzeige: Grünes Badge bei Schlussrechnung, Orange bei fehlender Schlussrechnung
  • E-Mail-Anhänge als Rechnung speichern: Direkt aus dem E-Mail-Client

E-Mail-Integration

Bei E-Mails, die einem Energievertrag zugeordnet sind:

  1. Toggle zwischen "Als Dokument" und "Als Rechnung" im Speichern-Dialog
  2. Rechnungsdatum und Typ auswählen
  3. Anhang wird automatisch als Rechnungsdokument gespeichert

Vertrags-Cockpit

Dashboard zur Überwachung offener Aufgaben und fehlender Dokumente.

Kategorien

Kategorie Prüfungen
Fehlende Dokumente Kündigungsschreiben, Kündigungsbestätigung (wenn Kündigung markiert)
Fehlende Rechnungen Schluss-/Zwischenrechnungen für Energieverträge
Ablaufende Dokumente Ausweise, Bankkarten (nächste 30 Tage)

Rechnungsprüfung für Energieverträge

Schlussrechnung (gekündigte/deaktivierte Verträge)

Status Prüfung
CANCELLED / DEACTIVATED Schlussrechnung oder "Nicht verfügbar" erforderlich

Zwischenrechnung (laufende Verträge)

Bedingung Warnung
Vertrag > 12 Monate alt, keine Rechnung "Zwischenrechnung fehlt"
Letzte Rechnung > 12 Monate her "Zwischenrechnung überfällig"

Hinweis Status-Logik:

  • EXPIRED = Laufzeit abgelaufen, aber Vertrag läuft ohne Kündigung weiter → Zwischenrechnung prüfen
  • CANCELLED = Aktive Kündigung → Schlussrechnung prüfen
  • DEACTIVATED = Manuell beendet → Schlussrechnung prüfen

Status-Info Modal

An verschiedenen Stellen (Vertragsformular, Vertragsdetails, Vertragsübersicht, Kundenansicht) zeigt ein -Icon neben dem Status eine Erklärung aller Vertragsstatus:

Status Bedeutung
Entwurf Vertrag wird noch vorbereitet
Ausstehend Wartet auf Aktivierung
Aktiv Vertrag läuft normal
Abgelaufen Laufzeit vorbei, läuft aber ohne Kündigung weiter
Gekündigt Aktive Kündigung eingereicht, Vertrag endet
Deaktiviert Manuell beendet/archiviert

E-Mail-Client

Ein vollständig integrierter E-Mail-Client pro Kunde mit IMAP-Empfang und SMTP-Versand.

Funktionen

E-Mails lesen & verwalten

  • E-Mail-Tab in Kundenansicht mit Ordnern: Posteingang, Gesendet, Papierkorb
  • Mehrere E-Mail-Konten pro Kunde (Dropdown zur Auswahl)
  • E-Mail-Detailansicht mit HTML/Text-Body, Absender, Empfänger, CC, Datum
  • Gelesen/Ungelesen markieren
  • Favoriten (Stern) für wichtige E-Mails
  • Papierkorb mit Wiederherstellen und endgültigem Löschen

E-Mails schreiben

  • Neue E-Mail verfassen mit An, CC, Betreff, Text
  • Antworten mit zitiertem Originaltext
  • Dateianhänge (max. 10 MB pro Datei, 25 MB gesamt)
  • SMTP-Versand über die gewählte StressfreiEmail-Adresse

Vertragszuordnung

  • E-Mails zu Verträgen zuordnen für bessere Nachverfolgung
  • E-Mail-Tab in Vertragsansicht zeigt nur zugeordnete E-Mails
  • Automatische Zuordnung bei Versand aus Vertragskontext
  • Manuelle Zuordnung über Suchfeld und Vertragsauswahl

Anhänge

  • Anhangsliste in E-Mail-Detail
  • Download einzelner Anhänge
  • Inline-Ansicht (im Browser öffnen)

Technische Details

Backend-Services

Service Beschreibung
imapService.ts IMAP-Client (ImapFlow) für E-Mail-Empfang
smtpService.ts SMTP-Client (Nodemailer) für E-Mail-Versand
cachedEmail.service.ts E-Mail-Caching, Synchronisation, Zuordnung

API-Endpunkte

GET  /api/customers/:id/emails              # E-Mails für Kunde
GET  /api/contracts/:id/emails              # E-Mails für Vertrag
GET  /api/emails/:id                        # Einzelne E-Mail mit Body
POST /api/stressfrei-emails/:id/sync        # IMAP-Synchronisation
POST /api/stressfrei-emails/:id/send        # E-Mail senden
POST /api/emails/:id/assign                 # Vertrag zuordnen
DELETE /api/emails/:id/assign               # Zuordnung aufheben
PATCH /api/emails/:id/read                  # Gelesen/Ungelesen
POST /api/emails/:id/star                   # Favorit umschalten
DELETE /api/emails/:id                      # In Papierkorb
POST /api/emails/:id/restore                # Aus Papierkorb wiederherstellen
DELETE /api/emails/:id/permanent            # Endgültig löschen
GET  /api/emails/:id/attachments/:filename  # Anhang herunterladen

# Rechnungen (Invoices)
GET    /api/invoices/ecd/:ecdId              # Rechnungen für EnergyContractDetails
POST   /api/invoices/ecd/:ecdId              # Rechnung hinzufügen
PUT    /api/invoices/ecd/:ecdId/:invoiceId   # Rechnung bearbeiten
DELETE /api/invoices/ecd/:ecdId/:invoiceId   # Rechnung löschen
POST   /api/invoices/:invoiceId/upload       # Rechnungsdokument hochladen

# Cockpit
GET    /api/contracts/cockpit                # Offene Aufgaben abrufen

Datenbank-Modell

model CachedEmail {
  id                Int              @id @default(autoincrement())
  stressfreiEmailId Int
  stressfreiEmail   StressfreiEmail  @relation(...)
  folder            String           // INBOX, SENT
  messageId         String           // RFC 5322 Message-ID
  uid               Int              // IMAP UID
  subject           String?
  fromAddress       String
  fromName          String?
  toAddresses       String           @db.Text  // JSON Array
  ccAddresses       String?          @db.Text
  receivedAt        DateTime
  textBody          String?          @db.LongText
  htmlBody          String?          @db.LongText
  hasAttachments    Boolean          @default(false)
  attachmentNames   String?          @db.Text  // JSON Array
  contractId        Int?             // Vertragszuordnung
  assignedAt        DateTime?
  assignedBy        Int?
  isAutoAssigned    Boolean          @default(false)
  isRead            Boolean          @default(false)
  isStarred         Boolean          @default(false)
  isDeleted         Boolean          @default(false)
  deletedAt         DateTime?

  @@unique([stressfreiEmailId, messageId, folder])
}

Sicherheit

  • Passwort-Verschlüsselung: AES-256-GCM für Mailbox-Passwörter
  • Passwort-Reset: Neues Passwort generieren und beim Provider setzen
  • Verschlüsselungsmodi: SSL, STARTTLS, oder unverschlüsselt
  • Selbstsignierte Zertifikate: Konfigurierbar pro Provider

Berechtigungen

Aktion Berechtigung
E-Mails lesen customers:read
E-Mails senden, markieren customers:update
Anhänge in Dokumente speichern customers:update
Vertrag zuordnen contracts:update
Löschen, Papierkorb emails:delete

Frontend-Komponenten

Komponente Beschreibung
EmailClientTab.tsx Haupt-Tab mit Konto-Auswahl und Ordnern
EmailList.tsx E-Mail-Liste mit Aktionen
EmailDetail.tsx E-Mail-Ansicht mit Anhängen
ComposeEmailModal.tsx Neue E-Mail / Antworten
TrashEmailList.tsx Papierkorb-Verwaltung
AssignToContractModal.tsx Vertragszuordnung
ContractEmailsSection.tsx E-Mails in Vertragsansicht
SaveAttachmentModal.tsx Anhänge in Dokumentfelder speichern
SaveEmailAsPdfModal.tsx E-Mail als PDF in Dokumentfelder speichern
InvoicesSection.tsx Rechnungsverwaltung in Vertragsdetails

Anhänge als Dokumente speichern

E-Mail-Anhänge können direkt in Dokumentfelder des CRM gespeichert werden. Über den blauen Speichern-Button (💾) neben jedem Anhang öffnet sich ein Modal mit allen verfügbaren Zielen.

Verfügbare Ziele

Kategorie Dokumentfelder
Kunde Datenschutzerklärung
Kunde (Gewerbe) + Gewerbeanmeldung, Handelsregisterauszug
Ausweisdokumente Dokumentscan (pro Ausweis)
Bankkarten Kartenscan (pro Karte)
Vertrag Kündigungsschreiben, Kündigungsbestätigung, Kündigungsschreiben (Optionen), Kündigungsbestätigung (Optionen)

Hinweis: Vertragsdokumente sind nur verfügbar, wenn die E-Mail einem Vertrag zugeordnet ist.

Dynamische Konfiguration

Die Dokumentziele werden zentral in backend/src/config/documentTargets.config.ts konfiguriert. Neue Dokumentfelder werden automatisch im Modal angezeigt, ohne Frontend-Änderungen.

// Beispiel: Neues Feld hinzufügen
{
  key: 'newDocument',
  label: 'Neues Dokument',
  field: 'newDocumentPath',      // Prisma-Feld
  condition: null,                // oder 'BUSINESS' für Geschäftskunden
  directory: 'new-documents'      // Upload-Verzeichnis
}

Warnung bei Überschreiben

Wenn bereits ein Dokument im Zielfeld vorhanden ist, wird eine Warnung angezeigt. Das vorhandene Dokument wird beim Speichern automatisch ersetzt und die alte Datei gelöscht.

Vertragszuordnung aufheben (X-Button)

Der X-Button zum Aufheben der Vertragszuordnung erscheint nur bei manuell zugeordneten E-Mails. E-Mails, die direkt aus dem Vertragskontext gesendet wurden (isAutoAssigned = true), bleiben dauerhaft mit dem Vertrag verknüpft.

Ansicht Vertrags-Badge X-Button sichtbar
Kundenakte - Posteingang immer
Kundenakte - Gesendet nur manuell zugeordnet
Kundenakte - Papierkorb je nach Original-Ordner
Vertrag - Posteingang immer
Vertrag - Gesendet nur manuell zugeordnet
Vertrag - Papierkorb je nach Original-Ordner
Detail-Ansicht (beide) nur manuell zugeordnet

Hinweis: Bei gesendeten E-Mails gilt:

  • isAutoAssigned = true: E-Mail wurde direkt aus dem Vertragskontext gesendet → X-Button ausgeblendet
  • isAutoAssigned = false: E-Mail wurde manuell dem Vertrag zugeordnet → X-Button sichtbar

Vertragsbaum in Kundenansicht

In der Kundendetailansicht werden Verträge als Baumstruktur mit Vorgänger-Verknüpfung dargestellt:

▼ GAS-ML781A4FYXU  │ Gas │ ACTIVE │ 01.01.2025 - 31.12.2026
  └─ GAS-ML24GKR...│ Gas │ EXPIRED│ 05.05.2023 - 05.05.2025  (Vorgänger)
     └─ GAS-OLD123 │ Gas │ EXPIRED│ 01.01.2021 - 04.05.2023  (Vorgänger)

▶ MOB-ML77W560A73  │ Mobil│ DRAFT  │ 02.01.2024 - 02.01.2026

Funktionsweise:

  • Aktuellste Verträge oben - Verträge ohne Nachfolger werden als Wurzelknoten angezeigt
  • Standardmäßig eingeklappt - Klick auf ▶ zeigt die Vorgängerkette
  • Vorgänger eingerückt - Mit grauem Rand und "(Vorgänger)" Label
  • Verknüpfung über previousContractId - Wird beim Erstellen eines Folgevertrags automatisch gesetzt

Hinweis: In der Hauptvertragsliste (/contracts) wird weiterhin die flache Ansicht ohne Baumstruktur verwendet.

Lizenz

MIT