# Changelog ## [Unreleased] ### Schema-Fix (tms-Spalten) - Die `tms`-Spalten von `llx_mahnung_mahnung`, `llx_mahnung_stufe` und `llx_mahnung_trackingpattern` wurden als reines `TIMESTAMP` angelegt — unter `explicit_defaults_for_timestamp` entstand daraus `NULL DEFAULT NULL`, sodass `tms` bei jedem UPDATE leer blieb. Jetzt Dolibarr-Standard `DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP`, damit der Änderungszeitpunkt (z.B. einer per Zahlungstrigger erledigten Mahnung) wieder nachvollziehbar ist. - Neue idempotente Migration `migrateTimestampSpalten()` (läuft im Modul-`init()`): befüllt bestehende `NULL`-Werte aus `datec` und stellt die Spalte per `ALTER TABLE` um. Greift auf Bestands-Installs beim Re-Aktivieren des Moduls. ### 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. - **OCR-Fallback**: wenn `pdftotext` nur Form-Feed-Zeichen liefert (Bild-PDF), wird automatisch `ocrmypdf --skip-text -l deu+eng` aufgerufen und das OCR-PDF erneut mit `pdftotext` gelesen. Temporaeres OCR-PDF wird nach Extraktion geloescht. - 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` gespeichert; **zusaetzlich werden `date_versand` und `versandweg` automatisch gesetzt** (falls noch leer), sodass kein separates Speichern noetig ist. - "Verwerfen" entfernt den Vorschlag aus der Session. - DPAG-Einschreiben-Regex erlaubt OCR-typische Leerzeichen zwischen Zifferngruppen (`R[A-Z]\s?\d{4}\s?\d{4}\s?\d\s?DE`). ### 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//`, 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. ### Verzugszinsen-Neuberechnung - Mahnungen im Status "Erstellt" (noch nicht versandt) werden beim Aufruf der card.php **automatisch mit der aktuellen Stufen-Konfiguration neu berechnet**. Aenderungen am Zinssatz-Override in den Einstellungen wirken sofort auf alle offenen Mahnungen. - Toleranz 0,001 EUR um unnoetige DB-Writes zu vermeiden. - `basiszins_snapshot` wird ebenfalls aktualisiert. ### Zinssatz-Override UX (Setup-Seite) - Zinssatz-Override-Felder zeigen jetzt einen **Placeholder** mit dem Standard-Zinssatz (z.B. "6,27"). - **Grauer Hilfetext** neben dem Feld: "Leer = Standard (1,27 + 5,0 % = 6,27 %), 0 = keine Zinsen" — macht die Unterscheidung zwischen leer (Standard) und 0 (keine Zinsen) klar. - Effektiver Zinssatz wird live aus den globalen Basiszins-/Aufschlag-Einstellungen berechnet. ### Fixes - Deutsche Post Tracking-URL korrigiert: `?piececode={nr}` statt `?form.sendungsnummer={nr}`. - `showdocuments()` Return-Wert wurde nicht geprinted — Sendebelege waren nach Upload unsichtbar. - `$upload_dir` war im `scan_belege`-Handler undefiniert (Variable wurde erst 90 Zeilen spaeter gesetzt) — eigene Pfadberechnung im Action-Block. - `pdftotext` Form-Feed-Zeichen (`\x0C`) bei Bild-PDFs: `trim()` entfernt `\x0C` nicht — explizite Zeichenliste noetig. - 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 1–10) ### 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`