Widget-Prognose: Paritaet zur Kundenkarten-Box + Frueh-Signal statt "ueberfaellig" [deploy]
All checks were successful
Deploy mahnung / deploy (push) Successful in 13s
All checks were successful
Deploy mahnung / deploy (push) Successful in 13s
Fix nach Eddys Beobachtung: Kunde mit Historie auf der Kundenkarte (BuchhaltungsWidget bewertet ab n>=1) erschien im Widget als "keine Historie", weil MAHNUNG_PROGNOSE_MIN_N auf 3 stand. - MIN_N Default 3 -> 1: Paritaet zur Zahlungsverhalten-Box, die ebenfalls ab der 1. bezahlten Rechnung bewertet. Weiter per Konstante erhoehbar. - "(ueberfaellig)" -> "(spaeter als ueblich)": Das Flag greift wenn das Prognosedatum verstrichen und die Rechnung noch offen ist - das kann schon VOR der formellen Faelligkeit passieren (Fruehzahler). "ueberfaellig" war dafuer das falsche Wort. Neuer Lang-Key MahnungProgSpaeterAlsUeblich. - Prognosedatum + OE-Verzug jetzt aus dem EXAKTEN Mittelwert (Timestamp statt gerundeter Tage), Anzeige mit einer Nachkommastelle -> deckt sich 1:1 mit "Differenz" auf der Kundenkarte (z.B. -3.1 T -> Faelligkeit-3.1). Lokal gegen die Test-DB verifiziert (n=1/n=2 werden gezeigt, Flag nur bei verstrichener Prognose, Zukunfts-Faelligkeit ohne Flag, php -l sauber). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
adfeafa09b
commit
4997a78599
5 changed files with 20 additions and 13 deletions
|
|
@ -6,8 +6,9 @@
|
|||
- **Neue Spalte „Vsl. Zahlung" (Zahlungsprognose)**: Je offener Rechnung wird aus dem bisherigen Zahlungsverhalten des Kunden prognostiziert, wann er die Rechnung voraussichtlich zahlt (`Fälligkeit + round(Ø-Verzug)`), inkl. Ampel-Icon + Bewertungsstufe + Ø-Verzug + Stichprobengröße `n`.
|
||||
- **Skala 1:1 aus dem BuchhaltungsWidget** übernommen (KB #886), damit die Ampel zur „Zahlungsverhalten"-Box auf der Kundenkarte passt: `diff = Ø(Zahlung−Rechnungsdatum) − Ø(Fälligkeit−Rechnungsdatum)` = Ø Tage nach Fälligkeit; Stufen ≤−5 Vorbildlich ★ / ≤0 Pünktlich ✓ / ≤7 Spätzahler ⚠ / ≤14 Verspätet ⏱ / >14 Problematisch ✗ (gleiche Farben `#28a745/#ffc107/#fd7e14/#dc3545`).
|
||||
- **Berechnung als self-contained Kopie** (`getZahlprognose()`, 1 Query je Kunde/Request gecacht) — **keine** Laufzeit-Abhängigkeit auf das BuchhaltungsWidget. Filter identisch KB #886: `type IN (0,1,5)`, `fk_statut=2 + paye=1`, `date_lim_reglement IS NOT NULL`, `DATEDIFF`, `entity`.
|
||||
- Prognosedatum bereits überschritten, Rechnung aber noch offen → Zusatz „(überfällig)" (Kunde zahlt schlechter als sonst = Mahnsignal).
|
||||
- Mindest-Stichprobe fürs Datum via Konstante `MAHNUNG_PROGNOSE_MIN_N` (Default 3), darunter „keine Historie". Neue Lang-Keys `MahnungProg*` (de_DE + en_US).
|
||||
- Prognosedatum bereits verstrichen, Rechnung aber noch offen → Zusatz „(später als üblich)" — der Kunde zahlt langsamer als sein eigenes Muster (Frühwarnung; greift ggf. schon **vor** der formellen Fälligkeit, ist also bewusst nicht mit „überfällig" beschriftet).
|
||||
- Prognosedatum + Ø-Verzug aus dem **exakten** Mittelwert (eine Nachkommastelle), damit die Zahl 1:1 zur „Differenz" auf der Kundenkarte passt (z.B. −3.1 T).
|
||||
- Mindest-Stichprobe fürs Datum via Konstante `MAHNUNG_PROGNOSE_MIN_N` (**Default 1** = Parität zur Kundenkarten-Box, die ebenfalls ab der 1. bezahlten Rechnung bewertet), darunter „keine Historie". Neue Lang-Keys `MahnungProg*` (de_DE + en_US).
|
||||
- **Summenzeile Netto + Brutto**: Die `liste_total`-Zeile zeigt jetzt zweizeilig **Netto** (`SUM(f.total_ht)`) und **Brutto** (`SUM(f.total_ttc)`). Der Bruttobetrag kommt direkt aus `f.total_ttc` der Rechnung und wird **nicht** aus dem Netto hochgerechnet — so bleiben Steuer-Sonderregelungen (Reverse-Charge §13b, Steuerbefreiung, Kleinunternehmer §19 UStG) korrekt. Einzelzeilen zeigen weiterhin nur Netto. Neue Lang-Keys `MahnungBoxNetto`/`MahnungBoxBrutto` (de_DE + en_US).
|
||||
- **Zähler im Kopf**: Der Badge im Widget-Kopf zeigt die tatsächliche Gesamtzahl offener Rechnungen (verlinkt auf die gefilterte Rechnungsliste). Titel-Lang-Key ohne `(%s)`.
|
||||
- **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 (korrekter Zähler), rendert aber nur `MAHNUNG_BOX_MAXLINES` Zeilen + eine `...`-Überlaufzeile.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ Dolibarr Custom-Modul: 3-stufiges Mahnwesen nach BGB §288 + Versand-Tracking +
|
|||
- **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 = Ø(Zahlung−Rechnungsdatum) − Ø(Fälligkeit−Rechnungsdatum)`; 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).
|
||||
- **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 = Ø(Zahlung−Rechnungsdatum) − Ø(Fälligkeit−Rechnungsdatum)`; Prognosedatum = `Fälligkeit + diff` (exakt, aus dem Timestamp); Mindest-`n` via `MAHNUNG_PROGNOSE_MIN_N` (**Default 1** = Parität zur Kundenkarten-Box). Prognose verstrichen + Rechnung offen → „(später als üblich)" (NICHT „überfällig" — greift ggf. schon vor formeller Fälligkeit). 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)
|
||||
|
|
|
|||
|
|
@ -406,7 +406,10 @@ class box_mahnung_offen extends ModeleBoxes
|
|||
global $langs;
|
||||
|
||||
$prog = $this->getZahlprognose($socid);
|
||||
$minN = getDolGlobalInt('MAHNUNG_PROGNOSE_MIN_N', 3);
|
||||
// Default 1 = Parität zur "Zahlungsverhalten"-Box (BuchhaltungsWidget, KB #886),
|
||||
// die ab der 1. bezahlten Rechnung bewertet. Höher setzen, wenn Einzelrechnungs-
|
||||
// Prognosen als zu unsicher empfunden werden.
|
||||
$minN = getDolGlobalInt('MAHNUNG_PROGNOSE_MIN_N', 1);
|
||||
|
||||
// transnoentities() liefert rohes UTF-8 (kein ü) — sonst würde das
|
||||
// nachfolgende dol_escape_htmltag() das & doppelt kodieren.
|
||||
|
|
@ -419,22 +422,25 @@ class box_mahnung_offen extends ModeleBoxes
|
|||
$rating = $this->prognoseRating($prog['diff']);
|
||||
$label = $langs->transnoentities($rating['labelkey']);
|
||||
|
||||
$tage = (int) round($prog['diff']);
|
||||
$progTs = (int) $datelimit + ($tage * 86400);
|
||||
// Prognosedatum aus dem EXAKTEN Ø-Verzug (gleiche Zahl wie "Differenz" auf der
|
||||
// Kundenkarte); Anzeige mit einer Nachkommastelle, damit z.B. −3.1 exakt passt.
|
||||
$progTs = (int) round((int) $datelimit + $prog['diff'] * 86400);
|
||||
$progDate = dol_print_date($progTs, 'day', 'tzuserrel');
|
||||
$avg = sprintf('%+d', $tage); // Vorzeichen: + nach Fälligkeit, − davor
|
||||
$avg = sprintf('%+.1f', $prog['diff']); // Vorzeichen: + nach Fälligkeit, − davor
|
||||
|
||||
// Prognosedatum überschritten, Rechnung noch offen → schlechter als sonst
|
||||
$ueberf = '';
|
||||
// Prognosedatum bereits verstrichen, Rechnung aber noch offen → der Kunde zahlt
|
||||
// langsamer als sein eigenes Muster. NICHT dasselbe wie "überfällig" (kann schon
|
||||
// vor der formellen Fälligkeit greifen) — daher eigene Formulierung.
|
||||
$hint = '';
|
||||
if ($progTs < dol_now()) {
|
||||
$ueberf = ' <span style="color:#dc3545;">('.dol_escape_htmltag($langs->transnoentities('MahnungProgUeberfaellig')).')</span>';
|
||||
$hint = ' <span style="color:#dc3545;">('.dol_escape_htmltag($langs->transnoentities('MahnungProgSpaeterAlsUeblich')).')</span>';
|
||||
}
|
||||
|
||||
// Parameter direkt an trans übergeben (macht das %s-sprintf selbst).
|
||||
$tooltip = $langs->transnoentities('MahnungProgTooltip', $progDate, $avg, (string) $prog['count']);
|
||||
|
||||
$html = '<span title="'.dol_escape_htmltag($tooltip).'">';
|
||||
$html .= '<span style="color:'.$rating['color'].';">'.$rating['icon'].'</span> ~'.$progDate.$ueberf;
|
||||
$html .= '<span style="color:'.$rating['color'].';">'.$rating['icon'].'</span> ~'.$progDate.$hint;
|
||||
$html .= '<br><span class="opacitymedium small">'.dol_escape_htmltag($label)
|
||||
.' · Ø '.$avg.' T · n='.((int) $prog['count']).'</span>';
|
||||
$html .= '</span>';
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ MahnungProgVerspaetet = Verspätet
|
|||
MahnungProgProblematisch = Problematisch
|
||||
MahnungProgKeineHistorie = keine Historie
|
||||
MahnungProgKeineHistorieTip = Zu wenig bezahlte Rechnungen für eine belastbare Zahlungsprognose
|
||||
MahnungProgUeberfaellig = überfällig
|
||||
MahnungProgSpaeterAlsUeblich = später als üblich
|
||||
MahnungProgTooltip = Voraussichtlich am %s (im Schnitt %s Tage zur Fälligkeit, %s bezahlte Rechnungen)
|
||||
|
||||
#
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ MahnungProgVerspaetet = Late
|
|||
MahnungProgProblematisch = Problematic
|
||||
MahnungProgKeineHistorie = no history
|
||||
MahnungProgKeineHistorieTip = Too few paid invoices for a reliable payment forecast
|
||||
MahnungProgUeberfaellig = overdue
|
||||
MahnungProgSpaeterAlsUeblich = later than usual
|
||||
MahnungProgTooltip = Expected on %s (on average %s days relative to due date, %s paid invoices)
|
||||
MahnungVorschlagStufeNichtKonfiguriert = Stage 1 not configured
|
||||
MahnungVorschlagFristNichtErreicht = Stage 1 deadline (%s days) not yet reached (overdue %s days)
|
||||
|
|
|
|||
Loading…
Reference in a new issue