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

170 lines
14 KiB
Markdown
Raw Permalink 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]
### 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.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`