24 KiB
📋 OpenCRM – Todo-Liste
🔜 Offen
Manuelle Tests (vor Release durchklicken)
Checklisten für Security + Email-Log-System stehen in docs/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
tenantIdim 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.devia 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 lassenpayment_failed→ Banner im CRM, nach X Tagen pausierenmandate_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):
- Kunde registriert sich auf Landing Page (Name, Firma, E-Mail, Wunsch-Subdomain)
- Admin-Portal: Trial-Instanz starten
- DB erstellen, Docker-Container hochfahren, Caddy-Config für Subdomain
- Einladungs-Email mit Admin-Login + Passwort-Reset-Link
- Tag 25: Erinnerungs-Email "Deine Trial läuft bald ab"
- Tag 30: Banner im CRM "Jetzt bezahlen oder pausieren"
- Kunde erfasst GoCardless-Mandat im Admin-Portal-Login
- Bei erfolgreicher Zahlung: Instanz bleibt aktiv
- 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.ymlpro 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
-
🔄 Automatische Vertrags-Status-Übergänge
- Nightly-Cron (02:00 + Catch-up 60s nach Start): alle Verträge mit
status=ACTIVEundendDate < heute→EXPIRED(mit Audit-Log). - Beim Upload der Kündigungsbestätigung (
cancellationConfirmationPath): wenn Vertrag aktuellACTIVE→ aufCANCELLEDsetzen (Audit-Log). Frontend fragt per Modal das Bestätigungs-Datum ab (Default: heute), wird direkt alscancellationConfirmationDategespeichert. Der "Optionen"-Upload löst den Status-Wechsel bewusst NICHT aus, da er für Vertragsänderungen (nicht echte Kündigungen) gedacht ist, setzt abercancellationConfirmationOptionsDateanalog. - Beim Upload einer
Lieferbestätigung(ContractDocument via direkt-Upload oder Email-Anhang-Import): wenn Vertrag aktuellDRAFT→ aufACTIVEsetzen +startDateauf das erfasste Lieferdatum (falls leer). Frontend zeigt Datums-Input conditional, wenn Typ "Lieferbestätigung" ausgewählt ist. - Keine neuen Status eingeführt:
cancellationSentDatevs.cancellationConfirmationDategenügen, um "gesendet vs. bestätigt" abzubilden.ACTIVEbleibt bis zur Bestätigung.
- Nightly-Cron (02:00 + Catch-up 60s nach Start): alle Verträge mit
-
🛡️ Security-Review + Hardening vor Production-Deployment (3 Runden)
-
Vollständiger Review aller kritischen Bereiche, dokumentiert in docs/SECURITY-REVIEW.md
-
Runde 1 – 6 kritische + 2 wichtige Findings gefixt:
- CORS offen →
CORS_ORIGINSexplizit - Helmet + Security-Headers
- JWT-Fallback-Secret entfernt (Fail-Fast beim Start)
- IDOR bei 7 Contract-Endpoints
- XSS via Email-Body (DOMPurify)
- Customer-API Data Exposure (Passwort-Hashes)
- Portal-JWT-Invalidation nach Passwort-Reset
- Body-Size-Limit 5 MB
- CORS offen →
-
Runde 2 – Deep-Dive mit parallelen Audit-Agents, 5 weitere kritische + 2 wichtige:
- Zip-Slip im Backup-Upload (Arbitrary File Write!)
- Mass Assignment bei Customer/User (Privilege Escalation via
roleIds!) - 13 weitere IDOR-Stellen (Meter-Readings, Email-Anhänge, StressfreiEmail-Credentials …)
- Path-Traversal bei Backup-Name und GDPR-Proof-Download
-
Runde 3 – Tiefer Dive (8 weitere Hardenings):
- JWT algorithm confusion:
jwt.verifyaufalgorithms: ['HS256']festgenagelt trust proxy = 1für Rate-Limiter hinter Reverse-Proxy (sonst unwirksam)- IDOR Invoice (alte
/api/energy-details/:ecdId/invoices): jetztcanAccessEnergyContractDetails→ Contract → customerId - IDOR PDF-Template-Generator (
:id/generate/:contractId): jetztcanAccessContract - Email-Anhang-Download: Content-Type-Safelist (HTML/SVG nie inline) +
X-Content-Type-Options: nosniff+ Filename-CRLF-Sanitizing - Provider/Tariff-GETs:
requirePermission('providers:read')(Portal-Kunden sehen Provider-Liste nicht mehr) - SMTP-Header-Injection: zentrale CRLF-Validierung in
smtpService.sendEmail(schützt alle Caller) - bcrypt cost 10 → 12 (OWASP 2026)
- JWT algorithm confusion:
-
Runde 8 – Loose Ends (DNS-Rebinding + Per-File-Ownership):
- DNS-Rebinding-Schutz in test-connection / test-mail-access:
Hostnames werden vor Connect via
dns.resolve4/6aufgelöst und jede IP gegen die SSRF-Block-Liste geprüft. Connection läuft anschließend gegen die IP, der ursprüngliche Hostname alstls.servernamefür SNI/Cert-Validation. Ein zweiter DNS-Lookup kann keine geblockte IP unterschieben. - Per-File-Ownership-Check statt freiem static-Handler:
app.use('/api/uploads', authenticate, express.static)wird ersetzt durchGET /api/files/download?path=.... Der Controller mappt den Pfad via DB-Lookup auf Customer/Contract und delegiert ancanAccessCustomer/canAccessContract– ein eingeloggter Portal-Kunde kann jetzt nur seine eigenen (oder vertretene mit Vollmacht) Dateien laden, selbst wenn er fremde Filenames irgendwo mitgeschnitten hätte./api/uploads/*bleibt als Backwards-Compat-Shim erhalten, ruft aber denselben Owner-Check. - 12 subDir-Mappings: bank-cards, documents, business-/commercial-/ privacy-, authorizations, contract-documents, invoices, alle 4 cancellation-* + pdf-templates (admin-only).
- Frontend
fileUrl()zeigt jetzt auf den neuen Endpoint. Path-Traversal wird sowohl per Format-Validation (begin /uploads/, no '..') als auch durch absoluten Path-Vergleich gegen uploadsRoot geblockt. - Live-verifiziert: Portal-User lädt eigene Contract-Datei (200), random Pfad (404), Traversal (400), kein Token (401), Backwards- Compat-Shim (200).
Bewusst NICHT gemacht (für v1.2):
- Signierte URLs mit kurzlebigen Download-Tokens statt JWT-im-Query (verhindert Token-Leak via Logs/Referrer). Nicht trivial wegen -Downloads ohne JS, lassen wir bis später.
- DNS-Rebinding-Schutz in test-connection / test-mail-access:
Hostnames werden vor Connect via
-
Runde 7 – Letzter Schliff (SSRF + Logout):
- SSRF-Schutz in
test-connectionundtest-mail-access: ein Admin-User konnte über die Plesk-API-URL bzw. SMTP/IMAP-Server-Felder Connections zu beliebigen IPs auslösen (Cloud-Metadata-Endpoints, Link-Local, AWS/GCP-Metadata-Hosts). Internal-Port-Scanning via Timing-Differenzen war messbar (22/80/3306/5432/6379 unterschiedlich). Fix: neuer Helperutils/ssrfGuard.tsblockiert vor jeder ausgehenden Verbindung 169.254.0.0/16, 0.0.0.0/8, Multicast/Reserved-Ranges, AWS-IPv6-Metadata, IPv6-Link-Local und bekannte Cloud-Metadata- Hostnames (metadata.google.internal etc.). Loopback (127.0.0.0/8) bleibt erlaubt für legitime Plesk/Postfix-Setups. - Logout-Endpoint
POST /api/auth/logout: setzttokenInvalidatedAt/portalTokenInvalidatedAtauf jetzt. Auth- Middleware prüft das Feld und lehnt Tokens mitiatdavor ab. JWTs sind stateless – ohne diesen Mechanismus bleibt ein „abgemeldeter" Token bis zum natürlichen Expiry (7d) gültig. - Live-verifiziert: 169.254.169.254/metadata.google.internal/0.0.0.0 werden mit 400 abgelehnt; 127.0.0.1 weiter erlaubt; Logout invalidiert den Token sofort (HTTP 401 „Sitzung ungültig").
Geprüft + sauber (Runde 7):
- Public Consent (random Hash → 404, kein Brute-Force durch 122-bit-UUID)
- Magic-Bytes-Bypass beim Upload (HTML als image/png) → blockiert
- PDF-Generation mit injizierten manualValues → kein XSS-Vektor (PDFs sind keine Web-Renderer)
- Audit-Logs für Portal-User: 403
- Email-Config-Update als Portal: 403
- Backup-Endpoints als Portal: 403
- Query-Filter-Override (?customerId=X) → vom Portal-Filter ignoriert
Bewusst NICHT gefixt (zu invasiv für v1.0):
- Vollständige DNS-Resolution beim SSRF-Guard (gegen DNS-Rebinding) – kann legitimes CDN/Caching brechen, v1.1-Item.
- Per-File-Ownership-Check bei
/api/uploads(siehe Runde 5).
- SSRF-Schutz in
-
Runde 6 – Tiefer Live-Pentest (auf Wunsch des Users, „bevor andere es tun"):
- 🚨
GET /api/customersleakte als Portal-User die komplette Kundendatenbank (alle Namen, E-Mails, customerNumber etc.). Der Single-Endpoint war Stage 4 mitcanAccessCustomergefixt, der List- Endpoint nicht. Jetzt: Portal-User bekommt nur eigene + vertretene Kunden (Filter im Controller). - 🚨 Rate-Limit-Bypass via
X-Forwarded-For: 12+ Login-Versuche mit rotierenden XFF-Werten gingen alle durch ohne 429.trust proxy = 1hat naiv jedem XFF-Wert vertraut. Jetzt:trust proxy = 'loopback'– XFF wird nur akzeptiert wenn die Connection von 127.0.0.1 / ::1 kommt (= lokaler Reverse-Proxy). Plus:LISTEN_ADDR=127.0.0.1in Production- Default, damit das Backend nicht direkt von außen ansprechbar ist. - Self-Grant + Existence-Disclosure in
toggleMyAuthorization:- Portal-User konnte sich selbst Vollmacht erteilen (1→1) und
Datensätze für beliebige
representativeIds anlegen (auch nicht- existierende, scheiterte erst auf DB-Constraint mit Prisma-Stack-Leak). - 404 vs 403 erlaubte Existence-Probing der gesamten customer-ID-Range.
- Fix: Self-Grant 400er. Existenz + aktives
CustomerRepresentative- Verhältnis in einem Query, beide Fehlfälle identisch 403.
- Portal-User konnte sich selbst Vollmacht erteilen (1→1) und
Datensätze für beliebige
- Prisma-Error-Leak generisch in
toggleMyAuthorization: keine Prisma-Stacks mehr im Response. - Live-verifiziert: Customer-Liste 3 statt 3000 (jetzt nur erlaubte),
Self-Grant 400, Existence-Disclosure dicht (alle 403 uniform), Auth
auf
/api/customers/:id200/403 (kein 404-Leak).
Geprüft + sauber (Runde 6):
- Prototype Pollution beim Login → kein Effekt
- HTTP-Method-Override via Header → ignoriert
- Path-Traversal in Backup-Name → durch Regex blockiert
- Developer-Routes existieren nicht (404)
- Email-Endpoints (Send/Sync/Read mit fremder StressfreiEmail-ID) → 403
- Self-grant Vollmacht via
customers/X/representatives→ 403 (perm) /api/customers/:idGET: 200 für eigene, 403 sonst (kein 404-Leak)
Offen für v1.1:
/api/contracts/:idGET liefert 404 für nicht-existente IDs (Existence- Probing). Da contractIds aber nicht direkt mit personenbezogenen Daten korrelieren, niedrig-Prio. Vereinheitlichung auf 403 wäre sauberer.- Prisma-Error-Leaks in anderen Admin-Endpoints (z.B.
addInvoicebei Validation-Fehler) – Defense-in-Depth-Kandidat.
- 🚨
-
Runde 5 – Hack-Das-Ding-Audit (Live-Pentest + 3 parallele Audit-Agents):
- 🚨
/api/uploads/*war OHNE AUTH erreichbar (DSGVO-GAU!) – jetzt hinterauthenticate. Direkte -Links nutzen?token=...Query-Parameter, unterstützt von auth-Middleware. Frontend-HelperfileUrl(path)hängt Token automatisch an, 24 URLs migriert (CustomerDetail, ContractDetail, InvoicesSection, PdfTemplates, GDPRDashboard). - Login-Timing-Side-Channel: Bei ungültigem User fehlte
bcrypt.compare→ 110ms vs 10ms, User-Enumeration trivial. Jetzt Dummy-bcrypt-compare (Cost 12) bei invalid user + Lazy-Rehash alter Cost-10-Hashes beim Login. Live-verifiziert: 422ms vs 425ms – Timing-Angriff dicht. - XSS via Privacy Policy / Imprint: 4 Frontend-Seiten renderten
Backend-HTML ohne DOMPurify (
PortalPrivacy,ConsentPage,PortalWebsitePrivacy,PortalImprint). Admin-eingegebene<script>-Tags wären bei jedem Portal-Kunden-Besuch ausgeführt worden. Jetzt mit strikter Sanitize-Config (FORBID_TAGS/ATTR). - IDOR-Härtung Upload/Delete/SaveAttachment:
canAccessContractjetzt inuploadContractDocument,deleteContractDocument, im generischenhandleContractDocumentUpload(Kündigungsschreiben + -bestätigungen) und insaveAttachmentAsContractDocument. Defense-in-Depth, blockt auch bei künftigen Staff-Scoping-Rollen. - Global Error-Handler:
err.statuswird respektiert (413/400 statt 500).
Offen für v1.1:
- Per-File-Ownership-Check bei
/api/uploads/*(aktuell reicht Authentifizierung, kein Datei-spezifischer Owner-Check). Implementierung bräuchte dediziertenGET /api/files/download?path=...-Endpoint mit DB-Lookup, welche Ressource zur Datei gehört. - TipTap-Link-Tool:
javascript:-Protokoll blockieren (Admin-only erreichbar, niedrig-Prio).
- 🚨
-
Runde 4 – Live-Tests gegen Dev-Server deckten 9 weitere IDORs auf:
getCustomer+getAddresses/getBankCards/getDocuments/getMeters/getRepresentatives/getPortalSettingshatten NUR Daten-Sanitizer aber KEINENcanAccessCustomer-Checkgdpr.getCustomerConsents+getAuthorizations+checkConsentStatusebenso ungeschützt- Portal-Kunde konnte live per
GET /api/customers/<fremde-id>kompletten Fremdkunden-Datensatz auslesen → jetzt 403 - Error-Handler:
err.statuswird jetzt respektiert (413/400 statt pauschalem 500)
Live-verifiziert als Portal-Kunde gegen fremden Test-Kunden #4:
Endpoint Vorher Nachher GET /api/customers/4🚨 200 mit Daten ✅ 403 GET /api/customers/4/addresses🚨 200 ✅ 403 GET /api/customers/4/bank-cards🚨 200 ✅ 403 GET /api/customers/4/documents🚨 200 ✅ 403 GET /api/customers/4/meters🚨 200 ✅ 403 GET /api/customers/4/representatives🚨 200 ✅ 403 GET /api/gdpr/customer/4/consents🚨 200 mit Consent-Daten ✅ 403 GET /api/gdpr/customer/4/authorizations🚨 200 ✅ 403 GET /api/gdpr/customer/4/consent-status🚨 200 ✅ 403 Eigene Daten /api/customers/1✅ 200 ✅ 200 (unverändert) 12 MB Body 500 „Interner Serverfehler" ✅ 413 „Anfrage zu groß" Malformed JSON 500 „Interner Serverfehler" ✅ 400 „Ungültiges JSON" -
Deployment-Checkliste komplett
-
-
🎉 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
- Passwort vergessen-Flow (Login → "Passwort vergessen?" Link)
-
Mandantenfähigkeit: Domain + Kunden-E-Mail-Label dynamisch pro Provider
- Neues Feld
customerEmailLabelam 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.deStrings 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
- Neues Feld
-
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:defaultsliestbackend/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
-
Email-Anhänge → Vertragsdokumente + Rechnungen für alle Vertragstypen
- Im SaveAttachmentModal (bei einem per Email zugeordneten Vertrag) gibt es jetzt drei Modi:
- Als Dokument (in feste Slots wie Kündigungsschreiben) – wie bisher
- Als Vertragsdokument – neu, mit Typ-Dropdown (Auftragsformular, Lieferbestätigung, Vertragsunterlagen, Vollmacht, Widerrufsbelehrung, Preisblatt, Sonstiges) + Notizen
- 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
saveAttachmentAsContractDocumentfür die flexible ContractDocument-Tabelle
- Im SaveAttachmentModal (bei einem per Email zugeordneten Vertrag) gibt es jetzt drei Modi:
-
Geburtstag-Management-Modal in Kundenstammdaten
- Neuer Button (Cake-Icon) neben Geburtsdatum öffnet Modal
- Gruß zurücksetzen: setzt
lastBirthdayGreetingYearauf 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
-
Anrede-Verhältnis Du/Sie pro Kunde
- Neues Feld
useInformalAddressin 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")
- Neues Feld
-
Geburtsdatum + Geburtsort auch bei Firmenkunden
- Felder werden jetzt unabhängig vom Kundentyp angezeigt
- Ermöglicht z.B. Geburtstage für Ansprechpartner bei Firmen
-
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
-
Typspezifische Zusatzinfos in Vertragslisten
- Strom/Gas → "Lieferadresse: ..."
- DSL/Glasfaser/Kabel → "Anschlussadresse: ..."
- Mobilfunk → "Rufnummer: ..."
- KFZ → "Kennzeichen: ..."
- Sichtbar in Admin-Liste, Portal-Liste und Kunden-Tab
-
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
-
Zweitarif-Zähler (HT/NT) bei Strom + Verbrauchsberechnung
-
Datumsformate vereinheitlichen (01.01.2026 statt 1.1.2026)
-
Audit-Log aussagekräftig (Vorher/Nachher bei allen Änderungen)
-
Impressum + Website-Datenschutzerklärung im Kundenportal
- Editor in Einstellungen
- Vorschlagstexte
-
Consent-Bestätigungs-Flow per Email
- Alle Hebel müssen gesetzt sein
- Bestätigungsbutton + Bestätigungsemail
-
Vertragsdokumente-Upload (Auftragsformular, Lieferbestätigung, Vertragsunterlagen als PDF/PNG)
-
Bug: Stressfrei-Email im Auftragsgenerator (funktioniert jetzt im Vertrag)
-
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)
-
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.)
-
Gruppenauswahl Liefer-/Rechnungs-/Eigentümer-Adresse im Auftragsgenerator
-
Objekttyp + Lage + Lage des Anschlusses bei Festnetz-Verträgen (DSL/Glasfaser/Kabel)
-
Bankverbindung-Fallback im PDF-Generator (neueste aktive Bankverbindung des Kunden)