mahnung/README.md
Eduard Wisch 9d345ff943
All checks were successful
Deploy mahnung / deploy (push) Has been skipped
Doku: Ablage am Beleg als Beilage (nur Reiter), Stand nachgezogen
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 08:58:45 +02:00

293 lines
18 KiB
Markdown
Raw 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.

# Modul Mahnung — Mahnwesen für Dolibarr
Mahnwesen-Modul für Dolibarr ERP: tägliche Vorschlagsliste überfälliger Rechnungen, frei konfigurierbare Mahnkette mit einer kostenlosen Zahlungserinnerung davor, Mahngebühren + tagesgenaue Verzugszinsen nach **BGB §288**, PDF- und ODT-Mahnschreiben, E-Mail-Versand über das Dolibarr-Mailformular, Sammelbrief, Trigger auf Zahlungseingang.
## Status
Version **0.3.1** (13.10.2026: Mahnschreiben im Rechnungsordner als Beilage indexiert) · davor **0.3.0** — Zahlungserinnerung als kostenlose Stufe 0, frei konfigurierbare Stufen, Mailversand über Dolibarrs `FormMail`, Setup-Seite als Akkordeon. Davor: UX-Vorschlagsliste, Versand-Erfassung, Sendebeleg-Upload, konfigurierbare Tracking-Patterns mit Live-Vorschau, Beleg-Scan via pdftotext, Versand-Reminder, Bonitäts-Workflow + Uneinbringlich-Klassifikation.
## Grundprinzip der Mahnkette
```
Fälligkeit überschritten
└─ Stufe 0 Zahlungserinnerung sofort, kostenlos, nur E-Mail
└─ Wartefrist (frist_tage der Zielstufe, gerechnet ab date_versand)
└─ Stufe 1 1. Mahnung Gebühr + Zinsen, Post/Einschreiben
└─ Stufe 2 … weitere Stufen frei konfigurierbar
└─ uneinbringlich (§288-Abschreibung)
```
- **Ob eine Stufe eine kostenlose Erinnerung ist, entscheidet das Flag `ist_erinnerung` — nicht die Stufennummer.** Eine Erinnerung kostet nichts (keine Mahngebühr, keine §288-Abs.-5-Pauschale, keine Verzugszinsen) und hat kein eigenes Mahn-PDF.
- **Erinnerungen gehen ausschließlich per E-Mail raus, echte Mahnungen ausschließlich per Post/Einschreiben.** Der Zugang einer E-Mail ist nicht beweisbar — das ist als Sperre im Code verdrahtet, nicht bloß Konvention.
- **Kein Mindestmaß an Stufen:** §286 BGB verlangt keine drei Mahnungen, eine genügt für den Verzug. Stufennummern sind frei wählbar (0–127), Lücken erlaubt; die Kette läuft aufsteigend über die aktiven Stufen.
## Features
| Feature | Implementierung |
|---|---|
| Tägliche Vorschlagsliste überfälliger Rechnungen | Cron `MahnungCronBuildVorschlag` |
| Push-Benachrichtigung mit Anzahl je Stufe | Ntfy + GlobalNotify |
| **Beliebig viele Stufen** pflegbar (Nummer, Frist, neue Frist, Gebühr B2C/B2B, Versandart, E-Mail-Template, PDF-Intro), Setup als Akkordeon | `admin/setup.php` |
| **Kostenlose Zahlungserinnerung** als eigene Stufe (Flag `ist_erinnerung`) — Gebühr, Pauschale und Zinsen hart auf 0 | `mahnungBaueVorgang()` in `lib/mahnung_anlage.lib.php` |
| B2C / B2B-Erkennung | über `llx_societe.tva_intra` |
| Verzugszinsen | tagesgenau, B2C: Basiszins +5 %, B2B: +9 %; Override pro Stufe möglich; **auto-Neuberechnung** bei noch nicht versandten Mahnungen |
| §288 Abs. 5 Pauschale 40 € | nur bei B2B, einmalig pro Rechnung |
| PDF-Mahnschreiben (DIN 5008) | Standard-TCPDF-Generator `pdf_standard_mahnung` |
| ODT-Mahnschreiben (je Stufe) | ODT-Template-Generator `doc_generic_mahnung_odt` mit Stufen-Auswahl |
| Mahnschreiben liegt im **Rechnungsordner** und steht im Reiter der Rechnung — als Beilage indexiert (`uploaded`), nicht vorne im Kasten der Rechnungskarte (0.3.1, KB #1641) | `ModelePDFMahnung::alsBeilageIndexieren()` |
| Dokumentenmodell-Verwaltung | Setup-Seite: Modelle aktivieren, Default wählen, ODT-Templates hochladen |
| Template-Variablen-Referenz | `admin/templatevars.php` — alle verfügbaren ODT-Variablen |
| Dokumentenliste auf Mahnungs-Karte | Vorschau (Lupe), Download, Modellauswahl |
| Bulk-Erstellung + Sammelbrief | TCPDI-basiert |
| **E-Mail-Versand der Zahlungserinnerung über Dolibarrs Mailformular** — Empfänger (Firma + alle Ansprechpartner), Betreff, Text und Anhang vor dem Absenden sichtbar und änderbar | `card.php?action=presend` mit `FormMail`; gesendet wird ausschließlich über `mahnungSendeErinnerungsMail()` |
| Anhang = **unveränderte Original-Rechnungs-PDF** (wird bei Bedarf nacherzeugt), weitere Dateien anhängbar | `mahnungHoleRechnungsPdf()` |
| Schutz gegen Doppelversand | atomare Reservierung per bedingtem `UPDATE` **vor** dem Senden |
| Auto-Erledigung bei Zahlungseingang | Trigger `BILL_PAYED` + `PAYMENT_CUSTOMER_CREATE` |
| Tab "Mahnungen (n)" auf Rechnungs- + Kundenkarte | Hook `completeTabsHead` (filtert auf add/external) |
| Widget: Offene Rechnungen mit Mahnstufe (Zähler im Kopf, Zeilenanzahl einstellbar, Summenzeile Netto+Brutto) | `box_mahnung_offen`, Konstante `MAHNUNG_BOX_MAXLINES` |
| **Widget-Spalte „Vsl. Zahlung" (Zahlungsprognose)**: Prognosedatum (Rechnungsdatum + Ø Tage nach Rechnungseingang) + Zahler-Ampel; Bewertungsskala 1:1 aus BuchhaltungsWidget/Kundenkarte, verstrichene Prognose = rotes Datum | `box_mahnung_offen` (`getZahlprognose`/`buildPrognoseCell`), Konstante `MAHNUNG_PROGNOSE_MIN_N` |
| **Vorschlagsliste-Filter** (Mahnstufe, Tage Verzug, Mindestbetrag, Kundentyp B2B/B2C, Kunden-Select2 mit Ajax) | `list.php` — Kundentyp-Wechsel filtert das Kunden-Dropdown sofort per Auto-Submit |
| **Kontakt-Spalte** in Vorschlagsliste (Telefon-/Mail-Icons mit Direktlink) | tel:/mailto: aus `llx_societe.phone/email` |
| **Versand-Erfassung** pro Mahnung (Datum, Versandweg, Sendungsnummer, Tracking-Provider) | `card.php` Versand-Block; setzt automatisch Status auf VERSENDET |
| **Tracking-Deep-Links** für DHL, DPAG, DPD, Hermes, UPS | konfigurierbar via Tracking-Pattern-Tabelle |
| **Sendebeleg-Upload** pro Mahnung | Dolibarrs `formfile->showdocuments('mahnung', ...)` → `DOL_DATA_ROOT/mahnung/<MAHN-Ref>/` |
| **Konfigurierbare Tracking-Patterns** (Regex + URL-Template, Priorität, Aktivierung) | `admin/tracking_patterns.php` CRUD + Live-Vorschau |
| **Live-Regex-Vorschau** (Treffer + URL-Preview während Eingabe) | `ajax/regex_preview.php` (debounced 300ms, ReDoS-Schutz) |
| **Beleg-Scan** (pdftotext + ocrmypdf-Fallback + Pattern-Matching → Sendungsnummer-Vorschlag) | Button "Belege scannen" auf `card.php`; "Übernehmen" setzt automatisch Tracking + Versanddatum + Versandweg |
| **Versand-Reminder** (Mahnung > N Tage unversendet → Ntfy-Push) | Cron `MahnungCronVersandReminder`, Schwellenwert via `MAHNUNG_VERSAND_REMINDER_DAYS` |
| **Bonitäts-Box auf Kundenkarte** (Anzahl + Summe + letztes Datum der `badcustomer` abandoned Rechnungen) | Hook `tabContentViewThirdparty` |
| **Bonitäts-Warning** bei Auftrag-/Rechnungs-Karte | Hook `formObjectOptions` (invoicecard + ordercard) |
| **"Als uneinbringlich klassifizieren"-Button** auf Mahnung Stufe 3 | Bestätigungs-Dialog mit Begründung → `Facture::setCanceled($user, CommonInvoice::CLOSECODE_BADDEBT, $note)` |
## Voraussetzungen
- Dolibarr ≥ 19
- PHP ≥ 7.4
- TCPDF (Dolibarr-Standard) für PDF
- Optional: ODTPHP (Dolibarr-Standard) für ODT-Templates
- Optional: TCPDI (Dolibarr `includes/tcpdf/tcpdi.php`) für Sammelbrief-Konkatenation
- Optional: `pdftotext` + `ocrmypdf` + `tesseract` im Container für OCR-Beleg-Scan
- Optional: `GlobalNotify` für In-App-Notification-Badges
- Empfohlen: `BankImport` (automatischer Zahlungseingang via FinTS triggert die Mahnungs-Erledigung sauber)
## Installation
```bash
git clone https://git.data-it-solution.de/data/mahnung.git \
/pfad/zu/dolibarr/htdocs/custom/mahnung
```
Dann **Startseite → Setup → Module → Finanzwesen → Mahnung** aktivieren.
Bei Aktivierung werden `llx_mahnung_mahnung` und `llx_mahnung_stufe` angelegt; drei Default-Stufen werden idempotent eingefügt. Dokumentenmodelle (`standard_mahnung`, `generic_mahnung_odt`) werden in `llx_document_model` registriert.
Nach Aktivierung in **Setup → Mahnwesen Einstellungen**:
1. Basiszins prüfen
2. Stufen-Texte (PDF-Intro, E-Mail-Body) anpassen
3. Dokumentenmodell wählen und ggf. ODT-Templates hochladen
4. Cron-Job `MahnungCronBuildVorschlag` aktivieren
## ODT-Templates
Templates werden unter **Setup → Mahnwesen Einstellungen → Dokumentenmodelle** hochgeladen.
### Dateinamen-Konvention
| Dateiname | Verwendung |
|---|---|
| `mahnung_stufe1.odt` | Zahlungserinnerung (Stufe 1) |
| `mahnung_stufe2.odt` | 1. Mahnung (Stufe 2) |
| `mahnung_stufe3.odt` | Letzte Mahnung (Stufe 3) |
| `mahnung.odt` | Fallback für alle Stufen |
### Verfügbare Template-Variablen (Auswahl)
| Variable | Beispiel |
|---|---|
| `{mahnung_ref}` | MAHN2026-0001 |
| `{mahnung_stufe}` | 1 |
| `{mahnung_stufe_label}` | Zahlungserinnerung |
| `{mahnung_date}` | 10.05.2026 |
| `{mahnung_betrag_offen}` | 131,34 |
| `{mahnung_mahngebuehr}` | 5,00 |
| `{mahnung_verzugszinsen}` | 1,23 |
| `{mahnung_summe}` | 137,57 |
| `{mahnung_kundentyp}` | B2C |
| `{facture_ref}` | IN2604-0036 |
| `{facture_total_ttc}` | 131,34 |
| `{company_name}` | Kundenname |
| `{mycompany_name}` | Eigene Firma |
| `{mahnung_bank_iban}` | DE89... |
Vollständige Liste: **Setup → Mahnwesen → Verfügbare Template-Variablen**
## Rechte
| Recht | Standard | Bedeutung |
|---|---|---|
| `mahnung.read` | aktiv | Mahnungen einsehen |
| `mahnung.write` | inaktiv | Mahnungen erstellen / bearbeiten |
| `mahnung.send` | inaktiv | Mahnungen versenden (E-Mail / Druck) |
| `mahnung.delete` | inaktiv | Mahnungen stornieren |
| `mahnung.setup` | inaktiv | Stufen, Basiszins, Versand konfigurieren |
## Modul-Konstanten
| Name | Default | Bedeutung |
|---|---|---|
| `MAHNUNG_BASISZINS` | `1.27` | BGB-Basiszins (%) — halbjährlich pflegen (1.1./1.7.) |
| `MAHNUNG_AUFSCHLAG_B2C` | `5.0` | Verzugszins-Aufschlag B2C (BGB §288 Abs. 1) |
| `MAHNUNG_AUFSCHLAG_B2B` | `9.0` | Verzugszins-Aufschlag B2B (BGB §288 Abs. 2) |
| `MAHNUNG_PAUSCHALE_B2B` | `40.00` | Pauschale B2B (EUR, BGB §288 Abs. 5, einmalig) |
| `MAHNUNG_ADDON_PDF` | `standard_mahnung` | Standard-Dokumentenmodell |
| `MAHNUNG_ADDON_PDF_ODT_PATH` | `DOL_DATA_ROOT/doctemplates/mahnung` | ODT-Template-Verzeichnis |
| `MAHNUNG_NTFY_TOPIC` | `vk-builds` | Ntfy-Topic für Vorschlags-Push |
| `MAHNUNG_VERSAND_REMINDER_DAYS` | `2` | Schwelle (Tage) für Versand-Reminder Ntfy-Push |
| `MAHNUNG_BOX_MAXLINES` | `0` | Widget: max. angezeigte offene Rechnungen (`0` = alle) |
| `MAHNUNG_PROGNOSE_MIN_N` | `1` | Widget „Vsl. Zahlung": min. bezahlte Rechnungen für eine Prognose (`1` = Parität zur Kundenkarten-Box) |
## Datenbank
| Tabelle | Zweck |
|---|---|
| `llx_mahnung_mahnung` | Mahnvorgänge — inkl. Versand-Felder `date_versand`, `versandweg`, `tracking_nr`, `tracking_provider` (idempotente Migration via `migrateVersandFelder()` beim Setup-Aufruf) |
| `llx_mahnung_stufe` | Stufen-Konfiguration (beliebig viele Stufen mit Fristen, Gebühren, Templates; `ist_erinnerung` markiert die kostenlose Zahlungserinnerung) |
| `llx_mahnung_trackingpattern` | Konfigurierbare Tracking-Regex + URL-Template pro Provider, Default-Seed mit DHL/DPAG/DPD/Hermes/UPS |
## AJAX-Endpoints
| Endpoint | Zweck | Parameter | Auth |
|---|---|---|---|
| `ajax/createmahnung.php` | Bulk-Mahnung-Erzeugung | `facture_ids[]`, `stufe`, `token` | `mahnung.write` |
| `ajax/sammelbrief.php` | Sammelbrief-PDF | `facture_ids[]`, `token` | `mahnung.send` |
| `ajax/regex_preview.php` | Live-Vorschau für Tracking-Patterns | `regex`, `sample`, `url_template` (POST) | `mahnung.setup` oder Admin |
> `ajax/sendmail.php` ist **kein Endpoint mehr**, sondern die Funktionsbibliothek des Mailversands (Empfängerermittlung, Rechnungsprüfung, Anhang, Platzhalter, Statuswächter, Versand-Reservierung, `CMailFile`-Aufruf). Verschickt wird ausschließlich über das Mailformular auf `card.php`. Ein Direktaufruf per HTTP sendet nichts, sondern leitet dorthin um.
## Hooks & Trigger
**Hook-Klasse:** `class/actions_mahnung.class.php` — registriert für `invoicecard`, `thirdpartycard`, `ordercard`.
| Hook | Zweck |
|---|---|
| `addMoreActionsButtons` | "Mahnung erstellen"-Button auf Rechnungs-Karte |
| `completeTabsHead` | Tab "Mahnungen (n)" — filtert auf `mode=add` + `filterorigmodule=external` (sonst doppelter Tab, siehe KB #601) |
| `tabContentViewThirdparty` | Bonitäts-Warnbox auf Kundenkarte wenn `badcustomer` abandoned Rechnungen existieren |
| `formObjectOptions` | Bonitäts-Warning bei Auftrag-/Rechnungs-Karten |
**Trigger:** `core/triggers/interface_99_modMahnung_MahnungTriggers.class.php`
- `BILL_PAYED` + `BILL_VALIDATE` + `PAYMENT_CUSTOMER_CREATE` → setzt offene Mahnungen auf `STATUS_ERLEDIGT`
**Cron-Jobs:**
- `MahnungCronBuildVorschlag` — täglich, Vorschlagsliste aufbauen + Ntfy-Push
- `MahnungCronVersandReminder` — täglich, Reminder für unversendete Mahnungen
## Workflow
```
Cron 06:00 (MahnungCronBuildVorschlag)
|
|--> ueberfaellige Rechnungen einsammeln
|--> Stufe ermitteln (Einstieg = kleinste aktive Stufe; Folgestufe, sobald
| frist_tage der ZIELstufe seit date_versand der
| Vorstufe verstrichen sind, Luecken werden uebersprungen)
|--> Ntfy-Push mit Anzahl je Stufe + Gesamt-EUR
|--> GlobalNotify "actionRequired" (wenn Modul aktiv)
|
v
list.php (Vorschlagsliste)
| Filter: Mahnstufe, min. Verzug, min. Betrag, Kundentyp (B2B/B2C), Kunde (Select2)
| Spalten: Rechnung, Kunde, Kontakt (tel/mail), Typ, Faelligkeit, Verzug,
| Betrag, letzte Mahnung, vorgeschlagene Stufe
|
|--> User waehlt aus + klickt "Mahnungen erzeugen"
| -> ajax/createmahnung.php berechnet Gebuehr + Verzugszinsen,
| persistiert llx_mahnung_mahnung, generiert PDF/ODT
|
|--> Alternativ "Sammelbrief erzeugen"
| -> ajax/sammelbrief.php generiert + konkateniert PDFs
|
v
card.php (Mahnung-Detailansicht)
| Documents: generierte PDFs + Modellauswahl + Regenerate
| (bei einer Zahlungserinnerung entfaellt das - kein eigenes Mahn-PDF)
| Bei Versandart E-Mail: nur Versandstatus (Empfaenger, versendet am)
| Bei Postversand: Datum, Versandweg, Sendungsnummer, Tracking-Deep-Link,
| Sendebeleg-Upload + Scan-Button
| Aktionen: Stornieren
| | Zahlungserinnerung per E-Mail senden (nur ist_erinnerung-Stufen)
| -> action=presend: Dolibarr-Mailformular mit Empfaengerauswahl
| (Firma + Ansprechpartner), Betreff, Text, Original-Rechnung
| als Anhang; action=send verschickt ueber
| mahnungSendeErinnerungsMail() (atomare Reservierung)
| | Als uneinbringlich klassifizieren (wenn keine weitere Stufe folgt)
|
Cron 06:00 (MahnungCronVersandReminder)
|--> Mahnungen mit Status=ERSTELLT > N Tage unversendet -> Ntfy-Push
|
Zahlungseingang (BankImport / Manual)
|
v Trigger BILL_PAYED / PAYMENT_CUSTOMER_CREATE
|--> alle offenen Mahnvorgaenge zur Rechnung -> status=erledigt
|
Endgueltig uneinbringlich (Mahnbescheid + Vollstreckung erfolglos)
|
v letzte Mahnstufe -> Button "Als uneinbringlich klassifizieren"
|--> Facture::setCanceled($user, CLOSECODE_BADDEBT, $note)
| -> fk_statut=3 (ABANDONED), close_code='badcustomer'
| -> Mahnung wird storniert, Begruendung in note_private
|--> Auf Kundenkarte erscheint rote Bonitaets-Warnbox
|--> Beim Anlegen neuer Auftraege/Rechnungen: Warning-Banner
|
Steuerlich (Steuer-Modul):
|--> EÜR ignoriert (liest llx_paiement, keine Zahlung = keine Einnahme)
|--> UStVA filtert fk_statut IN (1,2) -> abandoned automatisch ausgeschlossen
```
## Dateibaum
```
mahnung/
├── core/modules/modMahnung.class.php Descriptor, Rechte, Cron, Hooks, Models
│ (migrateVersandFelder + Pattern-Seed)
├── core/modules/mahnung/
│ ├── modules_mahnung.php Abstrakte Basis ModelePDFMahnung
│ └── doc/
│ ├── pdf_standard_mahnung.modules.php TCPDF-Generator (DIN 5008)
│ └── doc_generic_mahnung_odt.modules.php ODT-Template-Generator
├── core/triggers/interface_99_modMahnung_MahnungTriggers.class.php
├── core/boxes/box_mahnung_offen.php Widget: Offene Rechnungen + Mahnstufe
├── class/mahnung.class.php CRUD Mahnvorgang + generateDocument()
│ + setVersand + trackingUrl + defaultProviderForWeg
├── class/mahnungstufe.class.php CRUD Stufen-Konfig
├── class/mahnungvorschlag.class.php Vorschlags-Service (Stufenermittlung,
│ kundentyp/min_betrag-Filter, soc_phone/email)
├── class/mahnungtrackingpattern.class.php CRUD Tracking-Patterns + detectFromText
├── class/mahnungcron.class.php buildVorschlagsliste + versandReminder
├── class/mahnungntfy.class.php Ntfy-Wrapper
├── class/actions_mahnung.class.php Hook-Klasse:
│ - addMoreActionsButtons (invoicecard)
│ - completeTabsHead (invoice+thirdparty)
│ - tabContentViewThirdparty (Bonitaets-Box)
│ - formObjectOptions (invoice+ordercard Warning)
├── admin/setup.php Setup + Dokumentenmodell + Auto-Migration
├── admin/templatevars.php Template-Variablen-Referenz
├── admin/tracking_patterns.php CRUD + Live-Vorschau fuer Tracking-Patterns
├── ajax/createmahnung.php Bulk-Mahnung-Erzeugung
├── ajax/sammelbrief.php Sammelbrief-PDF-Konkatenation
├── ajax/sendmail.php Funktionsbibliothek Mailversand
│ (KEIN Endpoint - siehe AJAX-Abschnitt)
├── ajax/regex_preview.php Live-Vorschau-Endpoint fuer Pattern-Setup
├── lib/mahnung_anlage.lib.php Anlage eines Mahnvorgangs (mahnungBaueVorgang)
├── lib/mahnung_ui.lib.php Stufen-Badge + Farbskala (eine Quelle fuer
│ card.php, list.php und das Widget)
├── list.php Vorschlagsliste / Archiv (Select2-Filter)
├── card.php Detailansicht: Versand, Belege, Tracking,
│ Mailformular (FormMail), Uneinbringlich-Workflow
├── sql/llx_mahnung_*.sql Schema + Seed
└── langs/{de_DE,en_US}/mahnung.lang
```
## Lizenz
GPL-3.0