42 KiB
OpenCRM
Web-basiertes CRM-System für Kundenverwaltung mit Verträgen (Energie, Telekommunikation, KFZ-Versicherung).
Version: 1.1.0 (Changelog)
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)
- Auto-Vertragsstatus: Lieferbestätigung-Upload setzt
DRAFT→ACTIVE(mit Vertragsbeginn), Kündigungsbestätigung-Upload setztACTIVE→CANCELLED(mit Datum), nightly-Cron setztACTIVE-Verträge mit abgelaufenemendDateaufEXPIRED - 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
- DSGVO-Compliance: Audit-Logging mit Hash-Chain-Integritätsprüfung, Einwilligungsverwaltung, Datenexport, Löschanfragen
- Sicherheits-Monitoring: Realtime-Logging von Login-Fehlversuchen, IDOR-Abwehr, SSRF-Blocks, JWT-Manipulation; Threshold-Detection (Brute-Force, IDOR-Probing) mit Sofort-E-Mail-Alerts und stündlichem Digest – siehe Einstellungen → Monitoring
- Production-Hardening: 10 dokumentierte Hardening-Runden inkl. CORS, Helmet, IDOR-Schutz, Rate-Limiting, SSRF/DNS-Rebinding-Block, Per-File-Ownership-Check, mehr in docs/SECURITY-HARDENING.md
- 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/expresszur 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:
- E-Mail: admin@admin.com
- Passwort: admin
Wichtig: Vor dem ersten Production-Deployment das Default-Passwort sofort ändern und Secrets rotieren – siehe Production-Deployment.
Production-Deployment
Vor dem öffentlichen Schalten der Instanz muss in der Production-.env:
NODE_ENV=production
# Pflicht-Rotation – per `openssl rand` neu generieren!
JWT_SECRET=$(openssl rand -hex 64) # min. 32 Zeichen
ENCRYPTION_KEY=$(openssl rand -hex 32) # genau 64 Hex-Zeichen
# Backend nur lokal lauschen lassen, public-Verkehr läuft über Reverse-Proxy
LISTEN_ADDR=127.0.0.1
# Bei separatem Frontend-Host: erlaubte Origins
CORS_ORIGINS=https://crm.deine-domain.de
Plus:
- Reverse-Proxy (Nginx/Plesk) so konfigurieren, dass
X-Forwarded-Forhart auf die echte Client-IP gesetzt wird (nicht nur angefügt) – sonst Rate-Limit-Bypass möglich. - Default-Admin-Passwort ändern (admin@admin.com / admin).
- Manuelle Test-Checkliste aus docs/TESTING.md einmal komplett durchklicken.
- Monitoring konfigurieren: Einstellungen → Sicherheits-Monitoring → Alert-E-Mail hinterlegen, Test-Alert senden, Digest aktivieren.
- Vollständige Hardening-Story + restliche Trade-offs: docs/SECURITY-HARDENING.md
Developer-Tools aktivieren
Die Developer-Tools (Datenbankstruktur, ER-Diagramm) sind standardmäßig für Admins verfügbar. Falls der Menüpunkt nicht erscheint:
- Einmalig im Browser-Console ausführen:
fetch('/api/developer/setup', { method: 'POST' }) - 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
- Einstellungen → Email-Provisionierung öffnen
- Neuen Provider hinzufügen:
- Name: Bezeichnung (z.B. "Plesk Hauptserver")
- Typ: Plesk/cPanel/DirectAdmin
- API-URL: Server-URL (z.B.
https://server.de:8443) - API-Key (empfohlen bei Plesk): Key aus Plesk (siehe unten), alternativ Benutzername/Passwort
- Benutzername/Passwort: Nur wenn kein API-Key vorhanden
- Domain: E-Mail-Domain (z.B.
stressfrei-wechseln.de) - Standard-Weiterleitung: Zusätzliche Weiterleitungsadresse (optional)
- Provider als "Standard" und "Aktiv" markieren
- Verbindung testen
Plesk: API-Key anlegen
Der API-Key ist die empfohlene Authentifizierungsmethode (sicherer als Passwort, kann pro Anwendung vergeben und widerrufen werden).
Variante 1: Über die Plesk-Oberfläche (einfachster Weg)
- In Plesk als Admin einloggen
- Oben rechts auf den eigenen Namen → "Mein Profil" (oder direkt URL
/admin/my-profile/) - Tab "API-Token" oder "API-Schlüssel" öffnen
- "API-Schlüssel erstellen" (bzw. "Add API Key")
- Beschreibung vergeben (z.B. "OpenCRM")
- Den angezeigten Schlüssel sofort kopieren – er wird nur einmal angezeigt!
- Im CRM bei "API-Key" einfügen
Hinweis: Bei manchen Plesk-Versionen ist die Option unter Tools & Einstellungen → API-Schlüssel oder Werkzeuge & Einstellungen → API-Tokens zu finden. Wenn der Menüpunkt fehlt, muss ggf. die REST API Extension installiert werden (siehe Variante 2).
Variante 2: Über die Kommandozeile (SSH als root)
Falls der API-Key-Button in Plesk nicht vorhanden ist, lässt er sich auch per SSH erstellen:
# API-Key generieren (läuft nicht ab)
# WICHTIG: -ip-address weglassen, wenn der Key von beliebigen IPs genutzt werden soll!
plesk bin secret_key --create -description "OpenCRM"
# Alternativ mit IP-Einschränkung (nur Zugriffe von dieser IP sind erlaubt):
plesk bin secret_key --create -ip-address <IP-DES-CRM-SERVERS> -description "OpenCRM"
Achtung:
-ip-address 0.0.0.0funktioniert nicht wie bei anderen Tools! Plesk prüft exakt gegen die eingetragene IP. Für "alle IPs erlauben" muss der-ip-address-Parameter komplett weggelassen werden.
Der Befehl gibt den Key direkt zurück. Diesen kopieren und im CRM eintragen.
Alle API-Keys anzeigen:
plesk bin secret_key --list
API-Key löschen:
plesk bin secret_key --delete <KEY>
Plesk: REST API aktivieren (falls nicht vorhanden)
Bei älteren Plesk-Versionen oder Custom-Installationen kann es sein, dass die REST API fehlt. Dann:
- Tools & Einstellungen → Updates → Erweiterungen hinzufügen/entfernen
- Nach "REST API" suchen und installieren
- Plesk-Neustart (meist nicht nötig, aber zur Sicherheit)
Plesk: Firewall-Hinweis
Der CRM-Server muss den Plesk-Port 8443 (Standard) erreichen können. Bei Plesk-Firewall:
- Tools & Einstellungen → Firewall
- "Plesk-Dienst – Panel" (Port 8443) für die IP des CRM-Servers erlauben
Bei reiner Linux-Firewall (ufw/firewalld):
# Beispiel ufw
ufw allow from <CRM-SERVER-IP> to any port 8443
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:
- Geprüft, ob die E-Mail-Adresse bereits existiert
- 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 (Entwicklung)
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
Docker (Produktion)
Im docker/ Verzeichnis liegt ein komplettes Produktions-Setup:
cd docker
# Image bauen
docker-compose build
# Container starten
docker-compose up -d
# Logs anzeigen
docker-compose logs -f app
Komponenten:
- MariaDB 10.11: Datenbank
- App: Backend + Frontend in einem Container
- Caddy: Reverse-Proxy mit automatischem SSL
Umgebungsvariablen (docker/.env):
MYSQL_ROOT_PASSWORD=sicheres-root-passwort
MYSQL_DATABASE=opencrm
MYSQL_USER=opencrm
MYSQL_PASSWORD=sicheres-passwort
JWT_SECRET=sehr-langer-zufaelliger-string
ENCRYPTION_KEY=64-zeichen-hex-string
DOMAIN=crm.example.com
RUN_SEED=true # Nur beim ersten Start
Projektstruktur
opencrm/
├── backend/
│ ├── src/
│ │ ├── controllers/ # Request-Handler
│ │ │ ├── auditLog.controller.ts # Audit-Log API
│ │ │ └── gdpr.controller.ts # DSGVO API
│ │ ├── middleware/ # Auth, Validation
│ │ │ ├── audit.ts # Automatisches API-Logging
│ │ │ └── auditContext.ts # Before/After Context
│ │ ├── routes/ # API-Endpunkte
│ │ │ ├── auditLog.routes.ts # Audit-Log Routes
│ │ │ └── gdpr.routes.ts # DSGVO Routes
│ │ ├── services/ # Business-Logik
│ │ │ ├── audit.service.ts # Hash-Kette, Logging
│ │ │ ├── consent.service.ts # Einwilligungen
│ │ │ └── gdpr.service.ts # Export, Löschung
│ │ ├── lib/
│ │ │ └── prisma.ts # Prisma mit Audit-Middleware
│ │ ├── 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)
│ │ ├── gdpr/ # DSGVO-Löschnachweise
│ │ ├── 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
│ │ │ └── settings/
│ │ │ ├── AuditLogs.tsx # Audit-Protokoll
│ │ │ └── GDPRDashboard.tsx # DSGVO-Dashboard
│ │ ├── hooks/ # Custom Hooks
│ │ ├── services/ # API-Client
│ │ ├── types/ # TypeScript-Typen
│ │ └── App.tsx # Haupt-Komponente
│ └── package.json
├── docker/ # Docker-Deployment
│ ├── Dockerfile # Multi-Stage Build
│ ├── docker-compose.yml # Produktion (MariaDB, App, Caddy)
│ ├── Caddyfile # Reverse-Proxy mit SSL
│ └── entrypoint.sh # Container-Startup
├── docker-compose.yml # MariaDB-Container (Entwicklung)
└── README.md
Berechtigungen
| Rolle | Kunden | Verträge | Benutzer | Plattformen | Audit/DSGVO | Developer |
|---|---|---|---|---|---|---|
| Admin | CRUD | CRUD | CRUD | CRUD | Vollzugriff | Optional |
| Mitarbeiter | CRUD | CRUD | - | Lesen | Lesen | - |
| Mitarbeiter (Lesen) | Lesen | Lesen | - | Lesen | - | - |
| Kunde | Eigene | Eigene | - | - | - | - |
Troubleshooting
Datenbank-Verbindungsfehler
- Prüfe ob Container läuft:
docker ps - Prüfe die DATABASE_URL in
.env - 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:
PORTin.envändern - Frontend: In
vite.config.tsanpassen
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:
- Toggle zwischen "Als Dokument" und "Als Rechnung" im Speichern-Dialog
- Rechnungsdatum und Typ auswählen
- 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
Vertrag zurückstellen (Snooze)
Wenn ein Vertrag EXPIRED ist (Laufzeit vorbei, läuft aber weiter), erscheint er im Cockpit. Manchmal ist Bleiben günstiger als Wechseln - aber der Vertrag soll nicht dauerhaft im Cockpit erscheinen.
Lösung: Vertrag temporär zurückstellen mit Datum für erneute Prüfung.
Snooze aktivieren
Im Cockpit hat jeder Vertrag einen Snooze-Button (Glocke mit Uhr). Optionen:
- +3 Monate
- +6 Monate (Empfohlen)
- +12 Monate
- Eigenes Datum (Datepicker)
Unterdrückte Warnungen (bei aktivem Snooze)
| Warnung | Beschreibung |
|---|---|
| Kündigungsfrist | Frist läuft ab |
| Vertrag läuft ab | Enddatum naht |
| Kündigungsschreiben fehlt | Bei markierter Kündigung |
| Kündigungsbestätigung fehlt | Bei markierter Kündigung |
NICHT unterdrückte Warnungen
Diese Warnungen werden auch bei aktivem Snooze angezeigt:
| Warnung | Beschreibung |
|---|---|
| Schlussrechnung fehlt | Beendeter Vertrag ohne Endabrechnung |
| Zwischenrechnung fehlt/überfällig | Laufender Vertrag ohne Jahresrechnung |
| Zählerstand fehlt | Fehlende Zählerstände bei Energieverträgen |
| Zugangsdaten fehlen | Portal-Passwort, SIP-Daten, etc. |
Snooze aufheben
Im Cockpit: Bei Verträgen in der Kategorie "Erneute Prüfung fällig" erscheint ein "Snooze aufheben"-Button.
In Vertragsdetails: Bei zurückgestellten Verträgen erscheint ein bernsteinfarbenes Badge "Zurückgestellt bis [Datum]" mit X-Button zum Aufheben.
Erneute Prüfung fällig
Wenn das Snooze-Datum in der Vergangenheit liegt, erscheint der Vertrag in der neuen Kategorie "Erneute Prüfung fällig" mit Angabe, seit wie vielen Tagen die Prüfung fällig ist.
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 ausgeblendetisAutoAssigned = 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.
DSGVO-Compliance & Audit-Logging
Umfassendes Audit-Logging-System mit DSGVO-Compliance-Features.
Audit-Protokoll
Automatische Protokollierung aller API-Zugriffe mit:
| Feld | Beschreibung |
|---|---|
| Benutzer | User-ID, E-Mail, Rolle |
| Aktion | CREATE, READ, UPDATE, DELETE, EXPORT, ANONYMIZE, LOGIN, LOGOUT |
| Ressource | Tabelle + ID + lesbare Bezeichnung |
| Kontext | Endpoint, HTTP-Methode, IP-Adresse, User-Agent |
| Änderungen | Vorher/Nachher-Werte (bei Updates) |
| Sensitivität | LOW, MEDIUM, HIGH, CRITICAL |
| Integrität | SHA-256 Hash-Kette für Manipulationsschutz |
Sensitivitätsstufen
| Stufe | Ressourcen |
|---|---|
| LOW | Einstellungen, Plattformen, Tarife |
| MEDIUM | Verträge, Provider |
| HIGH | Kundendaten, Benutzerdaten |
| CRITICAL | Authentifizierung, Bankdaten, Ausweisdokumente |
Zugriff
- Einstellungen → Audit-Protokoll
- Filter nach: Datum, Benutzer, Aktion, Ressource, Sensitivität
- Detail-Ansicht mit Vorher/Nachher-Diff
- Export als JSON
- Integritätsprüfung (Hash-Kette verifizieren)
DSGVO-Dashboard
Zentrale Verwaltung für DSGVO-Anfragen unter Einstellungen → DSGVO-Dashboard.
Dashboard-Statistiken
- Offene Löschanfragen
- Abgeschlossene Löschungen (letzte 30 Tage)
- Datenexporte (letzte 30 Tage)
- Aktive Einwilligungen
Einwilligungsverwaltung (Consents)
| Consent-Typ | Beschreibung |
|---|---|
| DATA_PROCESSING | Grundlegende Datenverarbeitung |
| MARKETING_EMAIL | E-Mail-Marketing |
| MARKETING_PHONE | Telefonmarketing |
| DATA_SHARING_PARTNER | Datenweitergabe an Partner |
Einwilligungen können pro Kunde im Tab "Einwilligungen" verwaltet werden.
Löschanfragen (Art. 17)
Workflow für DSGVO-Löschanfragen:
| Status | Beschreibung |
|---|---|
| PENDING | Anfrage eingegangen |
| IN_PROGRESS | Wird bearbeitet |
| COMPLETED | Vollständig gelöscht/anonymisiert |
| PARTIALLY_COMPLETED | Teildaten behalten (z.B. aktive Verträge) |
| REJECTED | Abgelehnt mit Begründung |
Anonymisierung statt Löschung:
- Kundendaten werden anonymisiert (nicht gelöscht)
- Aktive Verträge werden beibehalten
- PDF-Löschnachweis wird generiert
Datenexport (Art. 15)
Kunden können alle gespeicherten Daten als JSON exportieren:
- Stammdaten
- Adressen, Bankdaten, Ausweise
- Verträge mit Details
- Zähler und Ablesungen
- Einwilligungen
- Zugriffsprotokolle
API-Endpunkte
# Audit-Logs
GET /api/audit-logs # Logs mit Filtern
GET /api/audit-logs/:id # Einzelnes Log
GET /api/audit-logs/customer/:id # Logs für Kunde
GET /api/audit-logs/export # Export (JSON/CSV)
POST /api/audit-logs/verify # Hash-Kette prüfen
GET /api/audit-logs/retention-policies # Aufbewahrungsfristen
PUT /api/audit-logs/retention-policies/:id
POST /api/audit-logs/cleanup # Manuelle Bereinigung
# DSGVO
GET /api/gdpr/dashboard # Dashboard-Statistiken
GET /api/gdpr/customer/:id/export # Kundendaten-Export
GET /api/gdpr/deletions # Löschanfragen
POST /api/gdpr/deletions # Löschanfrage erstellen
PUT /api/gdpr/deletions/:id/process # Löschanfrage bearbeiten
GET /api/gdpr/customer/:id/consents # Einwilligungen abrufen
PUT /api/gdpr/customer/:id/consents/:type # Einwilligung ändern
GET /api/gdpr/consents/overview # Consent-Übersicht
Aufbewahrungsfristen
| Ressource | Frist | Rechtsgrundlage |
|---|---|---|
| Standard | 10 Jahre | AO §147, HGB §257 |
| Authentifizierung | 2 Jahre | Sicherheit |
| Kundendaten (HIGH) | 10 Jahre | Steuerrecht |
| Verträge | 10 Jahre | Steuerrecht |
| Allgemein (LOW) | 3 Jahre | Verjährung |
Berechtigungen
| Aktion | Berechtigung |
|---|---|
| Audit-Logs lesen | audit:read |
| Audit-Logs exportieren | audit:export |
| Audit-Administration | audit:admin |
| DSGVO-Export | gdpr:export |
| Löschanfrage erstellen | gdpr:delete |
| DSGVO-Administration | gdpr:admin |
Technische Details
Hash-Kette
Jeder Audit-Log-Eintrag enthält einen SHA-256-Hash über:
- Alle Felder des Eintrags
- Hash des vorherigen Eintrags
Dies ermöglicht die Erkennung von Manipulationen.
Sensitive Daten
Folgende Felder werden in Audit-Logs gefiltert:
password,passwordHashportalPasswordHash,portalPasswordEncryptedemailPasswordEncrypted,internetPasswordEncryptedsipPasswordEncrypted,pin,puk,apiKey
Performance
- Logging erfolgt asynchron (
setImmediate) - API-Response wird nicht blockiert
- Before/After-Werte über Prisma Middleware
Factory-Defaults: Stammdaten-Kataloge teilen
Das Factory-Defaults-System erlaubt den Export und Import von Stammdaten-Katalogen (Anbieter, Tarife, PDF-Vorlagen usw.) zwischen verschiedenen OpenCRM-Installationen. Es ist bewusst streng abgegrenzt zu Datenbank-Backups:
Abgrenzung
| Factory-Defaults | Datenbank-Backup | |
|---|---|---|
| Anbieter, Tarife, Kündigungsfristen, Laufzeiten, Kategorien | ✅ | ✅ |
| PDF-Auftragsvorlagen (inkl. Dateien + Feldzuordnungen) | ✅ | ✅ |
| Kundendaten, Verträge, Dokumente | ❌ | ✅ |
| Emails, SMTP-/IMAP-Zugangsdaten | ❌ | ✅ |
| System-Einstellungen, Datenschutzerklärungen, Impressum | ❌ | ✅ |
| Zwischen verschiedenen Installationen teilbar | ✅ | ❌ (zu firmen-spezifisch) |
Kurz: Factory-Defaults = reine Kataloge, Backup = alles.
Export (Installation A → ZIP)
- Einstellungen → Factory-Defaults öffnen
- Übersicht prüfen (Anzahl pro Kategorie)
- Button „Factory-Defaults exportieren" klicken
- ZIP wird als
factory-defaults-YYYY-MM-DD.zipheruntergeladen
ZIP-Struktur:
factory-defaults-2026-04-23.zip
├── manifest.json # Version + Datum + Counts
├── providers/
│ └── providers.json # Anbieter inkl. zugehöriger Tarife
├── contract-meta/
│ ├── cancellation-periods.json # Kündigungsfristen (Code + Beschreibung)
│ ├── contract-durations.json # Laufzeiten (Code + Beschreibung)
│ └── contract-categories.json # Kategorien (Strom, Gas, DSL, ...)
└── pdf-templates/
├── pdf-templates.json # Vorlagen-Metadaten + Feldzuordnungen
└── *.pdf # Die eigentlichen PDF-Dateien
Die ZIP kann an andere Installationen weitergegeben werden (Partner, Test-System, neue Installation).
Import (ZIP → Installation B)
- ZIP herunterladen bzw. erhalten
- Inhalt nach
backend/factory-defaults/entpacken (Unterordnerstruktur beibehalten) - Im Backend-Verzeichnis ausführen:
npm run seed:defaults
Beispiel-Output:
📦 Factory-Defaults werden eingespielt...
✓ Anbieter: 7, Tarife: 12
✓ Kündigungsfristen: 5
✓ Laufzeiten: 4
✓ Vertragskategorien: 8
✓ PDF-Vorlagen: 3
✅ Factory-Defaults erfolgreich eingespielt.
Mehrere ZIPs kombinieren
Du kannst mehrere Exporte in backend/factory-defaults/ übereinanderlegen –
JSON-Dateien werden automatisch gemerged:
backend/factory-defaults/
providers/
verivox.json # 40 Anbieter aus Verivox-Paket
check24.json # 30 Anbieter aus Check24-Paket
eigene.json # 5 eigene Anbieter
Das Import-Script liest alle *.json im jeweiligen Unterordner und merged per
unique Key (letzter Eintrag gewinnt). Duplikate sind also unproblematisch.
Idempotenz
Das Script nutzt ausschließlich Prisma upsert:
- Neue Einträge werden angelegt
- Bestehende Einträge (per unique Key:
name,code) werden aktualisiert - Nichts wird gelöscht
Du kannst npm run seed:defaults also beliebig oft ausführen, ohne Datenverlust
oder Duplikate.
PDF-Dateien beim Import
Beim Import werden PDF-Vorlagen aus factory-defaults/pdf-templates/*.pdf nach
uploads/pdf-templates/ kopiert und die Pfade in der DB entsprechend gesetzt.
Beim Re-Import wird die alte Datei in uploads/ entsorgt und durch die neue
ersetzt.
Berechtigungen
| Aktion | Berechtigung |
|---|---|
| Factory-Defaults Vorschau | settings:read |
| Factory-Defaults Export | settings:update |
| Factory-Defaults Import (CLI) | Server-Zugang (SSH/Shell) |
Typischer Einsatzzweck
- Neue Installation aufsetzen: Eine Kollegen-ZIP importieren und sofort mit gepflegtem Anbieter- und Vorlagenkatalog loslegen
- Vorlagen-Paket teilen: Eine ZIP mit nur PDF-Vorlagen weitergeben (die anderen Ordner einfach aus der ZIP entfernen vor dem Entpacken)
- Anbieter-Paket teilen: ZIP mit nur
providers/weitergeben - Versionskontrolle: Die entpackten JSON-Dateien unter Versionskontrolle
stellen (außerhalb von
backend/factory-defaults/, da der Ordner gitignored ist)
Changelog
1.1.0 (2026-05-01)
Production-readiness – die Version, die wirklich öffentlich gehen darf.
- 🛡 Security-Hardening: 10 Runden statisches + dynamisches Audit, vollständig
dokumentiert in docs/SECURITY-HARDENING.md
(CORS/Helmet/JWT, IDOR-Schutz an 30+ Endpoints, Mass-Assignment-Whitelists,
Zip-Slip, Path-Traversal, Login-Timing-Side-Channel, XFF-Rate-Limit-Bypass,
Customer-Liste-Leak, SSRF + DNS-Rebinding, Per-File-Ownership statt
freiem
/api/uploads, JWT-Logout, Audit-Log-Hash-Chain). - 🚨 Sicherheits-Monitoring: neue
SecurityEvent-Tabelle + Hooks an Login, Logout, Rate-Limit-Hit, IDOR-Abwehr, SSRF-Block, Password-Reset, JWT-Reject. Threshold-Detection (Brute-Force, IDOR-Probing, SSRF-Probing) erzeugt CRITICAL-Events. Sofort-E-Mail-Alerts für CRITICAL + stündlicher Digest für HIGH/MEDIUM. UI in Einstellungen → Monitoring mit Filter, Pagination, Log-leeren (mit optionalem Tage-Filter) und Test-Alert-Button. - 🔄 Auto-Vertragsstatus:
- Lieferbestätigung-Upload →
DRAFT→ACTIVE+startDate - Kündigungsbestätigung-Upload →
ACTIVE→CANCELLED+cancellationConfirmationDate(mit Datums-Modal beim Upload) - Nightly-Cron 02:00: alle
ACTIVE-Verträge mitendDate < heute→EXPIRED
- Lieferbestätigung-Upload →
- 🔐 Lazy bcrypt-Rehash: Bestandshashes mit Cost 10 werden beim nächsten Login transparent auf Cost 12 geupgradet.
- 🚪 Logout-Endpoint
POST /api/auth/logout: invalidiert JWTs serverseitig übertokenInvalidatedAt. - 📦
npm audit fix: 8 transitive Vulnerabilities gefixt (lodash, path-to-regexp, undici, minimatch).
1.0.0
Erste Release-Version.
- Kunden-, Vertrags-, Adress-, Bankkarten-, Ausweis- und Zählerverwaltung
- Energie-/Telekommunikations-/KFZ-Verträge mit typspezifischen Details
- Vertrags-Cockpit mit Rechnungsprüfung
- E-Mail-Client mit Anhang-Verwaltung
- DSGVO-Compliance: Audit-Log, Einwilligungen, Datenexport, Löschanfragen
- PDF-Auftragsvorlagen-System mit visueller Feldzuordnung
- Factory-Defaults für Stammdaten-Kataloge
- Mandantenfähigkeit über
customerEmailLabelpro Provider - Passwort-Reset-Flow + Rate-Limiting + Auto-Geburtstagsgrüße
Lizenz
MIT