fix(mahnung): tms-Spalten auf ON UPDATE CURRENT_TIMESTAMP umstellen [deploy]
All checks were successful
Deploy mahnung / deploy (push) Successful in 14s

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>
This commit is contained in:
Eduard Wisch 2026-05-16 18:13:25 +02:00
parent d49e178554
commit bfc89917b1
5 changed files with 74 additions and 6 deletions

View file

@ -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

View file

@ -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"
);
}
}
}

View file

@ -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;

View file

@ -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)

View file

@ -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;