256 lines
13 KiB
Markdown
256 lines
13 KiB
Markdown
# 📋 OpenCRM – Todo-Liste
|
||
|
||
---
|
||
|
||
## 🔜 Offen
|
||
|
||
### Manuelle Tests (vor Release durchklicken)
|
||
Checklisten für Security + Email-Log-System stehen in **[TESTING.md](./TESTING.md)**.
|
||
Einmal komplett durchlaufen vor v1.0.0-Release.
|
||
|
||
### 🚀 SaaS-Ausbau: Instance-per-Customer + Admin-Portal + GoCardless
|
||
|
||
**Vision:** OpenCRM als SaaS anbieten. Jeder Kunde bekommt seine eigene
|
||
isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||
über ein zentrales Admin-Portal.
|
||
|
||
**Architektur-Entscheidung:** Weg C (Instance-per-Customer)
|
||
- Pro Kunde eine eigene Docker-Instanz mit eigener DB
|
||
- Keine `tenantId` im CRM-Code → keine Security-Risiken durch vergessene Filter
|
||
- Komplette Datenisolation (DSGVO-freundlich)
|
||
- Updates können gestaffelt ausgerollt werden (erst 10% testen)
|
||
- Bei Kündigung: Docker-Image + DB-Export als "Mitnehm-Paket"
|
||
|
||
**Bewusst NICHT dabei:** eigener Mailserver. Stattdessen Plesk-Integration
|
||
(die wir schon haben) – Kunde bekommt Mail-Zugang über unseren Plesk bei Bedarf.
|
||
|
||
---
|
||
|
||
**Admin-Portal (separate App, neben den CRM-Instanzen):**
|
||
- Kundenverwaltung: wer hat welchen Plan, Status (Trial/Active/Suspended/Cancelled)
|
||
- "Neuen Kunden anlegen" → Provisioning-Script
|
||
- DB anlegen (Master-DB kennt die Mapping)
|
||
- Docker-Container starten
|
||
- Subdomain konfigurieren (`kundenname.deincrm.de` via Caddy/Traefik)
|
||
- Initial-Admin-Account erstellen + Einladungs-Email senden
|
||
- Optional: Factory-Defaults für Stammdaten einspielen
|
||
- GoCardless-Integration (Webhook + Dashboard)
|
||
- Instanz-Management: Pause/Resume bei Zahlungsproblemen
|
||
- Logs & Metriken pro Instanz (optional)
|
||
- Support-Bereich (Tickets? oder einfach E-Mail)
|
||
|
||
---
|
||
|
||
**Abrechnung mit GoCardless (gocardless.com):**
|
||
- Zahlungsmethoden: SEPA-Lastschrift (Hauptfokus) + Kreditkarte (über GoCardless Embedded/Success)
|
||
- 30 Tage kostenlose Testphase ohne Zahlungsmittel
|
||
- Nach Trial: Mandats-Erfassung → regelmäßige Abbuchung
|
||
- Mehrere Pläne (z.B. Basic / Pro / Enterprise) mit unterschiedlichen Features
|
||
- Webhook-Endpoint im Admin-Portal:
|
||
- `payment_confirmed` → Instanz aktiv lassen
|
||
- `payment_failed` → Banner im CRM, nach X Tagen pausieren
|
||
- `mandate_cancelled` → Kündigungs-Flow
|
||
- Rechnungsstellung: GoCardless liefert Zahlungsbelege, aber **echte Rechnungen**
|
||
(mit USt-ID, Rechnungsnummer etc.) müssen wir selbst generieren
|
||
(evtl. über das existierende PDF-Template-System aus dem CRM nutzen)
|
||
|
||
---
|
||
|
||
**Provisioning-Flow (grober Entwurf):**
|
||
1. Kunde registriert sich auf Landing Page (Name, Firma, E-Mail, Wunsch-Subdomain)
|
||
2. Admin-Portal: Trial-Instanz starten
|
||
- DB erstellen, Docker-Container hochfahren, Caddy-Config für Subdomain
|
||
- Einladungs-Email mit Admin-Login + Passwort-Reset-Link
|
||
3. Tag 25: Erinnerungs-Email "Deine Trial läuft bald ab"
|
||
4. Tag 30: Banner im CRM "Jetzt bezahlen oder pausieren"
|
||
5. Kunde erfasst GoCardless-Mandat im Admin-Portal-Login
|
||
6. Bei erfolgreicher Zahlung: Instanz bleibt aktiv
|
||
7. Bei fehlender Zahlung nach 7 Tagen: Instanz pausiert (DB bleibt, UI zeigt Hinweis)
|
||
|
||
---
|
||
|
||
**Technische Bausteine für später:**
|
||
- Master-DB mit Tenant-Tabelle (Name, Subdomain, DB-Name, Plan, Status, GoCardlessIDs)
|
||
- Caddy oder Traefik als Reverse-Proxy mit Auto-SSL (Let's Encrypt)
|
||
- Docker-Orchestrierung: einzelne `docker-compose.yml` pro Kunde oder Docker-Swarm/K8s
|
||
- Backup-Strategie: pro Tenant separate Backups + zentrale Master-DB-Backups
|
||
- Monitoring: ein Fail macht nicht alle down, aber wir müssen es mitbekommen
|
||
- Logs zentral: z.B. Loki + Grafana für aggregierte Logs aller Instanzen
|
||
|
||
---
|
||
|
||
**Grobe Zeitschätzung:**
|
||
- Admin-Portal (MVP): ~1 Woche
|
||
- GoCardless-Integration + Webhooks: ~3-5 Tage
|
||
- Provisioning-Automatisierung (Docker + Caddy): ~1 Woche
|
||
- Landing Page + Checkout: ~3-5 Tage
|
||
- Tests + Polishing: ~1 Woche
|
||
- **Gesamt: ~3-4 Wochen**
|
||
|
||
**Vorbereitung JETZT (einfach, macht später Arbeit leichter):**
|
||
- ✅ Factory-Defaults System (schon erledigt, hilft beim Provisioning)
|
||
- ✅ Domain/Label dynamisch per Provider (schon erledigt)
|
||
- Docker-Compose aufräumen, Env-Variablen dokumentieren (klein, ein Tag)
|
||
- Backup-Script robust + wiederherstellbar (haben wir schon weitgehend)
|
||
|
||
---
|
||
|
||
## ✅ Erledigt
|
||
|
||
- [x] **🔄 Automatische Vertrags-Status-Übergänge**
|
||
- Nightly-Cron (02:00 + Catch-up 60s nach Start): alle Verträge mit
|
||
`status=ACTIVE` und `endDate < heute` → `EXPIRED` (mit Audit-Log).
|
||
- Beim Upload der Kündigungsbestätigung (`cancellationConfirmationPath`):
|
||
wenn Vertrag aktuell `ACTIVE` → auf `CANCELLED` setzen (Audit-Log).
|
||
Frontend fragt per Modal das Bestätigungs-Datum ab (Default: heute),
|
||
wird direkt als `cancellationConfirmationDate` gespeichert.
|
||
Der "Optionen"-Upload löst den Status-Wechsel bewusst NICHT aus, da er
|
||
für Vertragsänderungen (nicht echte Kündigungen) gedacht ist, setzt
|
||
aber `cancellationConfirmationOptionsDate` analog.
|
||
- Beim Upload einer `Lieferbestätigung` (ContractDocument via direkt-Upload
|
||
oder Email-Anhang-Import): wenn Vertrag aktuell `DRAFT` → auf `ACTIVE`
|
||
setzen + `startDate` auf das erfasste Lieferdatum (falls leer).
|
||
Frontend zeigt Datums-Input conditional, wenn Typ "Lieferbestätigung"
|
||
ausgewählt ist.
|
||
- Keine neuen Status eingeführt: `cancellationSentDate` vs.
|
||
`cancellationConfirmationDate` genügen, um "gesendet vs. bestätigt"
|
||
abzubilden. `ACTIVE` bleibt bis zur Bestätigung.
|
||
|
||
- [x] **🛡️ Security-Hardening vor Production-Deployment (10 Runden)**
|
||
- Vollständige Story inkl. aller Live-Test-Tabellen + Trade-offs:
|
||
**[SECURITY-HARDENING.md](./SECURITY-HARDENING.md)**
|
||
- Erste 2 Runden zusätzlich ausführlich in
|
||
[SECURITY-REVIEW.md](./SECURITY-REVIEW.md)
|
||
- Highlights:
|
||
- Runde 1–3: CORS, Helmet, JWT-Fallback, IDOR-Welle 1, XSS, Mass
|
||
Assignment, Zip-Slip, Path-Traversal, JWT-Algorithm, Rate-Limiter
|
||
- Runde 4: 9 Live-IDORs (customer.\*/gdpr.\*) + Error-Handler
|
||
- Runde 5: `/api/uploads`-Auth (DSGVO-GAU), Login-Timing,
|
||
Privacy-Policy-XSS
|
||
- Runde 6: Customer-List-Leak, XFF-Rate-Limit-Bypass,
|
||
Self-Grant + Existence-Disclosure
|
||
- Runde 7: SSRF-Schutz (Cloud-Metadata-Block), Logout-Endpoint
|
||
- Runde 8: DNS-Rebinding-Schutz, Per-File-Ownership-Check
|
||
- Runde 9: `npm audit fix` (8 Vulns weg), Audit-Chain-Rehash, keine
|
||
neuen Critical-Findings → diminishing returns erreicht
|
||
- Runde 10: Security-Monitoring (SecurityEvent-Tabelle + Hooks an
|
||
Login/IDOR/SSRF/Reset/Logout/JWT-Reject + Threshold-Detection +
|
||
Sofort-Alert für CRITICAL + Hourly-Digest + UI in Einstellungen)
|
||
- Deployment-Checkliste komplett (in HARDENING.md)
|
||
|
||
- [x] **🎉 Version 1.0.0 Feinschliff: Passwort-Reset + Rate-Limiting + Auto-Geburtstagsgrüße**
|
||
- **Passwort vergessen-Flow** (Login → "Passwort vergessen?" Link)
|
||
- Email-Reset-Token mit 2h Gültigkeit (kryptografisch sicher: 32 Byte Random)
|
||
- Funktioniert für Mitarbeiter UND Portal-Kunden (Typ-Auswahl)
|
||
- User-Enumeration-Schutz: immer 200 OK, egal ob Email existiert
|
||
- Reset-Link per Email mit schönem HTML-Template
|
||
- Nach Reset: alle bestehenden Sessions werden gekickt
|
||
- **Rate-Limiting** gegen Brute-Force
|
||
- Login: 10 Versuche pro 15 Min pro IP (erfolgreiche zählen nicht)
|
||
- Passwort-Reset-Anfrage: 5 Versuche pro Stunde pro IP
|
||
- **Cron-Job für automatische Geburtstagsgrüße**
|
||
- Täglich 08:00 Uhr: alle Kunden mit heutigem Geburtstag + autoBirthdayGreeting=true
|
||
- Email-Versand über System-E-Mail, Du/Sie-abhängiger Text
|
||
- Catch-up 30s nach Server-Start (falls Server am Geburtstag kurz down war)
|
||
- Marker lastBirthdayGreetingYear verhindert Doppel-Versand
|
||
|
||
- [x] **Mandantenfähigkeit: Domain + Kunden-E-Mail-Label dynamisch pro Provider**
|
||
- Neues Feld `customerEmailLabel` am EmailProviderConfig (z.B. "Stressfrei-Wechseln", "Meine-Firma")
|
||
- Wenn leer, wird das Label automatisch aus der Domain abgeleitet ("stressfrei-wechseln.de" → "Stressfrei-Wechseln")
|
||
- Neuer Frontend-Hook `useProviderSettings()` liefert Domain + Label
|
||
- Alle hardcoded "Stressfrei-Wechseln" und `@stressfrei-wechseln.de` Strings durch dynamische Werte ersetzt
|
||
(CustomerDetail, ContractForm, ContractDetail, EmailClientTab, Settings)
|
||
- Modal-Eingabefeld "Bezeichnung für Kunden-E-Mails" in Provider-Einstellungen
|
||
- Notwendig für Multi-Mandanten-Betrieb wenn das CRM an Dritte vermietet wird
|
||
|
||
- [x] **Factory-Defaults: Export + Import von Stammdaten-Katalogen**
|
||
- Enthält: Anbieter, Tarife, Kündigungsfristen, Laufzeiten, Vertragskategorien, PDF-Auftragsvorlagen (+ PDF-Dateien)
|
||
- Enthält NICHT: Kundendaten, Verträge, Dokumente, Emails, Einstellungen (dafür gibt es den Datenbank-Backup)
|
||
- Neue Einstellungsseite „Factory-Defaults" mit Übersicht (Anzahl pro Kategorie) und Export-Button
|
||
- Export: ZIP mit manifest.json + Kategorie-JSONs + PDF-Dateien, Download über Browser
|
||
- Import-Script: `npm run seed:defaults` liest `backend/factory-defaults/`, merged mehrere JSONs pro Kategorie, upsertet idempotent + kopiert PDFs in uploads/
|
||
- Ordner `backend/factory-defaults/` gitignoriert (außer .gitkeep + README), damit firmen-spezifische Kataloge nicht ins Repo kommen
|
||
|
||
- [x] **Email-Anhänge → Vertragsdokumente + Rechnungen für alle Vertragstypen**
|
||
- Im SaveAttachmentModal (bei einem per Email zugeordneten Vertrag) gibt es jetzt drei Modi:
|
||
1. **Als Dokument** (in feste Slots wie Kündigungsschreiben) – wie bisher
|
||
2. **Als Vertragsdokument** – neu, mit Typ-Dropdown (Auftragsformular, Lieferbestätigung, Vertragsunterlagen, Vollmacht, Widerrufsbelehrung, Preisblatt, Sonstiges) + Notizen
|
||
3. **Als Rechnung** – jetzt für **alle** Vertragstypen (vorher nur Strom/Gas)
|
||
- Gleiches gilt für das Speichern der gesamten Email als PDF-Rechnung
|
||
- Neuer Backend-Endpoint `saveAttachmentAsContractDocument` für die flexible ContractDocument-Tabelle
|
||
|
||
- [x] **Geburtstag-Management-Modal in Kundenstammdaten**
|
||
- Neuer Button (Cake-Icon) neben Geburtsdatum öffnet Modal
|
||
- **Gruß zurücksetzen:** setzt `lastBirthdayGreetingYear` auf null zurück (fürs Debugging + Fallback)
|
||
- **Gruß jetzt senden:** per Email (direkt), WhatsApp/Telegram/Signal (öffnet vorbefülltes Fenster)
|
||
- Beide Aktionen mit Ja/Nein-Bestätigungsdialog (kein versehentliches Klicken)
|
||
- Text respektiert Du/Sie-Einstellung des Kunden
|
||
- Checkbox "Automatisch senden" mit Kanal-Dropdown (neue Felder am Customer)
|
||
- Audit-Log für Reset + Send
|
||
|
||
- [x] **Anrede-Verhältnis Du/Sie pro Kunde**
|
||
- Neues Feld `useInformalAddress` in Stammdaten (auch bei Firmenkunden)
|
||
- Default: Sie (formell)
|
||
- Geburtstagsgruß im Portal nutzt die Anrede: "Du"-Kunden bekommen "Herzlichen Glückwunsch, Max!", "Sie"-Kunden "Herzlichen Glückwunsch, Herr Müller!"
|
||
- Komplett konsistent auch bei nachträglichen Glückwünschen ("hattest" vs "hatten")
|
||
|
||
- [x] **Geburtsdatum + Geburtsort auch bei Firmenkunden**
|
||
- Felder werden jetzt unabhängig vom Kundentyp angezeigt
|
||
- Ermöglicht z.B. Geburtstage für Ansprechpartner bei Firmen
|
||
|
||
- [x] **Geburtstagskalender + Geburtstagsgruß-Modal**
|
||
- Admin: Section im Vertrags-Cockpit mit Kunden, die in den nächsten 30 Tagen oder letzten 7 Tagen Geburtstag haben
|
||
- Portal: Modal mit Gruß am Geburtstag (inkl. nachträglichem Glückwunsch bis 7 Tage danach)
|
||
- Wird pro Jahr nur einmal angezeigt
|
||
|
||
- [x] **Typspezifische Zusatzinfos in Vertragslisten**
|
||
- Strom/Gas → "Lieferadresse: ..."
|
||
- DSL/Glasfaser/Kabel → "Anschlussadresse: ..."
|
||
- Mobilfunk → "Rufnummer: ..."
|
||
- KFZ → "Kennzeichen: ..."
|
||
- Sichtbar in Admin-Liste, Portal-Liste und Kunden-Tab
|
||
|
||
- [x] **Datenschutzerklärung PDF ↔ Online-Einwilligungen synchronisieren**
|
||
- PDF hochgeladen → alle 4 Consents auf GRANTED
|
||
- Haken entfernt im Portal → PDF löschen + Tabs sperren
|
||
- Entsperrung nur durch alle Haken oder neues PDF
|
||
|
||
- [x] **Zweitarif-Zähler (HT/NT)** bei Strom + Verbrauchsberechnung
|
||
|
||
- [x] **Datumsformate vereinheitlichen** (01.01.2026 statt 1.1.2026)
|
||
|
||
- [x] **Audit-Log aussagekräftig** (Vorher/Nachher bei allen Änderungen)
|
||
|
||
- [x] **Impressum + Website-Datenschutzerklärung** im Kundenportal
|
||
- Editor in Einstellungen
|
||
- Vorschlagstexte
|
||
|
||
- [x] **Consent-Bestätigungs-Flow per Email**
|
||
- Alle Hebel müssen gesetzt sein
|
||
- Bestätigungsbutton + Bestätigungsemail
|
||
|
||
- [x] **Vertragsdokumente-Upload** (Auftragsformular, Lieferbestätigung, Vertragsunterlagen als PDF/PNG)
|
||
|
||
- [x] **Bug: Stressfrei-Email im Auftragsgenerator** (funktioniert jetzt im Vertrag)
|
||
|
||
- [x] **PDF-Auftragsvorlagen-System**
|
||
- Template-Editor in Einstellungen
|
||
- PDF hochladen, Formularfelder automatisch auslesen
|
||
- CRM-Felder zuordnen (visuell mit Vorschau)
|
||
- Seitenweise Sortierung der Felder
|
||
- Dynamische Rufnummern-Felder mit Vorwahl-Extraktion
|
||
- Nicht zugeordnete Felder bleiben editierbar
|
||
- Auftrag generieren aus Vertragsdaten (Button im Vertrags-Detail)
|
||
|
||
- [x] **Eigentümer-Verwaltung**
|
||
- An Adresse gehängt (Firma, Vorname, Nachname, Anschrift, Kontakt)
|
||
- Fallback auf Kundendaten wenn leer
|
||
- Nur bei Liefer-/Meldeadressen (nicht Rechnung)
|
||
- Namens-Kombinationen (Firma + Vorname + Nachname etc.)
|
||
|
||
- [x] **Gruppenauswahl Liefer-/Rechnungs-/Eigentümer-Adresse** im Auftragsgenerator
|
||
|
||
- [x] **Objekttyp + Lage + Lage des Anschlusses** bei Festnetz-Verträgen (DSL/Glasfaser/Kabel)
|
||
|
||
- [x] **Bankverbindung-Fallback** im PDF-Generator (neueste aktive Bankverbindung des Kunden)
|