diff --git a/class/mahnungvorschlag.class.php b/class/mahnungvorschlag.class.php
index 826a15d..2b81ae0 100644
--- a/class/mahnungvorschlag.class.php
+++ b/class/mahnungvorschlag.class.php
@@ -108,14 +108,89 @@ class MahnungVorschlag
return $result;
}
+ /**
+ * Liefert alle ueberfaelligen Rechnungen, fuer die aktuell KEIN Vorschlag passt,
+ * inkl. Begruendung (skip_reason). Diagnose-Hilfe fuer das UI.
+ *
+ * @param array $filter (siehe getVorschlaege)
+ * @return array
+ */
+ public function getUebersprungeneRechnungen(array $filter = array())
+ {
+ $rows = $this->buildAlleVorschlaege($filter);
+ $skipped = array();
+ foreach ($rows as $r) {
+ if ($r['vorgeschlagene_stufe'] === null) {
+ $skipped[] = $r;
+ }
+ }
+ return $skipped;
+ }
+
+ /**
+ * Liefert sowohl vorgeschlagene als auch uebersprungene Rechnungen in einem Durchlauf.
+ * Result-Schluessel je Eintrag wie bei getVorschlaege(), zusaetzlich:
+ * skip_reason (string|null)
+ *
+ * @param array $filter
+ * @return array
+ */
+ public function buildAlleVorschlaege(array $filter = array())
+ {
+ $this->loadStufen();
+ if (empty($this->stufen)) {
+ return array();
+ }
+
+ $today = dol_now();
+ $sql = "SELECT f.rowid AS facture_id, f.ref AS facture_ref, f.date_lim_reglement,";
+ $sql .= " f.total_ttc, f.fk_soc, f.paye, f.statut,";
+ $sql .= " s.nom AS soc_nom, s.tva_intra";
+ $sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
+ $sql .= " INNER JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = f.fk_soc";
+ $sql .= " WHERE f.entity = ".((int) $this->entity);
+ $sql .= " AND f.statut = 1";
+ $sql .= " AND f.paye = 0";
+ $sql .= " AND f.type IN (0, 2, 3)";
+ $sql .= " AND f.date_lim_reglement IS NOT NULL";
+ $sql .= " AND f.date_lim_reglement < '".$this->db->idate($today)."'";
+
+ if (!empty($filter['soc_id'])) {
+ $sql .= " AND f.fk_soc = ".((int) $filter['soc_id']);
+ }
+
+ $sql .= " ORDER BY f.date_lim_reglement ASC";
+
+ $resql = $this->db->query($sql);
+ if (!$resql) {
+ dol_syslog('MahnungVorschlag::buildAlleVorschlaege SQL-Fehler: '.$this->db->lasterror(), LOG_ERR);
+ return array();
+ }
+
+ $result = array();
+ while ($obj = $this->db->fetch_object($resql)) {
+ $row = $this->buildVorschlag($obj, $today, true);
+ if ($row === null) {
+ continue;
+ }
+ if (isset($filter['min_tage_verzug']) && $row['tage_verzug'] < (int) $filter['min_tage_verzug']) {
+ continue;
+ }
+ $result[] = $row;
+ }
+ $this->db->free($resql);
+ return $result;
+ }
+
/**
* Berechnet fuer eine einzelne Rechnung, ob/wozu eine Mahnung vorgeschlagen wird.
*
* @param object $factureObj DB-Reihe aus facture+societe
* @param int $today Unix-Zeit
+ * @param bool $includeSkipped true = liefert auch uebersprungene mit skip_reason
* @return array|null
*/
- private function buildVorschlag($factureObj, $today)
+ private function buildVorschlag($factureObj, $today, $includeSkipped = false)
{
$dateLim = $this->db->jdate($factureObj->date_lim_reglement);
if (empty($dateLim)) {
@@ -133,33 +208,43 @@ class MahnungVorschlag
// Naechste Stufe ermitteln
$proposedStufe = null;
+ $skipReason = null;
if ($lastMahnung === null) {
- // Noch nichts gemahnt -> Stufe 1, sobald frist_tage erreicht
- if (isset($this->stufen[1]) && $tageVerzug >= (int) $this->stufen[1]->frist_tage) {
+ $frist1 = isset($this->stufen[1]) ? (int) $this->stufen[1]->frist_tage : 0;
+ if (!isset($this->stufen[1])) {
+ $skipReason = 'Stufe 1 nicht konfiguriert';
+ } elseif ($tageVerzug >= $frist1) {
$proposedStufe = 1;
+ } else {
+ $skipReason = 'Frist Stufe 1 ('.$frist1.' Tage) noch nicht erreicht (Verzug '.$tageVerzug.' Tage)';
}
} else {
- // Bereits gemahnt -> naechste Stufe wenn Wartefrist seit letzter Mahnung abgelaufen
$lastStufe = (int) $lastMahnung->stufe;
$nextStufe = $lastStufe + 1;
if ($lastStufe >= 3 || !isset($this->stufen[$nextStufe])) {
- return null; // alle Stufen ausgeschoepft
+ $skipReason = 'Alle Mahnstufen ausgeschoepft (zuletzt Stufe '.$lastStufe.')';
+ } else {
+ $wartefrist = isset($this->stufen[$lastStufe]) ? (int) $this->stufen[$lastStufe]->neue_frist_tage : 7;
+ $tageSeitMahnung = (int) floor(($today - $lastMahnung->date_mahnung) / 86400);
+ if ($tageSeitMahnung >= $wartefrist) {
+ $proposedStufe = $nextStufe;
+ } else {
+ $skipReason = 'Wartefrist nach Stufe '.$lastStufe.' laeuft noch ('.$tageSeitMahnung.'/'.$wartefrist.' Tage)';
+ }
}
- // Wartefrist: neue_frist_tage der zuletzt gemahnten Stufe
- $wartefrist = isset($this->stufen[$lastStufe]) ? (int) $this->stufen[$lastStufe]->neue_frist_tage : 7;
- $tageSeitMahnung = (int) floor(($today - $lastMahnung->date_mahnung) / 86400);
- if ($tageSeitMahnung >= $wartefrist) {
- $proposedStufe = $nextStufe;
- }
- }
-
- if ($proposedStufe === null) {
- return null;
}
// Offenen Betrag berechnen (total_ttc - Summe aller Zahlungen)
$betragOffen = $this->getBetragOffen((int) $factureObj->facture_id, (float) $factureObj->total_ttc);
if ($betragOffen <= 0) {
+ if (!$includeSkipped) {
+ return null;
+ }
+ $proposedStufe = null;
+ $skipReason = 'Offener Betrag <= 0 (vermutl. komplett bezahlt, paye-Flag noch nicht gesetzt)';
+ }
+
+ if ($proposedStufe === null && !$includeSkipped) {
return null;
}
@@ -178,7 +263,8 @@ class MahnungVorschlag
'letzte_mahnung_stufe' => $lastMahnung ? (int) $lastMahnung->stufe : null,
'letzte_mahnung_datum' => $lastMahnung ? $lastMahnung->date_mahnung : null,
'vorgeschlagene_stufe' => $proposedStufe,
- 'vorgeschlagene_stufe_label' => $this->stufen[$proposedStufe]->label,
+ 'vorgeschlagene_stufe_label' => $proposedStufe !== null ? $this->stufen[$proposedStufe]->label : null,
+ 'skip_reason' => $skipReason,
);
}
diff --git a/langs/de_DE/mahnung.lang b/langs/de_DE/mahnung.lang
index 9d33ff2..be23383 100644
--- a/langs/de_DE/mahnung.lang
+++ b/langs/de_DE/mahnung.lang
@@ -85,6 +85,9 @@ MahnungErstellen = Mahnung erstellen
MahnungSammelbrief = Sammelbrief erzeugen
MahnungStornieren = Stornieren
MahnungKeineUeberfaelligen = Keine ueberfaelligen Rechnungen vorhanden.
+MahnungUebersprungen = Aktuell uebersprungene Rechnungen
+MahnungUebersprungenHint = Diese Rechnungen sind ueberfaellig, werden aber aktuell nicht vorgeschlagen (Wartefrist laeuft noch oder alle Mahnstufen ausgeschoepft).
+MahnungSkipGrund = Grund
#
# Setup-Seite
diff --git a/langs/en_US/mahnung.lang b/langs/en_US/mahnung.lang
index bf4e19c..5acdbfb 100644
--- a/langs/en_US/mahnung.lang
+++ b/langs/en_US/mahnung.lang
@@ -85,6 +85,9 @@ MahnungErstellen = Create dunning
MahnungSammelbrief = Generate bulk letter
MahnungStornieren = Cancel
MahnungKeineUeberfaelligen = No overdue invoices found.
+MahnungUebersprungen = Currently skipped invoices
+MahnungUebersprungenHint = These invoices are overdue but currently not proposed (waiting period running or all dunning stages exhausted).
+MahnungSkipGrund = Reason
#
# Setup page
diff --git a/list.php b/list.php
index 507c6dc..70bc613 100644
--- a/list.php
+++ b/list.php
@@ -119,9 +119,16 @@ function renderVorschlagsliste($db, $filter)
$service = new MahnungVorschlag($db);
$rows = $service->getVorschlaege($filter);
+ $skipped = $service->getUebersprungeneRechnungen($filter);
+
+ if (empty($rows) && empty($skipped)) {
+ print '
'.$langs->trans('MahnungKeineUeberfaelligen').'
';
+ return;
+ }
if (empty($rows)) {
print ''.$langs->trans('MahnungKeineUeberfaelligen').'
';
+ renderUebersprungeneTabelle($skipped);
return;
}
@@ -175,6 +182,49 @@ function renderVorschlagsliste($db, $filter)
print '});';
}
print '';
+
+ renderUebersprungeneTabelle($skipped);
+}
+
+/**
+ * Diagnose-Tabelle: ueberfaellige Rechnungen, die aktuell nicht vorgeschlagen werden,
+ * inklusive Grund (Wartefrist, Stufen ausgeschoepft, ...).
+ *
+ * @param array $skipped
+ * @return void
+ */
+function renderUebersprungeneTabelle($skipped)
+{
+ global $langs;
+
+ if (empty($skipped)) {
+ return;
+ }
+
+ print '
'.$langs->trans('MahnungUebersprungen').' ('.count($skipped).')
';
+ print ''.$langs->trans('MahnungUebersprungenHint').'
';
+ print '';
+ print '';
+ print '| '.$langs->trans('MahnungRechnung').' | ';
+ print ''.$langs->trans('MahnungKunde').' | ';
+ print ''.$langs->trans('MahnungFaelligkeitAlt').' | ';
+ print ''.$langs->trans('MahnungTageVerzug').' | ';
+ print ''.$langs->trans('MahnungBetragOffen').' | ';
+ print ''.$langs->trans('MahnungLetzteMahnung').' | ';
+ print ''.$langs->trans('MahnungSkipGrund').' | ';
+ print '
';
+ foreach ($skipped as $r) {
+ print '';
+ print '| '.dol_escape_htmltag($r['facture_ref']).' | ';
+ print ''.dol_escape_htmltag($r['soc_nom']).' | ';
+ print ''.dol_print_date($r['facture_date_lim_reglement'], 'day').' | ';
+ print ''.((int) $r['tage_verzug']).' | ';
+ print ''.price($r['betrag_offen']).' | ';
+ print ''.($r['letzte_mahnung_stufe'] ? 'Stufe '.((int) $r['letzte_mahnung_stufe']).' am '.dol_print_date($r['letzte_mahnung_datum'], 'day') : '—').' | ';
+ print ''.dol_escape_htmltag((string) $r['skip_reason']).' | ';
+ print '
';
+ }
+ print '
';
}
/**
@@ -200,6 +250,28 @@ function renderArchiv($db, $filter)
return;
}
+ // Refs fuer alle in der Liste vorkommenden Rechnungen+Kunden in zwei Queries holen
+ $socIds = array();
+ $factIds = array();
+ foreach ($mahnungen as $m) {
+ $socIds[(int) $m->fk_soc] = true;
+ $factIds[(int) $m->fk_facture] = true;
+ }
+ $socMap = array();
+ if (!empty($socIds)) {
+ $resql = $db->query("SELECT rowid, nom FROM ".MAIN_DB_PREFIX."societe WHERE rowid IN (".implode(',', array_keys($socIds)).")");
+ while ($resql && ($r = $db->fetch_object($resql))) {
+ $socMap[(int) $r->rowid] = $r->nom;
+ }
+ }
+ $factMap = array();
+ if (!empty($factIds)) {
+ $resql = $db->query("SELECT rowid, ref FROM ".MAIN_DB_PREFIX."facture WHERE rowid IN (".implode(',', array_keys($factIds)).")");
+ while ($resql && ($r = $db->fetch_object($resql))) {
+ $factMap[(int) $r->rowid] = $r->ref;
+ }
+ }
+
print '