docs: todo.md + README aktualisiert (Pentest-Fixes, Folgezähler,

SIM-cardUser, EmailProvider-Label-Override etc.)

todo.md: rund 14 neue Erledigt-Einträge seit dem letzten Stand –
gruppiert nach Feature (Folgezähler-Workflow, Anzeige-/UX-Polishing,
Pentest 42.5/43.5/43.6) und im klassischen "kompakter Header +
Bullets"-Stil.

README.md:
- Zähler-Bullet um Lieferadress-Pflicht + Folgezähler-Kette ergänzt
- Strom/Gas-Vertragsfelder um Verbrauchs-Schätzwert aus Vorvertrag,
  HT/NT, Sofort-/Neukunden-Bonus, Folgezähler-Wechseldatum + Endstand
- SIM-Karten-Liste um "Kartennutzer" für Firmen-/Familienverträge

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 11:10:23 +02:00
parent 9519f0dbca
commit c58a60db23
2 changed files with 163 additions and 5 deletions
+13 -5
View File
@@ -10,7 +10,9 @@ Web-basiertes CRM-System für Kundenverwaltung mit Verträgen (Energie, Telekomm
- **Adressen**: Mehrere Liefer-/Melde- und Rechnungsadressen pro Kunde - **Adressen**: Mehrere Liefer-/Melde- und Rechnungsadressen pro Kunde
- **Bankkarten**: Mit Ablaufdatum, Aktiv-Status und Dokument-Upload (PDF) - **Bankkarten**: Mit Ablaufdatum, Aktiv-Status und Dokument-Upload (PDF)
- **Ausweise**: Personalausweis, Reisepass, etc. mit Ablaufdatum und Dokument-Upload (PDF) - **Ausweise**: Personalausweis, Reisepass, etc. mit Ablaufdatum und Dokument-Upload (PDF)
- **Zähler**: Strom-/Gaszähler mit Zählerstandhistorie - **Zähler**: Strom-/Gaszähler mit Zählerstandhistorie,
Pflicht-Zuordnung zur Lieferadresse und Folgezähler-Kette
(Auto-Propagation auf alle Verträge mit dem Vorgänger)
- **Rechnungen**: Rechnungsverwaltung für Energieverträge mit Dokumenten-Upload - **Rechnungen**: Rechnungsverwaltung für Energieverträge mit Dokumenten-Upload
- **Vertrags-Cockpit**: Dashboard zur Überwachung offener Aufgaben (fehlende Dokumente, Rechnungen) - **Vertrags-Cockpit**: Dashboard zur Überwachung offener Aufgaben (fehlende Dokumente, Rechnungen)
- **Auto-Vertragsstatus**: Lieferbestätigung-Upload setzt `DRAFT``ACTIVE` (mit Vertragsbeginn), - **Auto-Vertragsstatus**: Lieferbestätigung-Upload setzt `DRAFT``ACTIVE` (mit Vertragsbeginn),
@@ -724,11 +726,15 @@ Je nach Vertragstyp werden unterschiedliche Felder im Formular angezeigt:
#### Strom & Gas (ELECTRICITY, GAS) #### Strom & Gas (ELECTRICITY, GAS)
- Zähler-Auswahl - Zähler-Auswahl (gefiltert auf die Lieferadresse des Vertrags)
- Jahresverbrauch (kWh/m³) - Jahresverbrauch (kWh/m³) bei Folgeverträgen mit Schätzwert
- Grundpreis, Arbeitspreis aus dem Vorvertrag und 1-Klick-Übernahme
- Bonus - Grundpreis, Arbeitspreis (HT/NT bei Zweitarif)
- Sofort-Bonus, Neukunden-Bonus
- Vorversorger, Kundennummer beim Vorversorger - Vorversorger, Kundennummer beim Vorversorger
- Folgezähler-Workflow (Zählerwechsel): Wechseldatum + optionaler
Endstand des alten Zählers, der automatisch als Zählerstand
erfasst wird und in die Verbrauchsberechnung einfließt
#### Internet (DSL, CABLE, FIBER) #### Internet (DSL, CABLE, FIBER)
@@ -752,6 +758,8 @@ Je nach Vertragstyp werden unterschiedliche Felder im Formular angezeigt:
- Rufnummer, SIM-Kartennummer (ICCID) - Rufnummer, SIM-Kartennummer (ICCID)
- PIN, PUK (verschlüsselt) - PIN, PUK (verschlüsselt)
- Multisim-Flag, Hauptkarte-Flag - Multisim-Flag, Hauptkarte-Flag
- Kartennutzer (optional) tatsächlicher Nutzer bei Firmen-/
Familienverträgen, kann vom Vertragsinhaber abweichen
> **Hinweis Multisim:** Nicht buchbar bei Klarmobil, Congstar, Otelo. Benötigt Freenet oder vergleichbar. > **Hinweis Multisim:** Nicht buchbar bei Klarmobil, Congstar, Otelo. Benötigt Freenet oder vergleichbar.
+150
View File
@@ -97,6 +97,156 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt ## ✅ Erledigt
- [x] **🆕 EmailProvider-Settings: Override-Feld „Bezeichnung im UI"**
- `customerEmailLabel` existierte im Backend (Schema +
Update-Logik + Public-Endpoint), war im UI aber nicht
erreichbar das Label kam ausschließlich aus
`deriveLabelFromDomain`. Jetzt neuer optionaler Input
„Bezeichnung im UI" unter dem Domain-Block in
Einstellungen → E-Mail-Provider. Leer = Auto-Ableitung,
befüllt = überschreibt das Tab-Label und alle anderen
Stellen, die `customerEmailLabel` nutzen (max 60 Zeichen).
- [x] **🆕 SIM-Karten: Feld „Kartennutzer"**
- Bei Firmen- und Familienverträgen weicht der
Vertragsinhaber (Firma/Eltern) vom tatsächlichen Nutzer
(Mitarbeiter/Kind) ab. Neuer optionaler `SimCard.cardUser`
(String), Migration `20260601100000_sim_card_user` mit
`IF NOT EXISTS`.
- ContractForm: eigene Zeile „Kartennutzer" oberhalb der
technischen SIM-Felder mit erklärendem Placeholder.
- ContractDetail: zeigt „Nutzer: \<Name\>" neben den
Hauptkarte/Multisim-Badges (nur wenn gefüllt).
- [x] **🆕 Vorgängervertrag-Modal: Kundennr./Vertragsnr. sichtbar machen**
- Die „Anbieter & Tarif"-Card im ContractDetailModal war
nur sichtbar, wenn Provider oder Tarif gesetzt waren
Bei Entwürfen ohne Anbieter wurden so auch
`customerNumberAtProvider` + `contractNumberAtProvider`
versteckt, obwohl sie gepflegt sein können. Fix:
Sichtbarkeitsbedingung um die beiden Felder erweitert.
- [x] **🆕 ContractEmails-Card: Quicklinks auch ohne Postfach**
- „Postfach öffnen" + „Stressfrei wechseln Adressen" waren
nur im Normal-Zweig sichtbar. Jetzt in einer gemeinsamen
`cardTitle`-Variable extrahiert und auch im
„Kein-Mailbox"-State sichtbar.
- [x] **🆕 Vertrags-Forms: Mini-Links zu Stammdaten**
- In ContractEmailsSection neben „Postfach öffnen" jetzt
zusätzlich „Stressfrei wechseln Adressen" → Kundenakte-Tab.
- In ContractForm kleine ExternalLink-Icons neben Select-Labels:
Lieferadresse/Rechnungsadresse → `?tab=addresses`,
Bankkarte → `?tab=bankcards`, Ausweis → `?tab=documents`,
Anbieter + Tarif → `/settings/providers`,
Vertriebsplattform → `/settings/platforms`.
- Select-Komponente nimmt jetzt `ReactNode` als label
(statt nur string), rückwärtskompatibel.
- In ContractDetail (Strom/Gas Card-Header) zusätzlich
permanenter „Zähler verwalten"-Link in neuem Tab.
- [x] **🆕 Vertragshistorie: Vertragsnummern als Link**
- Erkennt Vertragsnummern (`PREFIX-RANDOM`) in `title` und
`description` via Regex, löst sie gegen
`previousContract` + `followUpContract` des aktuellen
Vertrags auf und rendert sie als Link in neuem Tab.
Nicht aufgelöste Nummern bleiben als Text.
- [x] **🆕 UI-Bug-Fix: „Wurde sondergekündigt?"-Checkbox-Label**
- Label-Klasse war `flex` → Block-Layout über volle
`col-span-2`-Breite. Klick rechts neben dem Text
triggerte ungewollt die Checkbox. Fix: `inline-flex`.
- [x] **🆕 „Zähler verwalten"-Link im Folgezähler-Form**
- In der SuccessorMeterForm (Vertragsansicht) im Header
rechts neben „Folgezähler hinzufügen (Zählerwechsel)"
ein Link in neuem Tab zur Zähler-Verwaltung des Kunden,
damit man bei „Kein passender Zähler verfügbar" direkt
rüberspringen kann.
- [x] **🆕 Folgezähler-Forms: Checkbox „Alten Zähler deaktivieren"**
- Beide Folgezähler-Forms (Kundenakte MeterModal +
Vertragsansicht SuccessorMeterForm) bekommen eine
Checkbox, die standardmäßig angehakt ist. Beim Speichern
wird der Vorgänger automatisch auf `isActive=false`
gesetzt ein-klick-fähiger Zählerwechsel.
- Backend: `createMeter` mit `successorOf` und
`addSuccessorMeter` akzeptieren `deactivatePredecessor`
(Default true).
- [x] **🆕 Kundenakte → Zähler: Verträge-Aufklappliste + Filter**
- Pro Zähler ein „Verträge (N)"-Aufklapp, listet alle
Verträge auf, die diesen Zähler nutzen als Hauptzähler
(`energyDetails.meterId`) oder über Folgezähler-Kette
(`ContractMeter`). Dedupliziert. Jeder Eintrag ist Link
in neuen Tab mit Vertragsnummer, Anbieter, Status-Badge.
- Neue Checkbox „Zähler ohne Verträge anzeigen" neben
„Inaktive anzeigen". Filtert die Liste auf Orphans
(Zähler ohne Vertragszuordnung):
| Inaktive | ohne Verträge | Ergebnis |
|----------|---------------|----------|
| ☐ | ☐ | nur aktive Zähler (Default) |
| ☑ | ☐ | alle Zähler |
| ☐ | ☑ | aktive Zähler ohne Vertrag |
| ☑ | ☑ | alle Zähler ohne Vertrag |
- [x] **🆕 Folgezähler-Button auch bei Single-Meter-Verträgen**
- Folgeverträge ohne ContractMeter-Eintrag (alte Daten oder
pure Single-Meter-Verträge) bekamen den Button nie zu
Gesicht. Fix: Button wird jetzt aus dem if/else gerendert,
sobald entweder ein Single-Meter oder ContractMeter-
Einträge vorhanden sind. Im Backend wird der bisherige
`energyDetails.meterId` bei Single-Meter-Verträgen
automatisch als ContractMeter (position 0,
`removedAt` = Wechseldatum) backfillt, damit der alte
Zähler in der Historie bleibt.
- [x] **🆕 Multi-Meter-Verbrauch auf Vertragslaufzeit clampen**
- Bei Verträgen, die Vorgänger einer Folgevertrags-Kette
sind, hängen über `ContractMeter` auch Folgezähler dran,
die nach Vertragsende installiert wurden. Die Berechnung
nahm `cm.installedAt..cm.removedAt` 1:1 ohne Clamp gegen
`contract.startDate/endDate` damit flossen Zählerstände
aus der Folgevertrags-Phase in den Verbrauch des
Vorvertrags ein. Fix:
`meterStart = max(installedAt, contractStart)`,
`meterEnd = min(removedAt, contractEnd)`,
Zähler komplett außerhalb der Laufzeit werden übersprungen.
- [x] **🆕 Vertragsansicht: Standort + Inaktiv-Badge beim Zähler**
- Strom/Gas-Card zeigt jetzt neben der Zählernummer ein
rotes „Inaktiv"-Badge (falls deaktiviert) und darunter
eine kleine Zeile „Standort: …" (falls gepflegt).
- [x] **🛡️ Pentest 43.5 (INFO) + 43.6 (MEDIUM): History-XSS + blocked:-Marker**
- 43.6: `ContractHistoryEntry.title` + `.description` waren
auf beiden Pfaden ungestrippt Admin konnte HTML/Script-
Tags einschreiben, Portal-User las sie roh zurück. Fix:
`stripHtml()` auf Create + Update (Write-Pfad) und
`sanitizeEntry()` im List + Get (Read-Pfad).
- 43.5: `stripHtml` ersetzt `javascript:``blocked:`
sinnvoll bei URL-Feldern, hässlich in Tarif-/Preis-Namen.
Neuer `stripForDisplay`-Wrapper entfernt den Marker
zusätzlich in den Display-Feldern.
- [x] **🛡️ Pentest 42.5 (MEDIUM): priceFirst12Months XSS**
- Drei Preisfelder sind im Schema `String?` (freitextliche
Tarifangaben). `sanitizeContract` strippte sie auf dem
Read-Pfad nicht Alt-Daten mit XSS-Payloads kamen 1:1
raus. Fix: `priceFirst12Months`, `priceFrom13Months`,
`priceAfter24Months` in `CONTRACT_DISPLAY_STRING_FIELDS`
aufgenommen.
- [x] **🛡️ Anzeige-Defense: HTML in display-relevanten Strings strippen**
- `sanitizeContract` und `sanitizeCustomer` strippen jetzt
zusätzlich HTML in den definierten Display-Feldern
(`providerName`, `tariffName`, `customerNumberAtProvider`,
`firstName`, `lastName`, `companyName`, etc.). Wirkt auch
auf nested `previousContract` + `energyDetails`. Damit
sehen Pentester-Payloads (`<script>`, `<img onerror>`)
in der Vertragsliste nicht mehr als hässlicher Klartext
aus, sondern verschwinden komplett.
- [x] **🆕 Vorvertrag-Verbrauch als Schätzwert im Folgevertrag** - [x] **🆕 Vorvertrag-Verbrauch als Schätzwert im Folgevertrag**
- **ContractForm** (Strom/Gas): Wenn ein `previousContractId` - **ContractForm** (Strom/Gas): Wenn ein `previousContractId`
gesetzt ist, wird der Vorvertrag samt Readings nachgeladen und gesetzt ist, wird der Vorvertrag samt Readings nachgeladen und