mahnung/CHANGELOG.md
Eduard Wisch bfc89917b1
All checks were successful
Deploy mahnung / deploy (push) Successful in 14s
fix(mahnung): tms-Spalten auf ON UPDATE CURRENT_TIMESTAMP umstellen [deploy]
tms war als reines TIMESTAMP angelegt -> unter explicit_defaults_for_timestamp
NULL DEFAULT NULL, blieb daher bei jedem UPDATE leer. Jetzt Dolibarr-Standard
DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP fuer mahnung/stufe/
trackingpattern. Neue idempotente Migration migrateTimestampSpalten() im init()
befuellt Alt-NULLs aus datec und stellt die Spalte per ALTER TABLE um.

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

14 KiB
Raw Blame History

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/<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.

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.statutf.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.phpBILL_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