Widget: Zahlungsprognose-Spalte (Skala aus BuchhaltungsWidget/KB #886) [deploy]
All checks were successful
Deploy mahnung / deploy (push) Successful in 13s
All checks were successful
Deploy mahnung / deploy (push) Successful in 13s
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>
This commit is contained in:
parent
7532770599
commit
adfeafa09b
5 changed files with 183 additions and 4 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -2,6 +2,16 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Widget (box_mahnung_offen)
|
||||||
|
- **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).
|
||||||
|
- **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.
|
||||||
|
|
||||||
### Schema-Fix (tms-Spalten)
|
### Schema-Fix (tms-Spalten)
|
||||||
- Die `tms`-Spalten von `llx_mahnung_mahnung`, `llx_mahnung_stufe` und `llx_mahnung_trackingpattern` wurden als reines `TIMESTAMP` angelegt — unter `explicit_defaults_for_timestamp` entstand daraus `NULL DEFAULT NULL`, sodass `tms` bei jedem UPDATE leer blieb. Jetzt Dolibarr-Standard `DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP`, damit der Änderungszeitpunkt (z.B. einer per Zahlungstrigger erledigten Mahnung) wieder nachvollziehbar ist.
|
- Die `tms`-Spalten von `llx_mahnung_mahnung`, `llx_mahnung_stufe` und `llx_mahnung_trackingpattern` wurden als reines `TIMESTAMP` angelegt — unter `explicit_defaults_for_timestamp` entstand daraus `NULL DEFAULT NULL`, sodass `tms` bei jedem UPDATE leer blieb. Jetzt Dolibarr-Standard `DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP`, damit der Änderungszeitpunkt (z.B. einer per Zahlungstrigger erledigten Mahnung) wieder nachvollziehbar ist.
|
||||||
- Neue idempotente Migration `migrateTimestampSpalten()` (läuft im Modul-`init()`): befüllt bestehende `NULL`-Werte aus `datec` und stellt die Spalte per `ALTER TABLE` um. Greift auf Bestands-Installs beim Re-Aktivieren des Moduls.
|
- Neue idempotente Migration `migrateTimestampSpalten()` (läuft im Modul-`init()`): befüllt bestehende `NULL`-Werte aus `datec` und stellt die Spalte per `ALTER TABLE` um. Greift auf Bestands-Installs beim Re-Aktivieren des Moduls.
|
||||||
|
|
|
||||||
|
|
@ -31,6 +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)
|
- **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.
|
- **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`.
|
- **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).
|
||||||
|
|
||||||
## Hooks-Stolperfallen
|
## 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)
|
- **`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)
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ class box_mahnung_offen extends ModeleBoxes
|
||||||
public $boxlabel = "MahnungBoxOffeneRechnungen";
|
public $boxlabel = "MahnungBoxOffeneRechnungen";
|
||||||
public $depends = array("facture", "mahnung");
|
public $depends = array("facture", "mahnung");
|
||||||
|
|
||||||
|
/** @var array<int,array|null> Zahlprognose je socid — 1 Query pro Kunde/Request (Cache) */
|
||||||
|
private static $prognoseCache = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param DoliDB $db
|
* @param DoliDB $db
|
||||||
* @param string $param
|
* @param string $param
|
||||||
|
|
@ -129,7 +132,7 @@ class box_mahnung_offen extends ModeleBoxes
|
||||||
// leeres info_box_contents würde ModeleBoxes::showBox nichts rendern lassen.
|
// leeres info_box_contents würde ModeleBoxes::showBox nichts rendern lassen.
|
||||||
if ($num == 0) {
|
if ($num == 0) {
|
||||||
$this->info_box_contents[0][] = array(
|
$this->info_box_contents[0][] = array(
|
||||||
'td' => 'class="center opacitymedium" colspan="6"',
|
'td' => 'class="center opacitymedium" colspan="7"',
|
||||||
'text' => $langs->trans("MahnungBoxKeineOffenenRechnungen"),
|
'text' => $langs->trans("MahnungBoxKeineOffenenRechnungen"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -217,14 +220,21 @@ class box_mahnung_offen extends ModeleBoxes
|
||||||
'text' => dol_print_date($datelimit, 'day', 'tzuserrel'),
|
'text' => dol_print_date($datelimit, 'day', 'tzuserrel'),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Spalte 5: Mahnstufe
|
// Spalte 5: Voraussichtliche Zahlung (Prognose aus dem Zahlungsverhalten)
|
||||||
|
$this->info_box_contents[$line][] = array(
|
||||||
|
'td' => 'class="center nowraponall"',
|
||||||
|
'text' => $this->buildPrognoseCell((int) $objp->socid, $datelimit),
|
||||||
|
'asis' => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Spalte 6: Mahnstufe
|
||||||
$this->info_box_contents[$line][] = array(
|
$this->info_box_contents[$line][] = array(
|
||||||
'td' => 'class="center nowraponall"',
|
'td' => 'class="center nowraponall"',
|
||||||
'text' => $mahnCell,
|
'text' => $mahnCell,
|
||||||
'asis' => 1,
|
'asis' => 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Spalte 6: Status (rechts am Rand, schmal)
|
// Spalte 7: Status (rechts am Rand, schmal)
|
||||||
$this->info_box_contents[$line][] = array(
|
$this->info_box_contents[$line][] = array(
|
||||||
'td' => 'class="nowraponall right" width="16"',
|
'td' => 'class="nowraponall right" width="16"',
|
||||||
'text' => $facturestatic->LibStatut($objp->paye, $objp->status, 3, $objp->am, $objp->type),
|
'text' => $facturestatic->LibStatut($objp->paye, $objp->status, 3, $objp->am, $objp->type),
|
||||||
|
|
@ -235,7 +245,7 @@ class box_mahnung_offen extends ModeleBoxes
|
||||||
|
|
||||||
// Mehr offene Rechnungen vorhanden als angezeigt: "..."-Zeile als Verweis
|
// Mehr offene Rechnungen vorhanden als angezeigt: "..."-Zeile als Verweis
|
||||||
if ($boxmax > 0 && $num > $boxmax) {
|
if ($boxmax > 0 && $num > $boxmax) {
|
||||||
$this->info_box_contents[$line][] = array('td' => 'colspan="6"', 'text' => '...');
|
$this->info_box_contents[$line][] = array('td' => 'colspan="7"', 'text' => '...');
|
||||||
$line++;
|
$line++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,6 +289,12 @@ class box_mahnung_offen extends ModeleBoxes
|
||||||
.'<br><span class="opacitymedium paddingright small">'.$langs->trans("MahnungBoxBrutto").'</span>'.price($totalTtc, 0, $langs, 0, -1, -1, $conf->currency),
|
.'<br><span class="opacitymedium paddingright small">'.$langs->trans("MahnungBoxBrutto").'</span>'.price($totalTtc, 0, $langs, 0, -1, -1, $conf->currency),
|
||||||
'asis' => 1,
|
'asis' => 1,
|
||||||
);
|
);
|
||||||
|
// Fälligkeit-Spalte (leer)
|
||||||
|
$this->info_box_contents[$line][] = array(
|
||||||
|
'td' => 'class="liste_total"',
|
||||||
|
'text' => ' ',
|
||||||
|
);
|
||||||
|
// Vsl.-Zahlung-Spalte (leer)
|
||||||
$this->info_box_contents[$line][] = array(
|
$this->info_box_contents[$line][] = array(
|
||||||
'td' => 'class="liste_total"',
|
'td' => 'class="liste_total"',
|
||||||
'text' => ' ',
|
'text' => ' ',
|
||||||
|
|
@ -296,6 +312,136 @@ class box_mahnung_offen extends ModeleBoxes
|
||||||
$this->db->free($result);
|
$this->db->free($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zahlungsverhalten eines Kunden aus seinen bezahlten Rechnungen.
|
||||||
|
*
|
||||||
|
* Rechnung IDENTISCH zu BuchhaltungsWidget::getPaymentStatistics() (KB #886),
|
||||||
|
* damit die Ampel hier zur "Zahlungsverhalten"-Box auf der Kundenkarte passt:
|
||||||
|
* diff = Ø(Zahlung − Rechnungsdatum) − Ø(Fälligkeit − Rechnungsdatum)
|
||||||
|
* = durchschnittliche Tage NACH Fälligkeit (negativ = vorher gezahlt).
|
||||||
|
* Bewusst nur Standard/Ersatz/Situation (type IN 0,1,5), voll bezahlt
|
||||||
|
* (fk_statut=2 + paye=1), mit Fälligkeit. Selbst-enthaltene Kopie —
|
||||||
|
* KEINE Laufzeit-Abhängigkeit auf BuchhaltungsWidget.
|
||||||
|
*
|
||||||
|
* @param int $socid
|
||||||
|
* @return array{count:int,diff:float}|null null bei Fehler / ohne Historie
|
||||||
|
*/
|
||||||
|
private function getZahlprognose($socid)
|
||||||
|
{
|
||||||
|
$socid = (int) $socid;
|
||||||
|
if ($socid <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (array_key_exists($socid, self::$prognoseCache)) {
|
||||||
|
return self::$prognoseCache[$socid];
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT COUNT(*) as invoice_count,";
|
||||||
|
$sql .= " AVG(DATEDIFF(sub.pay_date, f.datef)) as avg_payment_days,";
|
||||||
|
$sql .= " AVG(DATEDIFF(f.date_lim_reglement, f.datef)) as avg_due_days";
|
||||||
|
$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
|
||||||
|
$sql .= " INNER JOIN (";
|
||||||
|
$sql .= " SELECT pf.fk_facture, MAX(p.datep) as pay_date";
|
||||||
|
$sql .= " FROM ".MAIN_DB_PREFIX."paiement_facture as pf";
|
||||||
|
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."paiement as p ON p.rowid = pf.fk_paiement";
|
||||||
|
$sql .= " GROUP BY pf.fk_facture";
|
||||||
|
$sql .= " ) as sub ON sub.fk_facture = f.rowid";
|
||||||
|
$sql .= " WHERE f.fk_soc = ".$socid;
|
||||||
|
$sql .= " AND f.fk_statut = 2 AND f.paye = 1";
|
||||||
|
$sql .= " AND f.type IN (0, 1, 5)";
|
||||||
|
$sql .= " AND f.date_lim_reglement IS NOT NULL";
|
||||||
|
$sql .= " AND f.entity IN (".getEntity('invoice').")";
|
||||||
|
|
||||||
|
$out = null;
|
||||||
|
$res = $this->db->query($sql);
|
||||||
|
if ($res) {
|
||||||
|
$o = $this->db->fetch_object($res);
|
||||||
|
if ($o && (int) $o->invoice_count > 0) {
|
||||||
|
$out = array(
|
||||||
|
'count' => (int) $o->invoice_count,
|
||||||
|
'diff' => (float) $o->avg_payment_days - (float) $o->avg_due_days,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$this->db->free($res);
|
||||||
|
} else {
|
||||||
|
dol_syslog("box_mahnung_offen::getZahlprognose SQL-Fehler: ".$this->db->lasterror(), LOG_ERR);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$prognoseCache[$socid] = $out;
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewertungsstufe zu einem diff-Wert (Ø Tage nach Fälligkeit).
|
||||||
|
* Schwellen, Farben und Icons IDENTISCH zu BuchhaltungsWidget (KB #886).
|
||||||
|
*
|
||||||
|
* @param float $diff
|
||||||
|
* @return array{icon:string,color:string,labelkey:string}
|
||||||
|
*/
|
||||||
|
private function prognoseRating($diff)
|
||||||
|
{
|
||||||
|
if ($diff <= -5) {
|
||||||
|
return array('icon' => '★', 'color' => '#28a745', 'labelkey' => 'MahnungProgVorbildlich'); // ★
|
||||||
|
} elseif ($diff <= 0) {
|
||||||
|
return array('icon' => '✓', 'color' => '#28a745', 'labelkey' => 'MahnungProgPuenktlich'); // ✓
|
||||||
|
} elseif ($diff <= 7) {
|
||||||
|
return array('icon' => '⚠', 'color' => '#ffc107', 'labelkey' => 'MahnungProgSpaetzahler'); // ⚠
|
||||||
|
} elseif ($diff <= 14) {
|
||||||
|
return array('icon' => '⏱', 'color' => '#fd7e14', 'labelkey' => 'MahnungProgVerspaetet'); // ⏱
|
||||||
|
}
|
||||||
|
return array('icon' => '✗', 'color' => '#dc3545', 'labelkey' => 'MahnungProgProblematisch'); // ✗
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut die HTML-Zelle "Vsl. Zahlung": Ampel-Icon + Prognosedatum + Kurzstatistik.
|
||||||
|
* Prognosedatum = Fälligkeit + round(diff). Prognose bereits vorbei, Rechnung aber
|
||||||
|
* noch offen → Zusatz "(überfällig)" (Kunde zahlt schlechter als sonst).
|
||||||
|
*
|
||||||
|
* @param int $socid
|
||||||
|
* @param int $datelimit Fälligkeit als Unix-Timestamp (jdate)
|
||||||
|
* @return string HTML (wird 'asis' gerendert)
|
||||||
|
*/
|
||||||
|
private function buildPrognoseCell($socid, $datelimit)
|
||||||
|
{
|
||||||
|
global $langs;
|
||||||
|
|
||||||
|
$prog = $this->getZahlprognose($socid);
|
||||||
|
$minN = getDolGlobalInt('MAHNUNG_PROGNOSE_MIN_N', 3);
|
||||||
|
|
||||||
|
// transnoentities() liefert rohes UTF-8 (kein ü) — sonst würde das
|
||||||
|
// nachfolgende dol_escape_htmltag() das & doppelt kodieren.
|
||||||
|
// Zu wenig Historie (oder gar keine) → keine belastbare Prognose
|
||||||
|
if ($prog === null || $prog['count'] < $minN) {
|
||||||
|
return '<span class="opacitymedium" title="'.dol_escape_htmltag($langs->transnoentities('MahnungProgKeineHistorieTip')).'">'
|
||||||
|
.'○ '.dol_escape_htmltag($langs->transnoentities('MahnungProgKeineHistorie')).'</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$rating = $this->prognoseRating($prog['diff']);
|
||||||
|
$label = $langs->transnoentities($rating['labelkey']);
|
||||||
|
|
||||||
|
$tage = (int) round($prog['diff']);
|
||||||
|
$progTs = (int) $datelimit + ($tage * 86400);
|
||||||
|
$progDate = dol_print_date($progTs, 'day', 'tzuserrel');
|
||||||
|
$avg = sprintf('%+d', $tage); // Vorzeichen: + nach Fälligkeit, − davor
|
||||||
|
|
||||||
|
// Prognosedatum überschritten, Rechnung noch offen → schlechter als sonst
|
||||||
|
$ueberf = '';
|
||||||
|
if ($progTs < dol_now()) {
|
||||||
|
$ueberf = ' <span style="color:#dc3545;">('.dol_escape_htmltag($langs->transnoentities('MahnungProgUeberfaellig')).')</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 .= '<br><span class="opacitymedium small">'.dol_escape_htmltag($label)
|
||||||
|
.' · Ø '.$avg.' T · n='.((int) $prog['count']).'</span>';
|
||||||
|
$html .= '</span>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array|null $head
|
* @param array|null $head
|
||||||
* @param array|null $contents
|
* @param array|null $contents
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,17 @@ 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.
|
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
|
MahnungBoxNetto = Netto
|
||||||
MahnungBoxBrutto = Brutto
|
MahnungBoxBrutto = Brutto
|
||||||
|
# Widget: Zahlungsprognose (Skala identisch BuchhaltungsWidget / KB #886)
|
||||||
|
MahnungProgVslZahlung = Vsl. Zahlung
|
||||||
|
MahnungProgVorbildlich = Vorbildlich
|
||||||
|
MahnungProgPuenktlich = Pünktlich
|
||||||
|
MahnungProgSpaetzahler = Spätzahler
|
||||||
|
MahnungProgVerspaetet = Verspätet
|
||||||
|
MahnungProgProblematisch = Problematisch
|
||||||
|
MahnungProgKeineHistorie = keine Historie
|
||||||
|
MahnungProgKeineHistorieTip = Zu wenig bezahlte Rechnungen für eine belastbare Zahlungsprognose
|
||||||
|
MahnungProgUeberfaellig = überfällig
|
||||||
|
MahnungProgTooltip = Voraussichtlich am %s (im Schnitt %s Tage zur Fälligkeit, %s bezahlte Rechnungen)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Dokumentenmodelle
|
# Dokumentenmodelle
|
||||||
|
|
|
||||||
|
|
@ -336,6 +336,17 @@ 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.
|
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
|
MahnungBoxNetto = Net
|
||||||
MahnungBoxBrutto = Gross
|
MahnungBoxBrutto = Gross
|
||||||
|
# Widget: payment forecast (scale identical to BuchhaltungsWidget / KB #886)
|
||||||
|
MahnungProgVslZahlung = Est. payment
|
||||||
|
MahnungProgVorbildlich = Exemplary
|
||||||
|
MahnungProgPuenktlich = On time
|
||||||
|
MahnungProgSpaetzahler = Slightly late
|
||||||
|
MahnungProgVerspaetet = Late
|
||||||
|
MahnungProgProblematisch = Problematic
|
||||||
|
MahnungProgKeineHistorie = no history
|
||||||
|
MahnungProgKeineHistorieTip = Too few paid invoices for a reliable payment forecast
|
||||||
|
MahnungProgUeberfaellig = overdue
|
||||||
|
MahnungProgTooltip = Expected on %s (on average %s days relative to due date, %s paid 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)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue