From 7532770599d10ba67a22406625951e06901e454b Mon Sep 17 00:00:00 2001 From: Eduard Wisch Date: Fri, 10 Jul 2026 08:38:17 +0200 Subject: [PATCH] Widget offene Rechnungen: Brutto-Summe zusaetzlich zur Netto-Summe [deploy] Summenzeile zeigt jetzt Netto und Brutto (aus f.total_ttc der Rechnung, nicht aus Netto hochgerechnet - damit bleiben Reverse-Charge, Steuer- befreiung und Kleinunternehmerregelung korrekt). Neue Lang-Keys MahnungBoxNetto/MahnungBoxBrutto in de_DE + en_US. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 3 +++ README.md | 3 ++- core/boxes/box_mahnung_offen.php | 15 ++++++++++++--- langs/de_DE/mahnung.lang | 2 ++ langs/en_US/mahnung.lang | 2 ++ 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 300e2a7..287735d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,7 +27,10 @@ Dolibarr Custom-Modul: 3-stufiges Mahnwesen nach BGB §288 + Versand-Tracking + - `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`. ## 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) diff --git a/README.md b/README.md index 763966b..cb62256 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Version **0.2.0 + Phasen 1-6 (Unreleased)** — UX-Vorschlagsliste, Versand-Erfa | E-Mail-Versand mit PDF-Anhang | `CMailFile`, Subject/Body mit Platzhaltern | | 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 | `box_mahnung_offen` | +| Widget: Offene Rechnungen mit Mahnstufe (Zähler im Kopf, Zeilenanzahl einstellbar, Summenzeile Netto+Brutto) | `box_mahnung_offen`, Konstante `MAHNUNG_BOX_MAXLINES` | | **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 | @@ -123,6 +123,7 @@ Vollständige Liste: **Setup → Mahnwesen → Verfügbare Template-Variablen** | `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) | ## Datenbank diff --git a/core/boxes/box_mahnung_offen.php b/core/boxes/box_mahnung_offen.php index a67032d..9ff7c55 100644 --- a/core/boxes/box_mahnung_offen.php +++ b/core/boxes/box_mahnung_offen.php @@ -240,8 +240,11 @@ class box_mahnung_offen extends ModeleBoxes } if ($num > 0) { - // Summe (wie Original: separate Query ohne LIMIT) - $sql2 = "SELECT SUM(f.total_ht) as total_ht"; + // Summe (wie Original: separate Query ohne LIMIT). + // Brutto direkt aus f.total_ttc der Rechnung, NICHT aus total_ht + // hochgerechnet — so bleiben Sonderregelungen (Reverse-Charge §13b, + // Steuerbefreiung, Kleinunternehmer §19 UStG) korrekt. + $sql2 = "SELECT SUM(f.total_ht) as total_ht, SUM(f.total_ttc) as total_ttc"; $sql2 .= " FROM ".MAIN_DB_PREFIX."facture as f"; $sql2 .= " INNER JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = f.fk_soc"; $sql2 .= " WHERE f.entity IN (".getEntity('invoice').")"; @@ -251,9 +254,11 @@ class box_mahnung_offen extends ModeleBoxes } $resTotal = $this->db->query($sql2); $totalHt = 0; + $totalTtc = 0; if ($resTotal) { $objTotal = $this->db->fetch_object($resTotal); $totalHt = (float) $objTotal->total_ht; + $totalTtc = (float) $objTotal->total_ttc; $this->db->free($resTotal); } @@ -266,9 +271,13 @@ class box_mahnung_offen extends ModeleBoxes 'td' => 'class="liste_total"', 'text' => ' ', ); + // Netto (wie bisher) + Brutto darunter, Brutto aus f.total_ttc der + // Rechnung (Sonderregelungen wie §13b/§19 UStG bleiben korrekt). $this->info_box_contents[$line][] = array( 'td' => 'class="nowraponall right liste_total"', - 'text' => price($totalHt, 0, $langs, 0, -1, -1, $conf->currency), + 'text' => ''.$langs->trans("MahnungBoxNetto").''.price($totalHt, 0, $langs, 0, -1, -1, $conf->currency) + .'
'.$langs->trans("MahnungBoxBrutto").''.price($totalTtc, 0, $langs, 0, -1, -1, $conf->currency), + 'asis' => 1, ); $this->info_box_contents[$line][] = array( 'td' => 'class="liste_total"', diff --git a/langs/de_DE/mahnung.lang b/langs/de_DE/mahnung.lang index 8fe2c96..aa0c928 100644 --- a/langs/de_DE/mahnung.lang +++ b/langs/de_DE/mahnung.lang @@ -209,6 +209,8 @@ MahnungBoxKeineOffenenRechnungen = Keine offenen Kundenrechnungen MahnungBoxMaxLines = Widget: angezeigte offene Rechnungen MahnungBoxMaxLinesAll = Alle MahnungBoxMaxLinesHelp = Wie viele offene Rechnungen das Home-Widget maximal anzeigt. „Alle" = ohne Begrenzung. Der Zähler im Widget-Kopf zeigt immer die tatsächliche Gesamtzahl. +MahnungBoxNetto = Netto +MahnungBoxBrutto = Brutto # # Dokumentenmodelle diff --git a/langs/en_US/mahnung.lang b/langs/en_US/mahnung.lang index 30cb842..0ed1265 100644 --- a/langs/en_US/mahnung.lang +++ b/langs/en_US/mahnung.lang @@ -334,6 +334,8 @@ MahnungBoxKeineOffenenRechnungen = No open customer invoices MahnungBoxMaxLines = Widget: displayed open invoices MahnungBoxMaxLinesAll = All MahnungBoxMaxLinesHelp = How many open invoices the home widget shows at most. "All" = no limit. The counter in the widget header always shows the real total. +MahnungBoxNetto = Net +MahnungBoxBrutto = Gross MahnungVorschlagStufeNichtKonfiguriert = Stage 1 not configured MahnungVorschlagFristNichtErreicht = Stage 1 deadline (%s days) not yet reached (overdue %s days) MahnungVorschlagAlleStufenAusgeschoepft = All dunning stages exhausted (last stage %s)