Commit Graph

37 Commits

Author SHA1 Message Date
duffyduck ec577e6d76 Pentest 68.1 (LOW) + 68.2 (INFO): PDF-Active-Content-Filter + Modal-Limit
68.1: Magic-Byte-Check prüfte nur %PDF-. PDFs mit /JavaScript, /JS,
/Launch, /EmbeddedFile, /RichMedia (Flash) kamen durch und wurden
inline ausgeliefert – Browser-Viewer ignorieren JS, Adobe Acrobat
nicht.

- Neuer Helper assertSafePdf(buf) in utils/sanitize.ts mit
  case-sensitivem String-Scan auf die fünf Action-Patterns
  (\b-Word-Boundary verhindert False-Positives bei /JSXForm etc.).
- Neue Middleware pdfUploadSafety.ts mit zwei Varianten:
  requireSafeUploadedPdf (PDF-only) und scanUploadedPdfIfPresent
  (durchwinkt JPG/PNG, scannt nur PDFs).
- Eingehängt in: upload.routes (Magic-Byte-Validator erweitert),
  gdpr.routes Vollmacht-Upload, pdfTemplate.routes Template-Upload,
  contract.routes Vertragsdokumente, cachedEmail.controller
  (saveAttachmentTo, saveAttachmentAsInvoice,
  saveAttachmentAsContractDocument).
- Inline-Vorschau bleibt – Pentester-Empfehlung "disposition=inline
  abschalten" wurde bewusst nicht umgesetzt (löst Acrobat-Risiko
  nicht, bricht aber ~20 UI-Stellen).
- Smoke-Test: 5 Payload-Typen abgelehnt, clean PDF + Non-PDF + JSXForm
  durchgewinkt.

68.2: JpgToPdfModal-Self-DoS – MAX_IMAGES=50, MAX_IMAGE_BYTES=25MB.
2026-06-03 13:18:23 +02:00
duffyduck 30f528596c JPGs → PDF: neuer Button überall bei PDF-Upload
- Neue Komponente JpgToPdfModal (jsPDF clientseitig, kein Backend-Roundtrip).
- Bilder hinzufügen per Klick, Drag&Drop oder Strg+V (Clipboard).
- Reihenfolge per Drag&Drop sortierbar; pro Bild 90°/180°-Drehung +
  horizontal/vertikal-Spiegelung.
- Jedes Bild = eine A4-Seite, Orientation automatisch nach Bild,
  JPEG-Qualität 100%.
- FileUpload-Komponente zeigt den Sekundär-Button automatisch, sobald
  accept PDF einschließt (Datenschutz, Vollmacht, Bankkarten, Ausweise,
  Gewerbeanmeldung, Handelsregister, Kündigungsschreiben/-bestätigung
  + jeweilige Optionen).
- Direktinputs ebenfalls erweitert: Vertragsdokumente (ContractDetail),
  Vollmacht-Tab (CustomerDetail), Rechnungen (InvoicesSection).
- PdfTemplates bewusst ausgenommen – braucht AcroForm-Felder.
2026-06-03 12:27:37 +02:00
duffyduck 0f4ffe3c32 E-Mail als PDF speichern: Tab "Vertragsdokument" ergänzt
Bisher hatte das "E-Mail als PDF speichern"-Modal nur die Tabs
"Als Dokument" + "Als Rechnung" (nur Energieverträge). Wenn die
E-Mail einem Vertrag zugeordnet ist, fehlte die Möglichkeit, sie
direkt als Vertragsdokument (Auftragsformular, Lieferbestätigung
etc.) zu hinterlegen – analog zum Anhang-Modal.

Backend: neuer Endpoint POST /api/emails/:id/save-as-contract-document
{ documentType, notes?, deliveryDate? } – generiert das Mail-PDF,
speichert es unter /uploads/contract-documents und legt einen
ContractDocument-Eintrag an. Bei documentType "Lieferbestätigung"
wird der bestehende maybeActivateOnDeliveryConfirmation-Workflow
getriggert (DRAFT → ACTIVE, startDate-Übernahme).

Frontend: SaveEmailAsPdfModal bekommt den dritten Tab parallel zu
SaveAttachmentModal. Tab erscheint, sobald die E-Mail einem Vertrag
zugeordnet ist (auch bei Nicht-Energieverträgen); Tab "Als Rechnung"
bleibt auf Energieverträge beschränkt. Dokumenttyp-Dropdown und
Notizen-Feld werden aus dem Anhang-Modal übernommen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 19:15:23 +02:00
duffyduck cd7075e96f Quicklinks auch im "Kein Postfach"-Zustand der E-Mails-Card anzeigen
Der "Stressfrei wechseln Adressen"-Link (sowie "Postfach öffnen")
war nur im Normal-Zweig sichtbar, nicht aber wenn der Kunde noch
gar kein Mailbox-Konto hat. cardTitle in einer gemeinsamen Variable
extrahiert und in beiden Branches verwendet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 08:23:40 +02:00
duffyduck 9e3bce85f0 Vorgängervertrag-Modal: Kundennr./Vertragsnr. beim Anbieter auch ohne Provider/Tarif anzeigen
Die "Anbieter & Tarif"-Card war nur sichtbar, wenn provider oder
tariff gesetzt waren. Bei Entwürfen ohne Anbieter wurden dadurch
auch customerNumberAtProvider + contractNumberAtProvider versteckt,
obwohl sie pflegbar sind und für den Wechsel-Workflow wichtig sind.
Fix: Card-Sichtbarkeitsbedingung um die beiden Felder erweitert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 07:51:36 +02:00
duffyduck 4fb700cf57 Vertrags-Forms: Mini-Links zu Stammdaten in neuem Tab
ContractEmailsSection (Vertragsansicht): Zusätzlich zu "Postfach
öffnen" gibt es jetzt "Stressfrei wechseln Adressen" → Tab in der
Kundenakte.

ContractForm (Bearbeiten): Kleine ExternalLink-Icons neben den
Select-Labels:
- Lieferadresse + Rechnungsadresse → Kundenakte/Adressen
- Bankkarte → Kundenakte/Bankkarten
- Ausweis → Kundenakte/Ausweise
- Anbieter + Tarif → Settings/Anbieter & Tarife
- Vertriebsplattform → Settings/Vertriebsplattformen

Select-Komponente nimmt jetzt ReactNode als label (statt nur string),
um JSX-Labels mit eingebettetem Link zu erlauben. Rückwärts-
kompatibel zu allen bestehenden String-Aufrufen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 07:47:40 +02:00
duffyduck 3a9cece929 Vertragshistorie: Vertragsnummern als Link in neuem Tab
Erwähnte Vertragsnummern (Pattern PREFIX-RANDOM) in Title und
Description werden gegen previousContract + followUpContract des
aktuellen Vertrags aufgelöst und als Link mit target="_blank"
gerendert. Nicht aufgelöste Nummern bleiben als Text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 14:33:11 +02:00
duffyduck d92d0b1eaf Vertrags-Mail-Card: Link "Postfach öffnen" → Kunden-Postfach in neuem Tab
Neben dem "E-Mails"-Titel der Card im Vertragsdetail jetzt ein
kleiner Link mit ExternalLink-Icon, der den Tab "E-Mail-Postfach"
in der Kundenakte in einem neuen Tab öffnet (target="_blank",
rel="noopener noreferrer"). Greift auf das bereits unterstützte
?tab=emails-Deep-Link-Pattern in CustomerDetail zurück.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 12:23:29 +02:00
duffyduck 0bd2f9be7e fix: "Anzeigen"-Buttons öffnen Datei wieder im Browser-Tab
Folge-Symptom des Pen-30.13-Fixes: alle file-downloads liefen mit
Content-Disposition: attachment – das ist gegen Stored-XSS richtig,
hat aber die "Anzeigen"-Buttons (Bankkarten / Ausweise /
Verträge / etc.) kaputtgemacht, weil der Browser jetzt
herunterlud statt im Tab zu öffnen.

Magic-Byte-basierter Whitelist-Pfad eingebaut: optional ?disposition=
inline am Download-Endpoint, ABER nur wenn die ersten Bytes der
Datei das Magic eines safe Typs zeigen (PDF, PNG, JPEG, GIF, WebP).
Bei Mismatch fällt's auf attachment zurück – Stored-XSS bleibt
weiterhin unmöglich, falls jemand HTML als .pdf hochlädt.

Frontend: neuer viewUrl(path)-Alias = fileUrl(path, {inline: true}).
Alle Stellen mit `<a href={fileUrl(...)} target="_blank">` oder
`window.open(fileUrl(...), '_blank')` (13 Stellen über CustomerDetail,
ContractDetail, PdfTemplates, GDPRDashboard, InvoicesSection)
nutzen jetzt viewUrl. Download-Stellen bleiben fileUrl
(= attachment, byte-genaues File-Save).

Live-verifiziert auf dev:
- ohne Param: attachment (default, Stored-XSS-Schutz)
- ?disposition=inline + echte PDF: inline + application/pdf
- ?disposition=inline + HTML als .pdf: attachment (Magic-Mismatch
  → Browser lädt herunter statt zu rendern)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 09:19:04 +02:00
duffyduck 771f46d2ac Vertragsansicht: Kunden-Schnellansicht-Modal + Cent/Euro-Input
Feature 1 – Kunden-Schnellansicht:
Info-Icon neben dem Kundenlink im Vertragsdetail oeffnet ein Modal
mit den wichtigsten Kundendaten (Firma, Name, Geburtsdatum/-ort,
Gruendungsdatum, Adresse, Telefon, Mobil, E-Mail, Portal-E-Mail,
Steuer-/Handelsregisternr). Jedes Feld hat einen Copy-Button.
Lazy-Fetch via customerApi.getById, staleTime 30s.

Feature 2 – Cent/Euro-Doppel-Input:
Neben dem €/kWh-Arbeitspreis-Feld jetzt ein zweites ct/kWh-Feld.
Bidirektional gekoppelt – Tippen in € aktualisiert ct (×100),
Tippen in ct aktualisiert € (÷100). Backend speichert weiterhin
nur den Euro-Wert; Cent ist reine UI-Hilfe. Float-Rausch-Schutz
verhindert "0.25 → 25.0000000000004". Greift fuer unitPrice und
unitPriceNt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 14:41:37 +02:00
duffyduck f6df97226d feat(email): Weiterleiten + Erneut senden im Detail-Pane
Zwei Aktionen, die der existierende Reply-Pfad bisher nicht abdeckte:

1. Weiterleiten (Compose-Modal-Forward-Modus):
   - Neuer Button im EmailDetail, neben "Antworten"
   - ComposeEmailModal akzeptiert jetzt einen `forwardOf` prop und
     füllt das Formular im Forward-Stil vor:
     * To leer (User trägt selbst ein)
     * Subject mit "Fwd:"-Prefix
     * Body mit zitierten Headern (Von, An, Datum, Betreff) +
       Original-Text
   - Titel des Modals reagiert ("Antworten" / "Weiterleiten" /
     "Neue E-Mail")

2. Erneut senden (One-Click-Resend):
   - Neuer Button im EmailDetail; schickt die Mail nochmal an die
     ursprüngliche toAddresses (= die Stressfrei-Adresse selbst).
     Plesk routet dann gemäß der HEUTE hinterlegten Forwards –
     Use-Case: die Stressfrei-Forward-Adresse wurde nach Empfang
     umgestellt, der Empfang soll beim neuen Forward-Empfänger
     landen.
   - Confirm-Dialog erklärt den Vorgang und warnt explizit, dass
     Anhänge nicht erneut mit gesendet werden (Anhänge wären
     IMAP-Refetch, dafür "Weiterleiten" nutzen).
   - Toast-Feedback für Erfolg/Fehler.
   - Im TRASH-Folder wird der Resend-Button bewusst nicht
     eingeblendet (kein sinnvoller Use-Case dort).

Backend braucht keine neuen Endpoints – beide Aktionen nutzen die
bestehenden `stressfreiEmailApi.sendEmail` + `cachedEmailApi.getById`
(letztere für den Body, der ohnehin schon im Detail-View geladen ist).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 14:46:44 +02:00
duffyduck 185b38dc55 feat(email): Suchleiste + erweiterte Filter im Email-Postfach
Variante B aus der Trade-off-Diskussion: Suchleiste über der Email-Liste
plus eine ausklappbare Box mit Detail-Filtern, alle AND-verknüpft.

Backend:
- EmailListOptions um search + 9 Detail-Filter erweitert (fromFilter,
  toFilter, subjectFilter, bodyFilter, attachmentNameFilter,
  hasAttachments, isRead, isStarred, receivedFrom, receivedTo)
- getCachedEmails baut die where-Klausel:
  * `search` → OR über Subject/From-Address/From-Name/Body (Volltext-
    Quicksearch)
  * Feldspezifische Filter werden AND-verknüpft an die where gehängt;
    From-/Body-Filter intern als kleine OR-Subqueries (Match in
    Adresse ODER Name; Match in textBody ODER htmlBody)
- Controller-Parser akzeptiert die Filter als Query-Parameter
  (parseBoolParam/parseDateParam tolerieren leere/invalide Werte)

Frontend:
- Suchleiste mit X-Button zum Leeren + Filter-Toggle mit Badge (zeigt
  Anzahl aktiver Filter)
- Ausklappbare Filter-Box: Von, An, Betreff, Inhalt, Datum von/bis,
  Anhang-Dateiname, Mit/Ohne Anhang, Gelesen-Status, Markiert-Status
- Filter-State fließt via useMemo + queryKey in den useQuery → React
  Query macht automatisch ein Re-Fetch bei jeder Änderung
- "Alle zurücksetzen"-Button räumt komplett auf
- Nicht für TRASH-Folder eingeblendet (eigener Pfad ohne Filter-API)

Bewusst nicht gebaut: voller AND/OR-Builder mit Plus-Button und
Bool-Verschachtelung. Reale Such-Use-Cases im Email-Kontext sind
quasi immer AND-verknüpft; Bool-Builder bringt mehr Bedienprobleme
als Mehrwert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 14:31:43 +02:00
duffyduck 35745ce3bb Security-Hardening Runde 5: Hack-Das-Ding (DSGVO-GAU + Timing + XSS)
Live-Pentest gegen Dev-Server + 3 parallele Audit-Agents.

🚨 CRITICAL: /api/uploads/* war ohne Auth erreichbar
- express.static('/api/uploads', ...) → jeder konnte mit ratbarer URL
  sensible PDFs (Kündigungsbestätigungen, Ausweise, Bankkarten,
  Vollmachten) ziehen. Live-verifiziert: 23-KB-PDF eines echten Kunden
  ohne Login geladen.
- Fix: authenticate-Middleware vor static-Handler (req.query.token
  unterstützung war schon da, jetzt aktiv genutzt).
- Frontend: utils/fileUrl.ts hängt JWT als ?token=... an. 24 direkte
  /api${...Path}-URLs in 5 Dateien per Skript migriert (CustomerDetail,
  ContractDetail, InvoicesSection, PdfTemplates, GDPRDashboard).

🚨 HIGH: Login-Timing User-Enumeration
- bcrypt.compare wurde nur bei existierenden Usern ausgeführt → 110ms
  vs 10ms Differenz, Email-Enumeration trivial messbar.
- Fix: Dummy-bcrypt-compare bei invalid user (Cost 12). Plus Lazy-
  Rehash bei erfolgreichem Login: alte Cost-10-Hashes (z.B. admin aus
  Installation) werden auf BCRYPT_COST upgraded, damit Dummy- und
  Echt-Hash-Cost zusammenpassen.
- Live-verifiziert nach Admin-Rehash: 422ms (invalid) vs 423ms (valid)
  – Side-Channel dicht.

🚨 HIGH: XSS via Privacy-Policy/Imprint-HTML
- 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 – auch auf der öffentlichen Consent-Seite.
- Fix: DOMPurify.sanitize mit strikter FORBID_TAGS/ATTR Config.

🛡 HIGH: IDOR-Härtung an Upload-/Document-Endpoints
- canAccessContract jetzt in: uploadContractDocument,
  deleteContractDocument, handleContractDocumentUpload (Kündigungs-
  Letter+Confirmation), handleContractDocumentDelete,
  saveAttachmentAsContractDocument.
- Defense-in-Depth: aktuell durch requirePermission abgesichert,
  schützt auch gegen künftige Staff-Scoping-Rollen.

Offen für v1.1:
- Per-File-Ownership-Check für /api/uploads (Kontroll-Lookup
  welche Ressource zur Datei gehört)
- TipTap-Link-Tool javascript:-Protokoll blockieren
- Prisma-Error-Messages in Admin-Endpoints generisch sanitisieren

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 00:21:37 +02:00
duffyduck dea2da0271 Vertragsstatus-Trigger: Datum beim Upload miterfassen
Beim automatischen Status-Wechsel wird jetzt auch das passende Datum gesetzt,
damit Status und Datumsfeld konsistent sind (Cockpit-Warnung "Datum fehlt"
verschwindet sofort nach Upload).

Backend:
- Upload-Handler für Kündigungsbestätigung(s-Optionen) nimmt optional
  `confirmationDate` aus multipart an, speichert als
  cancellationConfirmationDate / cancellationConfirmationOptionsDate.
  Fallback: heute (nur falls Feld noch leer war).
- maybeActivateOnDeliveryConfirmation nimmt optional deliveryDate, setzt
  Contract.startDate falls leer. Fallback: heute.

Frontend:
- ContractDetail: neues kleines Modal beim Kündigungsbestätigungs-Upload
  fragt das Bestätigungs-Datum ab (Default: heute oder bereits gesetzter
  Wert). Der bestehende inline-Datums-Editor bleibt für spätere Korrekturen.
- ContractDocumentsSection: Datums-Input erscheint conditional im
  Upload-Bereich, sobald Typ "Lieferbestätigung" gewählt ist.
- SaveAttachmentModal (E-Mail-Anhang → Vertragsdokument): gleicher
  Datums-Input conditional für "Lieferbestätigung".
- API-Methoden uploadCancellationConfirmation / uploadDocument /
  saveAttachmentAsContractDocument nehmen optional Datum entgegen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 13:40:04 +02:00
duffyduck 1c46d7345c Security-Hardening: IDOR-Fixes, XSS-Sanitizer, CORS+Helmet, Data-Exposure
Umfassender Security-Review vor öffentlichem Deployment.
Detaillierter Report in docs/SECURITY-REVIEW.md.

🔴 KRITISCHE FIXES:

1. CORS offen → jetzt nur explizite Origins (via CORS_ORIGINS env),
   in Production per default komplett aus (gleiche Origin erzwingt Browser).

2. Keine Security-Headers → helmet-Middleware hinzugefügt.
   X-Frame-Options, X-Content-Type-Options, HSTS, Referrer-Policy, CORP.

3. JWT-Fallback-Secret entfernt. Beim Server-Start wird jetzt geprüft ob
   JWT_SECRET (min 32 Zeichen) und ENCRYPTION_KEY (exakt 64 Hex) gesetzt sind,
   sonst Fail-Fast mit klarer Fehlermeldung.

4. IDOR bei 7 Contract-Endpoints. Portal-Kunden mit 'contracts:read'
   konnten über geratene IDs fremde Daten abrufen (Passwort, SIM-PIN/PUK,
   Internet-Zugangsdaten, SIP-Credentials, Vertragsdokumente, Rechnungen).
   Neuer Helper canAccessContract() in utils/accessControl.ts in allen
   betroffenen Endpoints eingebaut. Prüft Vertrag-Besitzer + Vollmachten.

5. XSS via Email-Body. email.htmlBody wurde ungefiltert via
   dangerouslySetInnerHTML gerendert. Angreifer konnte Mail mit <script>
   schicken → Token-Diebstahl aus localStorage. Jetzt mit DOMPurify
   sanitized: verbietet script/iframe/form/inline-handler, erlaubt
   normale Formatierung + Bilder.

6. Customer-API leakte sensible Felder:
   - portalPasswordHash (bcrypt-Hash)
   - portalPasswordEncrypted (symmetrisch, mit ENCRYPTION_KEY entschlüsselbar)
   - portalPasswordResetToken (gültig 2h)
   Neuer Sanitizer in utils/sanitize.ts, angewendet in getCustomer/getCustomers.
   Admin mit customers:update darf portalPasswordEncrypted sehen (für UI-Anzeige),
   alle anderen Rollen nicht.

🟡 WICHTIGE FIXES:

7. Portal-JWT-Invalidation nach Passwort-Reset. Neues Feld
   Customer.portalTokenInvalidatedAt, wird beim Reset auf now() gesetzt.
   Auth-Middleware prüft Portal-Sessions dagegen. Alte Sessions werden
   dadurch invalidiert.

8. express.json() mit 5 MB Size-Limit (statt Default 100 KB unklar).

Neue Files:
- backend/src/utils/accessControl.ts - IDOR-Schutz
- backend/src/utils/sanitize.ts - Response-Sanitizer
- docs/SECURITY-REVIEW.md - vollständiger Report + Deployment-Checkliste

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 22:06:16 +02:00
duffyduck cdde7b4ab7 Mandantenfähigkeit: Domain + Kunden-E-Mail-Label dynamisch pro Provider
Alle hardcoded Referenzen auf 'stressfrei-wechseln.de' und 'Stressfrei-Wechseln'
durch dynamische Werte aus der EmailProviderConfig ersetzt. Notwendig für
Multi-Mandanten-Betrieb, wenn das CRM an Dritte vermietet wird.

Schema:
- Neues Feld EmailProviderConfig.customerEmailLabel (String?)
- Wenn leer, wird Label aus Domain abgeleitet ('stressfrei-wechseln.de' → 'Stressfrei-Wechseln')

Backend:
- Neuer Endpoint GET /api/email-providers/public-settings liefert { domain, customerEmailLabel }
- Neue Service-Funktionen: getProviderPublicSettings(), deriveLabelFromDomain()
- create/updateProviderConfig erweitert um customerEmailLabel

Frontend:
- Neuer Hook useProviderSettings() mit Auto-Caching
- Neues Eingabefeld 'Bezeichnung für Kunden-E-Mails' im Provider-Modal
- Dynamische Domain-Suffix im Adress-Hinzufügen-Dialog (@<domain>)
- Tab-Label 'Stressfrei-Wechseln' im Kunden-Detail → dynamisch
- 'Stressfrei-Wechseln Adresse' in ContractForm → dynamisch
- '(Stressfrei-Wechseln)' Badge in ContractDetail → dynamisch
- 'Stressfrei-Wechseln E-Mail' im Generate-Modal → dynamisch
- Leere-Zustand-Meldungen in Tab und E-Mail-Client → dynamisch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 15:43:19 +02:00
duffyduck cf4370c905 Toast-Benachrichtigungen bei IMAP-Sync- und SMTP-Send-Fehlern
Bisher blieb ein fehlgeschlagener IMAP-Sync oder E-Mail-Versand still – der User
sah nur im Browser-Devtools, dass etwas schief lief. Jetzt erscheint eine rote
Toast-Benachrichtigung (8 Sekunden) mit der konkreten Fehlermeldung des Servers,
z.B. 'Sync fehlgeschlagen: IMAP-Authentifizierung fehlgeschlagen: NO [AUTHENTICATIONFAILED]'.

EmailClientTab (Synchronisieren-Button):
- toast.success bei erfolgreichem Sync
- toast.error bei Fehler + bei Backend-Response mit success=false

ComposeEmailModal (Senden):
- toast.success bei erfolgreichem Versand
- toast.error bei SMTP-Fehler mit Server-Response (zusätzlich zum Inline-Fehler)

Außerdem im imapService.testImapConnection:
- Roh-Error wird jetzt geloggt (code, response, responseStatus, authenticationFailed)
- ImapFlow-spezifische Felder werden in die Fehlermeldung übernommen, sodass
  z.B. '2 NO [AUTHENTICATIONFAILED] Authentication failed.' direkt sichtbar wird

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 15:16:04 +02:00
duffyduck 2879bd64d6 Email-Anhänge als Vertragsdokumente + Rechnungen für alle Vertragstypen
Der SaveAttachmentModal hat jetzt drei Modi (wenn E-Mail einem Vertrag zugeordnet ist):

1. Als Dokument – in feste Slots (Kündigungsschreiben etc.), unverändert
2. Als Vertragsdokument – NEU: flexible ContractDocument-Tabelle mit Typ-Dropdown
   (Auftragsformular, Lieferbestätigung, Vertragsunterlagen, Vollmacht,
   Widerrufsbelehrung, Preisblatt, Sonstiges) + optionalen Notizen
3. Als Rechnung – jetzt für ALLE Vertragstypen (vorher nur Strom/Gas)

Backend:
- Neuer Endpoint POST /api/emails/:id/attachments/:filename/save-as-contract-document
- saveAttachmentAsInvoice + saveEmailAsInvoice: ELECTRICITY/GAS-Einschränkung entfernt,
  nutzt jetzt addInvoiceByContract als Fallback für Nicht-Energie-Verträge

Frontend:
- cachedEmailApi.saveAttachmentAsContractDocument hinzugefügt
- SaveAttachmentModal: neuer Mode 'contractDocument' mit Typ+Notizen
- Mode-Toggle zeigt jetzt alle drei Optionen wenn Vertrag zugeordnet

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 13:06:10 +02:00
duffyduck 9d6bd68ddc Geburtstag-Management-Modal mit Reset + Send + Auto-Flag
Neuer Cake-Button neben dem Geburtsdatum in den Stammdaten öffnet ein Modal
mit drei Funktionen:

1. **Gruß-Marker zurücksetzen** (lastBirthdayGreetingYear → null)
   - Für Debugging oder als Fallback, wenn der Kunde den Gruß erneut sehen soll
   - Mit Bestätigungsdialog

2. **Geburtstagsgruß jetzt senden** (Email / WhatsApp / Telegram / Signal)
   - Email: direkt via System-SMTP mit HTML-Template (Du/Sie-abhängig)
   - WhatsApp/Telegram/Signal: öffnet vorbefülltes Fenster mit Gruß-Text
   - Text beachtet Du/Sie-Verhältnis (pronomen, possessiv, etc.)
   - Mit Bestätigungsdialog

3. **Automatisch senden** – neue Einstellung am Customer
   - autoBirthdayGreeting (Boolean) + autoBirthdayChannel (String)
   - Für späteren Cron-basierten Automatik-Versand vorbereitet

Backend:
- birthday.service.ts: resetBirthdayGreeting, buildBirthdayGreetingText, getBirthdayGreetingData
- birthday.controller.ts: resetBirthdayGreeting, sendBirthdayGreeting
- Routes: POST /birthdays/:customerId/reset + /send
- Audit-Log bei beiden Aktionen

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 12:46:03 +02:00
duffyduck 2a3928d0e7 Anrede-Verhältnis Du/Sie pro Kunde + Geburtstagsgruß respektiert Anrede
Schema:
- Customer.useInformalAddress: Boolean (Default: false = Sie)
- Auch bei Firmenkunden verfügbar (Chef kann man auch duzen)

Frontend:
- Neues Pflichtfeld "Anrede per" (Du/Sie) im Kunden-Formular
- Anzeige als Badge in CustomerDetail-Stammdaten

Geburtstagsgruß im Portal:
- Bei Du: "Herzlichen Glückwunsch, Max! Alles Gute zu deinem 42. Geburtstag!"
- Bei Sie: "Herzlichen Glückwunsch, Herr Müller! Alles Gute zu Ihrem 42. Geburtstag!"
- Konsistent auch bei nachträglichen Glückwünschen (hattest/hatten, bist/sind etc.)
- Backend liefert firstName, lastName, salutation und useInformalAddress

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 12:27:23 +02:00
duffyduck 018784cca6 Geburtstagskalender + Geburtstagsgruß-Modal im Kundenportal
Admin (Vertrags-Cockpit):
- Neue Section "Geburtstage" zeigt Kunden mit Geburtstag
- Fenster: -7 bis +30 Tage um heute
- Farbcodierung: heute (pink), vergangen (amber), bevorstehend (grau)
- Anzeige: Name, Kundennummer, Geburtsdatum, Alter, "Heute!" / "In X Tagen" / "Vor X Tagen"

Portal (Kundenportal):
- Modal mit Geburtstagsgruß wenn Geburtstag heute oder in den letzten 7 Tagen war
- Unterscheidet zwischen aktuellem Geburtstag und nachträglichen Glückwünschen
- Schönes Gradient-Design mit Konfetti-Emojis
- Wird pro Jahr nur einmal angezeigt (Customer.lastBirthdayGreetingYear)
- Bestätigung speichert das aktuelle Jahr

Backend:
- Neues Feld Customer.lastBirthdayGreetingYear (Int?)
- Service birthday.service.ts mit Fenster-Logik + Alter-Berechnung
- Endpoints /api/birthdays/upcoming (Admin),
  /api/birthdays/my-birthday (Portal GET + POST /acknowledge)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 11:51:20 +02:00
duffyduck 0e75e6c8e5 added place to telecommunication, added contract documents, added invoice to other contracts 2026-03-25 16:55:48 +01:00
duffyduck a15772cb54 impressum datenschutz added 2026-03-25 15:25:34 +01:00
duffyduck fd55742c57 complete new audit system 2026-03-21 18:23:54 +01:00
duffyduck c3edb8ad2e gdpr audit implemented, email log, vollmachten, pdf delete cancel data privacy and vollmachten, removed message no id card in engergy car, and other contracts that are not telecom contracts, added insert counter for engery 2026-03-21 11:59:53 +01:00
duffyduck e0fc26795e added contract history 2026-02-08 19:24:37 +01:00
duffyduck 4f588015a4 contractnumber provider added, old provider number field only if no previous contact exist 2026-02-08 14:34:56 +01:00
duffyduck 2ab2bb7562 snooze vor expired, contracts, display snoozed contracts if an item is missing, un snooze implemented, fixed invoice upload bug 2026-02-08 13:08:58 +01:00
duffyduck ad2b8ea5b6 added invoices and status in cockpit, created info button for contract status types 2026-02-08 01:18:12 +01:00
duffyduck 80dd5cc157 contractmodaldetail date format and before contract and next contract question to add 2026-02-04 21:17:13 +01:00
duffyduck f33d157b9b save email as pdf likae attachment version 2 2026-02-04 19:49:09 +01:00
duffyduck 8c65fecef0 remove uploads from repo but keep empty folder 2026-02-04 19:19:18 +01:00
duffyduck d8ced5cb24 added new view in contracts customer and contracts 2026-02-04 00:52:04 +01:00
duffyduck b87053760e save attachment from email in customer data and - or contracts 2026-02-03 23:58:00 +01:00
duffyduck bf068276b5 all email views the same 2026-02-03 23:04:42 +01:00
duffyduck 8c9e61cf17 added backup and email client 2026-02-01 00:02:35 +01:00
Stefan Hacker e209e9bbca first commit 2026-01-29 01:16:54 +01:00