All checks were successful
Deploy mahnung / deploy (push) Successful in 13s
Neue Spalte "Vsl. Zahlung" in box_mahnung_offen: je offener Rechnung Prognose (Faelligkeit + round(Oe-Verzug)) aus dem bisherigen Zahlungs- verhalten des Kunden, mit Ampel-Icon + Bewertungsstufe + Tooltip. - Skala 1:1 aus BuchhaltungsWidget uebernommen (KB #886), damit die Ampel zur "Zahlungsverhalten"-Box auf der Kundenkarte passt: 5 Stufen Vorbildlich/Puenktlich/Spaetzahler/Verspaetet/Problematisch anhand Oe-Tage nach Faelligkeit, gleiche Schwellen + Farben. - Berechnung als self-contained Kopie (getZahlprognose, 1 Query je Kunde/Request gecacht) - keine Laufzeit-Abhaengigkeit auf das BuchhaltungsWidget. - Prognose ueberschritten + Rechnung noch offen -> "(ueberfaellig)" als zusaetzliches Mahnsignal. - Mindest-Stichprobe MAHNUNG_PROGNOSE_MIN_N (Default 3), sonst "keine Historie". Neue Lang-Keys MahnungProg* (de_DE + en_US). - $langs->transnoentities() statt trans() genutzt (sonst leere %s durch doppeltes sprintf bzw. doppeltes &-Encoding, siehe KB #897). End-to-end via CLI-Harness gegen die lokale Test-DB verifiziert (alle 5 Stufen, Overdue-Zweig, Empty-State, php -l sauber). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.8 KiB
6.8 KiB
CLAUDE.md — Mahnung-Modul
Projekt
Dolibarr Custom-Modul: 3-stufiges Mahnwesen nach BGB §288 + Versand-Tracking + Forderungsausfall-Workflow.
Technisches
- numero: 500038 (NICHT ändern — 500037 ist Eplan)
- Deploy: nur via Pipeline (
[deploy]in Commit-Message), NIEMALS manuell kopieren - Prod-Pfad: /mnt/appdata/firma/dolibarr-202509/modules/mahnung/
- Lokal: Symlink /var/www/dolibarr/custom/mahnung → repo/
- Test-DB: dolibarr_test auf 192.168.155.11
- Forgejo-Repo: data/mahnung (NICHT data-it/ — historisch, soll bleiben)
Schema-Migration
modMahnung::migrateVersandFelder()läuft automatisch beim Setup-Page-Aufruf- Idempotent via
SHOW COLUMNS LIKE→ fehlende Spalten viaALTER TABLE ADD COLUMN - Default-Tracking-Patterns werden via
MahnungTrackingPattern::seedDefaults()geseedet (Check:COUNT(*) > 0→ skip) - Nach Deploy: User muss Setup-Page einmal aufrufen, sonst fehlen die neuen Spalten
Dokumentenmodell-System
commonGenerateDocument()fügt automatischdoc_/pdf_Prefix hinzu- DB-Einträge in
llx_document_model.nomOHNE Prefix speichern actions_setmoduleoptions.inc.phpMUSS vorllxHeader()stehen (Upload)- ODT-Templates: mahnung_stufe1.odt, mahnung_stufe2.odt, mahnung_stufe3.odt, mahnung.odt (Fallback)
Widget
box_mahnung_offenbasiert 1:1 aufbox_factures_imp.php(Standard-Widget)- Zeigt ALLE offenen Rechnungen, nicht nur überfällige
- Mahnstufe-Badge nur wenn Mahnung existiert, sonst Strich
- Zähler im Kopf: Der Badge im Widget-Kopf zeigt die tatsächliche Gesamtzahl offener Rechnungen (verlinkt auf die Rechnungsliste). Titel-Lang-Key hat kein
(%s)mehr — die Zahl steckt im Badge. Der Zähler wird erst NACH der Query gesetzt (wenn$numbekannt ist), damit die frühen Return-Pfade (fehlende Rechte / SQL-Fehler) ohne Zähler bleiben. - Zeilenanzahl konfigurierbar via Konstante
MAHNUNG_BOX_MAXLINES(Admin-Select insetup.php: Alle/5/10/20/30/50, Default0= alle). Widget lädt IMMER alle offenen Rechnungen (für den korrekten Zähler), rendert aber nurMAHNUNG_BOX_MAXLINESZeilen + eine...-Überlaufzeile. Nicht auf das von Dolibarr übergebene$maxverlassen — das kommt ausMAIN_SIZE_SHORTLIST_LIMIT(Default 5) und gilt global für ALLE Home-Boxen. (KB #598) - Empty-State Pflicht: bei
$num == 0Platzhalter-Zeile ininfo_box_contentseinfügen — sonst rendertModeleBoxes::showBox()gar nichts und das Widget verschwindet komplett (auch nach neuen Rechnungen sieht der User es nicht zurückkommen). Siehe KB #682. - Summenzeile Netto+Brutto: Betragszelle der
liste_total-Zeile zeigt zweizeilig Netto (SUM(f.total_ht)) und Brutto (SUM(f.total_ttc)). Brutto kommt direkt ausf.total_ttcder Rechnung, NICHT aus Netto × Steuersatz hochgerechnet — sonst wären Reverse-Charge §13b, Steuerbefreiung und Kleinunternehmer §19 UStG falsch. Lang-KeysMahnungBoxNetto/MahnungBoxBrutto. - Spalte „Vsl. Zahlung" (Zahlungsprognose):
getZahlprognose()/buildPrognoseCell()/prognoseRating(). Skala + Berechnung sind eine self-contained Kopie aus BuchhaltungsWidget (getPaymentStatistics()), Referenz KB #886 — bei Skala-Änderungen BEIDE Module synchron halten (Schwellen ≤−5/≤0/≤7/≤14, Farben#28a745/#ffc107/#fd7e14/#dc3545, Filtertype IN (0,1,5)+fk_statut=2+paye=1+date_lim_reglement IS NOT NULL).diff = Ø(Zahlung−Rechnungsdatum) − Ø(Fälligkeit−Rechnungsdatum); Prognosedatum =Fälligkeit + round(diff); Mindest-nviaMAHNUNG_PROGNOSE_MIN_N(Default 3), darunter „keine Historie". Die Kundenkarten-Statistik selbst liefert BuchhaltungsWidget (HooktabContentViewThirdparty) — Mahnung baut dort KEINEN zweiten Block.$langs->transnoentities(...)verwenden (nichttrans()+sprintf→ leere%s; nichttrans()+dol_escape_htmltag→ doppeltes&-Encoding).
Hooks-Stolperfallen
completeTabsHeadwird bei jedem Aufruf voncomplete_head_from_modules()getriggert — pro Karte mehrfach (core + external + remove). Filter aufmode=add+filterorigmodule=external, sonst doppelter Tab. (KB #601)- Hook-Kontexte:
invoicecard,thirdpartycard,ordercard— letztere für Bonitäts-Warnings.
Filter-Syntax-Stolperfallen
$form->select_company($selected, $htmlname, $filter, ...): der$filter-Parameter erwartet USC-Syntax(feld:operator:wert), NICHT plain SQL. Beispiel B2C:(s.tva_intra:is:NULL) OR (s.tva_intra:=:''). Sonst SQL-Syntax-Error + 500. (KB #602)search_socid=-1wird vonselect_companyals "nichts ausgewählt" geliefert → im Filter-Check> 0statt!empty()nutzen.
Pipeline-Stolperfallen
${{ github.event.head_commit.message }}NIE direkt inrun:-Skript interpolieren — bei Sonderzeichen (Klammern, Backticks) bricht Bash. Immer viaenv:durchreichen. (KB #603)[deploy]-Tag im Commit nötig, sonst kein Auto-Deploy.
Verzugszinsen-Override
zinssatz_b2c_uebersteuern/zinssatz_b2b_uebersteuerninllx_mahnung_stufe: NULL = Standard (Basiszins + Aufschlag), 0 = keine Zinsen, Wert = fester Prozentsatz- Nicht-versandte Mahnungen (Status ≤ ERSTELLT) werden beim card.php-Aufruf automatisch neu berechnet
- Setup-Seite zeigt Placeholder mit Standard-Zinssatz + Hilfetext
Versand & Bonität (Phase 6)
- Versand-Felder:
date_versand,versandweg,tracking_nr,tracking_provideranllx_mahnung_mahnung - Tracking-URLs aus DB (
llx_mahnung_trackingpattern) viaMahnungTrackingPattern::urlFor(), Fallback:Mahnung::trackingUrl()(hardcoded) - Beleg-Upload:
formfile->showdocuments('mahnung', $ref, $filedir, ...)—$conf->mahnung->dir_outputwird von Dolibarr automatisch gesetzt (KB #605), kein Custom-Setup nötig - Beleg-Scan:
pdftotext+ocrmypdf(OCR-Fallback für Bild-PDFs) im90-Dolibarr-Prod-Custom-Container; Pattern-Match viaMahnungTrackingPattern::detectFromText() pdftotextgibt\x0C(Form-Feed) bei Bild-PDFs zurück —trim()mit expliziter Zeichenliste" \t\n\r\0\x0B\x0C"nötig- "Übernehmen" setzt
tracking_nr+tracking_provider+date_versand+versandwegautomatisch (kein extra Speichern) - Uneinbringlich-Klassifikation:
Facture::setCanceled($user, CommonInvoice::CLOSECODE_BADDEBT, $note)→ setztfk_statut=3+close_code='badcustomer'(KB #606) - Steuer-Modul kompatibel: EÜR ignoriert (liest nur
llx_paiement), UStVA filtertfk_statut IN (1,2)automatisch (KB #607)
Dolibarr-Versionshinweise
f.fk_statutstattf.statut(seit Dolibarr 22.x)verifCsrf()existiert nicht — CSRF vianewToken()+ GETPOST('token')dol_mkdir()gibt 0 zurück wenn Verzeichnis bereits existiert (nicht false)dol_dir_list()gibtfullnamezurück (nichtfullpath)$form->formconfirm()unterstützt textarea-Feld via$formquestion-Array (KB #609)