mahnung/CHANGELOG.md
Eduard Wisch 3a49c67fbd
All checks were successful
Deploy mahnung / deploy (push) Successful in 13s
Bonitaets-Workflow: Warnbox Kundenkarte + Uneinbringlich-Button Stufe 3 [deploy]
Bonitaets-Anzeige:
- Hook tabContentViewThirdparty rendert prominente rote Warnbox auf der
  Kundenkarte wenn fk_statut=3 + close_code=badcustomer existiert. Zeigt
  Anzahl, Gesamtsumme, Datum letzter Abschreibung + Link zur Detail-Liste.
- Hook formObjectOptions zeigt kompakte Warn-Zeile auf ordercard und
  invoicecard wenn der Kunde Forderungsausfaelle hat.
- ordercard zum module_parts.hooks.data ergaenzt.

Uneinbringlich-Button:
- Auf Mahnung-Karten der Stufe 3, Status >= ERSTELLT, nicht storniert,
  Rechnung noch nicht abandoned.
- Bestaetigungs-Dialog mit Begruendungs-Textfeld (Default-Text setzt
  das aktuelle Datum ein).
- Ruft Facture::setCanceled mit CommonInvoice::CLOSECODE_BADDEBT.
- Mahnung wird storniert + Begruendung in note_private festgehalten.

Steuer-Modul kompatibel: EÜR liest nur llx_paiement (keine Zahlung =
keine Einnahme), UStVA filtert fk_statut IN (1,2) — abandoned Rechnungen
werden automatisch korrekt ausgeschlossen. Bei Ist-Versteuerung damit
buchhalterisch sauber, kein manueller Eingriff noetig.

Lang-Keys: 16 neu (de_DE + en_US) fuer Bonitaets-Box + Uneinbringlich-Workflow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:17:44 +02:00

151 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Changelog
## [Unreleased]
### Bonitaet / Forderungsausfall-Workflow
- Neuer Hook `tabContentViewThirdparty` rendert auf der Kundenkarte eine **prominente rote Warnbox**, wenn der Kunde abgeschriebene Rechnungen (`fk_statut=3` + `close_code='badcustomer'`) hat. Zeigt Anzahl, Gesamtsumme, Datum der letzten Abschreibung + Link zur Detail-Liste.
- Neuer Hook `formObjectOptions` zeigt eine kompakte Warn-Zeile bei Auftrags-/Rechnungs-Karten ("ordercard", "invoicecard"), wenn der Kunde Forderungsausfaelle hat — Bonitaets-Pruefung vor neuem Geschaeft.
- Neuer Hook-Kontext `ordercard` zum Modul-Descriptor ergaenzt.
- Neuer Button **"Als uneinbringlich klassifizieren"** auf Mahnung-Karten der Stufe 3 (Status ≥ ERSTELLT, nicht storniert, Rechnung nicht bereits abandoned).
- Bestaetigungs-Dialog mit Begruendungs-Textfeld (Default: "Mahnverfahren erfolglos abgeschlossen am ...")
- Ruft `Facture::setCanceled($user, CommonInvoice::CLOSECODE_BADDEBT, $note)` → Rechnung auf `fk_statut=3` + `close_code='badcustomer'`
- Mahnung wird zugleich storniert und die Begruendung in `note_private` festgehalten
- Steuerlich passt das: Dolibarrs Steuer-Modul (EÜR) ignoriert abandoned Rechnungen automatisch (UStVA filtert `fk_statut IN (1,2)`; EÜR liest nur `llx_paiement` = tatsaechliche Zahlungen). Bei Ist-Versteuerung ist damit alles korrekt — keine separate Buchung noetig.
### UX-Fixes (Vorschlagsliste)
- Kundentyp-Filter (B2B/B2C) wird jetzt direkt an `select_company()` durchgereicht — wenn B2C gewaehlt ist, zeigt das Kunden-Dropdown nur noch Drittparteien ohne TVA-Nummer (entsprechend umgekehrt fuer B2B). Filter nutzt Dolibarrs **Universal-Search-Criteria-Syntax** `(feld:operator:wert)` — plain SQL wuerde durch `forgeSQLFromUniversalSearchCriteria` fehlschlagen.
- Auto-Submit beim Wechsel des Kundentyps + automatisches Reset von `search_socid`, damit das Dropdown ohne extra "Suche"-Klick aktualisiert wird und keine ungueltige (im neuen Filter nicht enthaltene) Kunden-ID stehen bleibt.
- `search_socid=-1` (von `select_company` als "nichts ausgewaehlt" geliefert) wird jetzt korrekt ignoriert statt als Filter auf `fk_soc=-1` zu wirken.
- Skip-Grund-Spalte in der Uebersprungen-Tabelle: `opacitymedium` jetzt am inneren `span` statt am `td`, damit Theme-spezifisches Zellen-Border-Verhalten konsistent bleibt.
### Versand-Reminder (Cron + Ntfy)
- Neuer Cron-Job `MahnungCronVersandReminder` (taeglich): sucht Mahnungen mit Status `ERSTELLT` deren PDF seit > N Tagen erstellt aber noch nicht versendet wurde, schickt Ntfy-Push und (falls aktiv) GlobalNotify-Badge.
- Schwellenwert konfigurierbar via Konstante `MAHNUNG_VERSAND_REMINDER_DAYS` (Default 2).
- Nachricht listet bis zu 8 Mahnungen (Ref + Stufe + Alter in Tagen + Kunde); Rest als "+N weitere".
### Beleg-Scan mit Sendungsnummer-Erkennung
- Neuer Button "Belege scannen" im Versand-Block der Mahnungs-Karte.
- Beim Klick werden alle hochgeladenen Belege (PDF via `pdftotext`, sonst txt/html) durchsucht und gegen die konfigurierten Tracking-Patterns gematcht.
- Erkannte Sendungsnummern werden als Vorschlag ueber dem Beleg-Bereich angezeigt (mit Dateiname, Provider-Label, Sendungsnummer + Deep-Link).
- Per "Uebernehmen"-Button werden `tracking_nr` + `tracking_provider` in einem Klick gespeichert. "Verwerfen" entfernt den Vorschlag aus der Session.
- Fallback: wenn `pdftotext` im Container fehlt, wird das im UI klar gemeldet — txt/html werden trotzdem durchsucht.
- OCR (Tesseract fuer Bilder) bewusst noch nicht enthalten — kommt separat falls gewuenscht, ist Container-Aufwand.
### Konfigurierbare Tracking-Patterns (Setup-Seite)
- Neue Tabelle `llx_mahnung_trackingpattern` (Pro Eintrag: provider, label, regex, url_template, priority, active). Auto-Migration + Default-Seed beim Setup-Aufruf.
- Default-Patterns: DHL Paket (20-stellig), DPAG Einschreiben (`RR123456789DE`), UPS (1Z…), DHL 11-stellig, Hermes 14-stellig, DPD 14-stellig — Prioritaeten so gesetzt dass spezifischere Patterns zuerst greifen.
- Neue Setup-Seite `admin/tracking_patterns.php` mit CRUD: Pattern anlegen/bearbeiten/aktivieren-deaktivieren/loeschen.
- **Live-Vorschau**: Beim Tippen von Regex/URL/Beispieltext wird via AJAX-Endpoint `ajax/regex_preview.php` direkt gezeigt ob der Regex syntaktisch gueltig ist, was er aus dem Beispieltext matcht und wie die finale Tracking-URL aussieht.
- ReDoS-Schutz im AJAX-Endpoint: max 10 KB Sample, `pcre.backtrack_limit=100k`, Whitelist Delimiter `/ # ~`.
- `Mahnung::trackingUrl()` (hardcoded Fallback) bleibt — primaer wird `MahnungTrackingPattern::urlFor()` aus DB-Patterns benutzt.
- Setup-Page-Link: Button "Tracking-Muster (Regex)" oben rechts auf der Modul-Setup-Seite.
### Versand & Belege (Mahnungs-Karte)
- Neue Felder `date_versand`, `versandweg`, `tracking_nr`, `tracking_provider` an `llx_mahnung_mahnung` — idempotente Migration laeuft beim ersten Setup-Aufruf nach dem Deploy.
- Neuer Block "Versand & Belege" auf der Mahnungs-Karte:
- Erfassung Versanddatum + Versandweg (Brief/Einschreiben/DHL/DPD/Hermes/UPS/Fax/Mail/Persoenlich/Eigen).
- Optionale Sendungsnummer + Anbieter — Mahnung-Klasse liefert Deep-Link zur Sendungsverfolgung (DHL, Deutsche Post, DPD, Hermes, UPS).
- "Sendung verfolgen"-Button oeffnet die Provider-Seite mit eingesetzter Sendungsnummer.
- Beleg-Upload via Dolibarrs `formfile->showdocuments()` — Dateien landen in `DOL_DATA_ROOT/mahnung/<MAHN-Ref>/`, voll integriert mit ECM/document.php.
- Status springt automatisch auf `STATUS_VERSENDET` sobald ein Versanddatum gesetzt wird (sofern vorher <= ERSTELLT).
- Neue Methoden `Mahnung::setVersand()`, `Mahnung::trackingUrl()`, `Mahnung::defaultProviderForWeg()`, `Mahnung::getVersandwegLabel()`.
### Vorschlagsliste — UX
- Kunden-Filter: rowid-Input ersetzt durch Dolibarr-Standard `select_company()` (Ajax-Suche bzw. klassisches Dropdown, je nach Dolibarr-Konfiguration). Direkt-Links `?search_socid=74` bleiben funktional.
- Neuer Filter "Mindestbetrag" (in EUR, Komma erlaubt).
- Neuer Filter "Kundentyp" (B2B / B2C).
- Neue Spalte "Kontakt" mit Telefon- und Mail-Direktlink-Icons.
### Fixes
- Kundenkarte: Tab "Mahnwesen" erschien doppelt, weil `complete_head_from_modules()` pro Karte mehrfach (core + external + remove) feuert. Hook filtert jetzt auf `mode=add` + `filterorigmodule=external`.
## [0.2.0] — 2026-05-10 — ODT-Template-System, Widget, Dokumentenmodelle
### ODT-Template-System
- Abstrakte Basis-Klasse `ModelePDFMahnung extends CommonDocGenerator` (`core/modules/mahnung/modules_mahnung.php`)
- ODT-Generator `doc_generic_mahnung_odt` mit Stufen-spezifischer Template-Auswahl (mahnung_stufe1/2/3.odt, Fallback mahnung.odt)
- TCPDF-Generator `pdf_standard_mahnung` (refactored aus `mahnungpdf.class.php`)
- `mahnung.class.php`: `generateDocument()` Methode via `commonGenerateDocument()`
- Template-Variablen: Mahnung, Rechnung, Kunde, Bankverbindung, Dolibarr-Standard
- ODT-Template-Upload auf Setup-Seite mit Benennungskonvention-Hinweis
### Widget
- `box_mahnung_offen`: Offene Kundenrechnungen mit Mahnstufe-Badge (basiert auf box_factures_imp)
- Alle offenen Rechnungen (nicht nur überfällige), Status-Icon wie Original
- Farbige Mahnstufe-Badges (blau/orange/rot) mit Link zur Mahnung-Detailseite
- Strich (—) bei Rechnungen ohne Mahnung
### Dokumentenliste auf card.php
- Generierte Dokumente zur Mahnung auflisten (aus Rechnungsordner)
- PDF-Vorschau (Lupe), Download-Button
- Modellauswahl-Dropdown bei mehreren aktiven Dokumentenmodellen
### Setup-Erweiterungen
- Dokumentenmodell-Verwaltung (aktivieren/deaktivieren, Default setzen)
- `admin/templatevars.php`: Referenzseite aller verfügbaren ODT-Template-Variablen
- Link von Setup zur Variablen-Referenz
### Modul-Descriptor
- `module_parts['models'] = 1`
- Neue Konstanten: `MAHNUNG_ADDON_PDF`, `MAHNUNG_ADDON_PDF_ODT_PATH`
- Document-Model-Registrierung in `init()` (standard_mahnung + generic_mahnung_odt)
- Widget `box_mahnung_offen@mahnung` registriert
- Picto korrigiert: `fa-envelope-open-text` (FA5-Free)
### Bugfixes
- numero 500037 → 500038 (Kollision mit Eplan behoben)
- `verifCsrf()` entfernt (existiert nicht in Dolibarr, CSRF via `newToken()`)
- `f.statut``f.fk_statut` (Dolibarr 22.x Spaltenname)
- `actions_setmoduleoptions.inc.php` vor `llxHeader()` verschoben (ODT-Upload)
- Widget: `require_once mahnung.class.php` an Dateianfang (Fatal Error bei Klassen-Konstanten in SQL)
### Entfernt
- `class/mahnungpdf.class.php` — Logik in `core/modules/mahnung/doc/pdf_standard_mahnung.modules.php`
---
## [0.1.0] — 2026-05-07 — Erstveröffentlichung (Phase 110)
### DB-Schema (Phase 1)
- `llx_mahnung_mahnung` — Mahnvorgänge mit Stufe, Beträgen, Zinsen, Status, Snapshot des Basiszinses für Reproduzierbarkeit
- `llx_mahnung_stufe` — pro Stufe konfigurierbar: Frist, neue Frist, Gebühren B2C/B2B, optional Zinssatz-Override, Versandart, E-Mail-/PDF-Templates
- 3 Default-Stufen werden bei Aktivierung idempotent eingefügt
### Modul-Descriptor (Phase 1)
- numero `500038`, family `financial`, FA-Picto `fa-envelope-open-text`
- Modul-Konstanten: `MAHNUNG_BASISZINS`, `MAHNUNG_AUFSCHLAG_B2C`, `MAHNUNG_AUFSCHLAG_B2B`, `MAHNUNG_PAUSCHALE_B2B`, `MAHNUNG_NTFY_TOPIC`
- Rechte: `read`, `write`, `send`, `delete`, `setup`
- Cron-Job `MahnungCronBuildVorschlag` (täglich, default deaktiviert)
- Linkes Menü unter „Rechnungen" (mainmenu=billing) mit Vorschlagsliste / Archiv
### CRUD + Setup (Phase 2)
- `class/mahnung.class.php` — CRUD, Status-Konstanten, Verzugszinsen-Berechnung nach BGB §288
- `class/mahnungstufe.class.php` — Stufen-Konfiguration, Override-Helfer für Zinsen/Gebühren
- `admin/setup.php` — Stufen-Tabelle vollständig pflegbar, Konstanten persistent
### Vorschlagsliste + Cron (Phase 3)
- `class/mahnungvorschlag.class.php` — gemeinsamer Service: ermittelt pro überfälliger Rechnung die nächste vorgeschlagene Stufe, B2C/B2B-Erkennung via `tva_intra`, offener Betrag aus `paiement_facture`
- `class/mahnungcron.class.php` — Cron sammelt Vorschläge, sendet Ntfy-Push (Topic aus Setup), schreibt zusätzlich GlobalNotify-Action wenn aktiv
- `class/mahnungntfy.class.php` — schmaler Ntfy-Push-Wrapper
- `list.php` — Vorschlagsliste-UI mit Multi-Select, Filter nach Stufe / Verzugstagen / Kunde, Buttons „Mahnungen erzeugen" und „Sammelbrief"
### PDF-Generator + Erstellen (Phase 4)
- TCPDF-basierter Generator (DIN-5008 Form A): Adressfenster, Bezugszeichenzeile, Tabelle, Gebührenblock, Verzugszinsen mit Snapshot-Zinssatz, neue Frist, Bankverbindungs-Footer
- PDFs landen in `documents/facture/{ref}/` und erscheinen automatisch im Dokumente-Tab der Rechnung
- `ajax/createmahnung.php` — Bulk-Endpoint mit CSRF + Permission-Check, erzeugt Mahnung + PDF, behandelt §288 Abs. 5 Pauschale einmalig pro Rechnung
### Hooks + Trigger (Phase 5)
- `core/triggers/interface_99_modMahnung_MahnungTriggers.class.php``BILL_PAYED` und `PAYMENT_CUSTOMER_CREATE` setzen offene Mahnungen auf erledigt
- `class/actions_mahnung.class.php` — Hook auf Rechnungs- und Kundenkarte: Tab „Mahnungen (n)" mit Badge, Button „Mahnung erstellen" wenn überfällig
- `card.php` — Detailansicht eines Mahnvorgangs mit Storno-Aktion (`formconfirm`-Modal)
### E-Mail + Sammelbrief (Phase 6)
- `ajax/sendmail.php` — sendet Mahnung-PDF via `CMailFile` an die Kunden-Mail; Subject/Body mit Platzhaltern aus Stufen-Konfig
- `ajax/sammelbrief.php` — erzeugt Mahnungen für Auswahl, konkateniert ihre PDFs via TCPDI in eine Datei, liefert Download
### Integrationen (Phase 7 + 8)
- GlobalNotify: Cron sendet zusätzlich `actionRequired`-Notification ins Dolibarr-UI (wenn Modul aktiv)
- Tab „Mahnungen" auf Kundenkarte (`thirdpartycard`) zusätzlich zur Rechnungskarte
### Pipeline (Phase 10)
- `.forgejo/workflows/deploy.yml` — Deploy auf `/mnt/appdata/firma/dolibarr-202509/modules/mahnung` bei Push auf `main` mit `[deploy]` oder Tag `v*`, Ntfy-Notify auf Topic `vk-builds`