From bfc89917b101599e8047454d06be42176553733e Mon Sep 17 00:00:00 2001 From: Eduard Wisch Date: Sat, 16 May 2026 18:13:25 +0200 Subject: [PATCH] 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) --- CHANGELOG.md | 25 +++++++++++++-- core/modules/modMahnung.class.php | 49 +++++++++++++++++++++++++++++ sql/llx_mahnung_mahnung.sql | 2 +- sql/llx_mahnung_stufe.sql | 2 +- sql/llx_mahnung_trackingpattern.sql | 2 +- 5 files changed, 74 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4070f7e..ca1f499 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [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. @@ -26,10 +30,11 @@ ### 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` 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. +- 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. @@ -56,7 +61,21 @@ - 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 diff --git a/core/modules/modMahnung.class.php b/core/modules/modMahnung.class.php index b16594f..13d3ba7 100644 --- a/core/modules/modMahnung.class.php +++ b/core/modules/modMahnung.class.php @@ -334,6 +334,9 @@ class modMahnung extends DolibarrModules // Migration: Versand-Felder ergänzen, falls Tabelle aus alter Version stammt $this->migrateVersandFelder(); + // Migration: tms-Spalten auf "ON UPDATE CURRENT_TIMESTAMP" umstellen + $this->migrateTimestampSpalten(); + // Default-Tracking-Patterns seeden (idempotent — nur beim ersten Mal) require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnungtrackingpattern.class.php'; MahnungTrackingPattern::seedDefaults($this->db); @@ -395,4 +398,50 @@ class modMahnung extends DolibarrModules $db->query("ALTER TABLE ".MAIN_DB_PREFIX."mahnung_mahnung ".implode(', ', $alter)); } } + + /** + * Stellt die tms-Spalten der Modul-Tabellen auf das Dolibarr-Standardverhalten + * "DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" um. Ältere Installs + * hatten tms als reines "TIMESTAMP", was unter explicit_defaults_for_timestamp + * als "NULL DEFAULT NULL" angelegt wurde — tms blieb dadurch bei jedem UPDATE leer. + * + * Idempotent: Tabellen, deren tms bereits ON UPDATE trägt, werden übersprungen. + * Bestehende NULL-Werte werden vor der NOT-NULL-Umstellung aus datec befüllt. + * + * @return void + */ + public function migrateTimestampSpalten() + { + global $db; + + $tables = array('mahnung_mahnung', 'mahnung_stufe', 'mahnung_trackingpattern'); + foreach ($tables as $table) { + $full = MAIN_DB_PREFIX.$table; + + // Aktuelle tms-Definition prüfen — Tabelle/Spalte fehlt -> überspringen + $res = $db->query("SHOW COLUMNS FROM ".$full." LIKE 'tms'"); + if (!$res || $db->num_rows($res) == 0) { + if ($res) { + $db->free($res); + } + continue; + } + $col = $db->fetch_object($res); + $db->free($res); + + // Bereits migriert (Extra enthält "on update ...") -> nichts zu tun + if (stripos((string) $col->Extra, 'on update') !== false) { + continue; + } + + // Alt-Zeilen ohne tms aus dem Erstelldatum befüllen, damit die + // anschließende NOT-NULL-Umstellung keine 0000-Werte erzeugt. + $db->query("UPDATE ".$full." SET tms = COALESCE(datec, NOW()) WHERE tms IS NULL"); + + $db->query( + "ALTER TABLE ".$full." MODIFY COLUMN tms TIMESTAMP NOT NULL" + ." DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" + ); + } + } } diff --git a/sql/llx_mahnung_mahnung.sql b/sql/llx_mahnung_mahnung.sql index 037003d..8a6cc45 100644 --- a/sql/llx_mahnung_mahnung.sql +++ b/sql/llx_mahnung_mahnung.sql @@ -33,7 +33,7 @@ CREATE TABLE llx_mahnung_mahnung ( tracking_nr VARCHAR(50), tracking_provider VARCHAR(20), datec DATETIME, - tms TIMESTAMP, + tms TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, fk_user_creat INTEGER, fk_user_modif INTEGER ) ENGINE=InnoDB; diff --git a/sql/llx_mahnung_stufe.sql b/sql/llx_mahnung_stufe.sql index f4c5532..afada36 100644 --- a/sql/llx_mahnung_stufe.sql +++ b/sql/llx_mahnung_stufe.sql @@ -25,7 +25,7 @@ CREATE TABLE llx_mahnung_stufe ( pdf_intro TEXT, active TINYINT DEFAULT 1 NOT NULL, datec DATETIME, - tms TIMESTAMP + tms TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB; -- Default-Stufen (idempotent: INSERT IGNORE wegen UNIQUE entity+stufe) diff --git a/sql/llx_mahnung_trackingpattern.sql b/sql/llx_mahnung_trackingpattern.sql index 979ed2d..8accfc9 100644 --- a/sql/llx_mahnung_trackingpattern.sql +++ b/sql/llx_mahnung_trackingpattern.sql @@ -17,5 +17,5 @@ CREATE TABLE llx_mahnung_trackingpattern ( priority INTEGER DEFAULT 100, active TINYINT DEFAULT 1, datec DATETIME, - tms TIMESTAMP + tms TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB;