fix(box): Widget bleibt sichtbar wenn keine offenen Rechnungen vorhanden [deploy]
All checks were successful
Deploy mahnung / deploy (push) Successful in 12s

Wenn alle Rechnungen bezahlt sind, blieb info_box_contents leer und
ModeleBoxes::showBox renderte keinen Widget-Rahmen mehr. Widget kam
auch nach neuen Rechnungen nicht zurück. Fix: bei 0 Treffern eine
Platzhalter-Zeile "Keine offenen Kundenrechnungen" einfügen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-05-24 07:38:09 +02:00
parent bfc89917b1
commit 73e377dc01
5 changed files with 23 additions and 3 deletions

View file

@ -40,11 +40,18 @@ Dolibarr Custom-Modul: 3-stufiges Mahnwesen nach BGB §288 + Versand-Tracking +
- **`${{ github.event.head_commit.message }}` NIE direkt in `run:`-Skript interpolieren** — bei Sonderzeichen (Klammern, Backticks) bricht Bash. Immer via `env:` durchreichen. (KB #603) - **`${{ 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. - `[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 & Bonität (Phase 6)
- Versand-Felder: `date_versand`, `versandweg`, `tracking_nr`, `tracking_provider` an `llx_mahnung_mahnung` - 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) - 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-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` ist im `90-Dolibarr-Prod-Custom`-Container drin (`/usr/bin/pdftotext`); Pattern-Match via `MahnungTrackingPattern::detectFromText()` - 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) - 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) - Steuer-Modul kompatibel: EÜR ignoriert (liest nur `llx_paiement`), UStVA filtert `fk_statut IN (1,2)` automatisch (KB #607)

View file

@ -14,7 +14,7 @@ Version **0.2.0 + Phasen 1-6 (Unreleased)** — UX-Vorschlagsliste, Versand-Erfa
| Push-Benachrichtigung mit Anzahl je Stufe | Ntfy + GlobalNotify | | Push-Benachrichtigung mit Anzahl je Stufe | Ntfy + GlobalNotify |
| 3 Stufen pflegbar (Frist, neue Frist, Gebühr B2C/B2B, Versandart, E-Mail-Template, PDF-Intro) | `admin/setup.php` | | 3 Stufen pflegbar (Frist, neue Frist, Gebühr B2C/B2B, Versandart, E-Mail-Template, PDF-Intro) | `admin/setup.php` |
| B2C / B2B-Erkennung | über `llx_societe.tva_intra` | | B2C / B2B-Erkennung | über `llx_societe.tva_intra` |
| Verzugszinsen | tagesgenau, B2C: Basiszins +5 %, B2B: +9 %; Override pro Stufe möglich | | 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 | | §288 Abs. 5 Pauschale 40 € | nur bei B2B, einmalig pro Rechnung |
| PDF-Mahnschreiben (DIN 5008) | Standard-TCPDF-Generator `pdf_standard_mahnung` | | PDF-Mahnschreiben (DIN 5008) | Standard-TCPDF-Generator `pdf_standard_mahnung` |
| ODT-Mahnschreiben (je Stufe) | ODT-Template-Generator `doc_generic_mahnung_odt` mit Stufen-Auswahl | | ODT-Mahnschreiben (je Stufe) | ODT-Template-Generator `doc_generic_mahnung_odt` mit Stufen-Auswahl |
@ -33,7 +33,7 @@ Version **0.2.0 + Phasen 1-6 (Unreleased)** — UX-Vorschlagsliste, Versand-Erfa
| **Sendebeleg-Upload** pro Mahnung | Dolibarrs `formfile->showdocuments('mahnung', ...)``DOL_DATA_ROOT/mahnung/<MAHN-Ref>/` | | **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 | | **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) | | **Live-Regex-Vorschau** (Treffer + URL-Preview während Eingabe) | `ajax/regex_preview.php` (debounced 300ms, ReDoS-Schutz) |
| **Beleg-Scan** (pdftotext + Pattern-Matching → Sendungsnummer-Vorschlag) | Button "Belege scannen" auf `card.php` | | **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` | | **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-Box auf Kundenkarte** (Anzahl + Summe + letztes Datum der `badcustomer` abandoned Rechnungen) | Hook `tabContentViewThirdparty` |
| **Bonitäts-Warning** bei Auftrag-/Rechnungs-Karte | Hook `formObjectOptions` (invoicecard + ordercard) | | **Bonitäts-Warning** bei Auftrag-/Rechnungs-Karte | Hook `formObjectOptions` (invoicecard + ordercard) |
@ -46,6 +46,7 @@ Version **0.2.0 + Phasen 1-6 (Unreleased)** — UX-Vorschlagsliste, Versand-Erfa
- TCPDF (Dolibarr-Standard) für PDF - TCPDF (Dolibarr-Standard) für PDF
- Optional: ODTPHP (Dolibarr-Standard) für ODT-Templates - Optional: ODTPHP (Dolibarr-Standard) für ODT-Templates
- Optional: TCPDI (Dolibarr `includes/tcpdf/tcpdi.php`) für Sammelbrief-Konkatenation - 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 - Optional: `GlobalNotify` für In-App-Notification-Badges
- Empfohlen: `BankImport` (automatischer Zahlungseingang via FinTS triggert die Mahnungs-Erledigung sauber) - Empfohlen: `BankImport` (automatischer Zahlungseingang via FinTS triggert die Mahnungs-Erledigung sauber)

View file

@ -115,6 +115,15 @@ class box_mahnung_offen extends ModeleBoxes
$line = 0; $line = 0;
$l_due_date = $langs->trans('Late').' ('.strtolower($langs->trans('DateDue')).': %s)'; $l_due_date = $langs->trans('Late').' ('.strtolower($langs->trans('DateDue')).': %s)';
// Damit das Widget auch ohne offene Rechnungen sichtbar bleibt:
// leeres info_box_contents würde ModeleBoxes::showBox nichts rendern lassen.
if ($num == 0) {
$this->info_box_contents[0][] = array(
'td' => 'class="center opacitymedium" colspan="6"',
'text' => $langs->trans("MahnungBoxKeineOffenenRechnungen"),
);
}
while ($line < min($num, $this->max)) { while ($line < min($num, $this->max)) {
$objp = $this->db->fetch_object($result); $objp = $this->db->fetch_object($result);

View file

@ -205,6 +205,7 @@ MahnungCronBuildVorschlagDesc = Sucht täglich überfällige Rechnungen und send
# Widget # Widget
# #
MahnungBoxOffeneRechnungen = Überfällige Rechnungen mit Mahnstufe (%s) MahnungBoxOffeneRechnungen = Überfällige Rechnungen mit Mahnstufe (%s)
MahnungBoxKeineOffenenRechnungen = Keine offenen Kundenrechnungen
# #
# Dokumentenmodelle # Dokumentenmodelle

View file

@ -329,6 +329,8 @@ MahnungSetupTemplateVars = Available template variables
MahnungTriggerBeschreibung = Dunning trigger: marks open dunnings as closed on payment receipt. MahnungTriggerBeschreibung = Dunning trigger: marks open dunnings as closed on payment receipt.
MahnungBoxStufe = Stage %s MahnungBoxStufe = Stage %s
MahnungBoxStufeVom = Stage %s from %s MahnungBoxStufeVom = Stage %s from %s
MahnungBoxOffeneRechnungen = Overdue customer invoices with dunning stage (%s)
MahnungBoxKeineOffenenRechnungen = No open customer invoices
MahnungVorschlagStufeNichtKonfiguriert = Stage 1 not configured MahnungVorschlagStufeNichtKonfiguriert = Stage 1 not configured
MahnungVorschlagFristNichtErreicht = Stage 1 deadline (%s days) not yet reached (overdue %s days) MahnungVorschlagFristNichtErreicht = Stage 1 deadline (%s days) not yet reached (overdue %s days)
MahnungVorschlagAlleStufenAusgeschoepft = All dunning stages exhausted (last stage %s) MahnungVorschlagAlleStufenAusgeschoepft = All dunning stages exhausted (last stage %s)