mahnung/CLAUDE.md
Eduard Wisch adfeafa09b
All checks were successful
Deploy mahnung / deploy (push) Successful in 13s
Widget: Zahlungsprognose-Spalte (Skala aus BuchhaltungsWidget/KB #886) [deploy]
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>
2026-07-19 11:30:09 +02:00

6.8 KiB
Raw Blame History

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 via ALTER 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 automatisch doc_/pdf_ Prefix hinzu
  • DB-Einträge in llx_document_model.nom OHNE Prefix speichern
  • actions_setmoduleoptions.inc.php MUSS vor llxHeader() stehen (Upload)
  • ODT-Templates: mahnung_stufe1.odt, mahnung_stufe2.odt, mahnung_stufe3.odt, mahnung.odt (Fallback)

Widget

  • box_mahnung_offen basiert 1:1 auf box_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 $num bekannt ist), damit die frühen Return-Pfade (fehlende Rechte / SQL-Fehler) ohne Zähler bleiben.
  • Zeilenanzahl konfigurierbar via Konstante MAHNUNG_BOX_MAXLINES (Admin-Select in setup.php: Alle/5/10/20/30/50, Default 0 = alle). Widget lädt IMMER alle offenen Rechnungen (für den korrekten Zähler), rendert aber nur MAHNUNG_BOX_MAXLINES Zeilen + eine ...-Überlaufzeile. Nicht auf das von Dolibarr übergebene $max verlassen — das kommt aus MAIN_SIZE_SHORTLIST_LIMIT (Default 5) und gilt global für ALLE Home-Boxen. (KB #598)
  • Empty-State Pflicht: bei $num == 0 Platzhalter-Zeile in info_box_contents einfügen — sonst rendert ModeleBoxes::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 aus f.total_ttc der Rechnung, NICHT aus Netto × Steuersatz hochgerechnet — sonst wären Reverse-Charge §13b, Steuerbefreiung und Kleinunternehmer §19 UStG falsch. Lang-Keys MahnungBoxNetto/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, Filter type IN (0,1,5) + fk_statut=2+paye=1+date_lim_reglement IS NOT NULL). diff = Ø(ZahlungRechnungsdatum) Ø(FälligkeitRechnungsdatum); Prognosedatum = Fälligkeit + round(diff); Mindest-n via MAHNUNG_PROGNOSE_MIN_N (Default 3), darunter „keine Historie". Die Kundenkarten-Statistik selbst liefert BuchhaltungsWidget (Hook tabContentViewThirdparty) — Mahnung baut dort KEINEN zweiten Block. $langs->transnoentities(...) verwenden (nicht trans()+sprintf → leere %s; nicht trans()+dol_escape_htmltag → doppeltes &-Encoding).

Hooks-Stolperfallen

  • completeTabsHead wird bei jedem Aufruf von complete_head_from_modules() getriggert — pro Karte mehrfach (core + external + remove). Filter auf mode=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=-1 wird von select_company als "nichts ausgewählt" geliefert → im Filter-Check > 0 statt !empty() nutzen.

Pipeline-Stolperfallen

  • ${{ github.event.head_commit.message }} NIE direkt in run:-Skript interpolieren — bei Sonderzeichen (Klammern, Backticks) bricht Bash. Immer via env: durchreichen. (KB #603)
  • [deploy]-Tag im Commit nötig, sonst kein Auto-Deploy.

Verzugszinsen-Override

  • zinssatz_b2c_uebersteuern / zinssatz_b2b_uebersteuern in llx_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_provider an llx_mahnung_mahnung
  • Tracking-URLs aus DB (llx_mahnung_trackingpattern) via MahnungTrackingPattern::urlFor(), Fallback: Mahnung::trackingUrl() (hardcoded)
  • Beleg-Upload: formfile->showdocuments('mahnung', $ref, $filedir, ...)$conf->mahnung->dir_output wird von Dolibarr automatisch gesetzt (KB #605), kein Custom-Setup nötig
  • Beleg-Scan: pdftotext + ocrmypdf (OCR-Fallback für Bild-PDFs) im 90-Dolibarr-Prod-Custom-Container; Pattern-Match via MahnungTrackingPattern::detectFromText()
  • pdftotext gibt \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 + versandweg automatisch (kein extra Speichern)
  • Uneinbringlich-Klassifikation: Facture::setCanceled($user, CommonInvoice::CLOSECODE_BADDEBT, $note) → setzt fk_statut=3 + close_code='badcustomer' (KB #606)
  • Steuer-Modul kompatibel: EÜR ignoriert (liest nur llx_paiement), UStVA filtert fk_statut IN (1,2) automatisch (KB #607)

Dolibarr-Versionshinweise

  • f.fk_statut statt f.statut (seit Dolibarr 22.x)
  • verifCsrf() existiert nicht — CSRF via newToken() + GETPOST('token')
  • dol_mkdir() gibt 0 zurück wenn Verzeichnis bereits existiert (nicht false)
  • dol_dir_list() gibt fullname zurück (nicht fullpath)
  • $form->formconfirm() unterstützt textarea-Feld via $formquestion-Array (KB #609)