mahnung/core/boxes/box_mahnung_offen.php
Eduard Wisch 73e377dc01
All checks were successful
Deploy mahnung / deploy (push) Successful in 12s
fix(box): Widget bleibt sichtbar wenn keine offenen Rechnungen vorhanden [deploy]
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>
2026-05-24 07:38:09 +02:00

283 lines
10 KiB
PHP

<?php
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 3.
*/
/**
* \file htdocs/custom/mahnung/core/boxes/box_mahnung_offen.php
* \ingroup mahnung
* \brief Widget: Aelteste offene Kundenrechnungen mit Mahnstufe.
* Basiert auf box_factures_imp.php, erweitert um Mahnstufe-Spalte.
*/
require_once DOL_DOCUMENT_ROOT.'/core/boxes/modules_boxes.php';
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
require_once DOL_DOCUMENT_ROOT.'/custom/mahnung/class/mahnung.class.php';
/**
* Widget: Aelteste offene Kundenrechnungen mit Mahnstufe-Badge.
*/
class box_mahnung_offen extends ModeleBoxes
{
public $boxcode = "mahnungoffenerechnungen";
public $boximg = "object_bill";
public $boxlabel = "MahnungBoxOffeneRechnungen";
public $depends = array("facture", "mahnung");
/**
* @param DoliDB $db
* @param string $param
*/
public function __construct($db, $param)
{
global $user;
$this->db = $db;
$this->hidden = !($user->hasRight('facture', 'lire'));
}
/**
* @param int $max
*/
public function loadBox($max = 5)
{
global $conf, $user, $langs;
$this->max = $max;
$langs->loadLangs(array('bills', 'mahnung@mahnung'));
$facturestatic = new Facture($this->db);
$societestatic = new Societe($this->db);
$textHead = $langs->trans("MahnungBoxOffeneRechnungen", $this->max);
$this->info_box_head = array(
'text' => $textHead.'<a class="paddingleft valignmiddle" href="'.DOL_URL_ROOT.'/compta/facture/list.php?search_status=1&sortfield=f.date_lim_reglement,f.ref&sortorder=ASC,ASC"><span class="badge">...</span></a>',
'limit' => dol_strlen($textHead),
);
if (!$user->hasRight('facture', 'lire')) {
$this->info_box_contents[0][0] = array(
'td' => 'class="nohover left"',
'text' => '<span class="opacitymedium">'.$langs->trans("ReadPermissionNotAllowed").'</span>'
);
return;
}
// Query wie box_factures_imp, plus Mahnstufe per Subquery
$sql = "SELECT s.rowid as socid, s.nom as name, s.code_client, s.client,";
$sql .= " s.logo, s.email, s.entity,";
$sql .= " f.rowid as facid, f.ref, f.type, f.datef as date,";
$sql .= " f.date_lim_reglement as datelimit,";
$sql .= " f.total_ht, f.total_tva, f.total_ttc,";
$sql .= " f.paye, f.fk_statut as status,";
$sql .= " SUM(pf.amount) as am,";
// Letzte aktive Mahnstufe
$sql .= " (SELECT m2.stufe FROM ".MAIN_DB_PREFIX."mahnung_mahnung as m2";
$sql .= " WHERE m2.fk_facture = f.rowid AND m2.status != ".((int) Mahnung::STATUS_STORNIERT);
$sql .= " ORDER BY m2.stufe DESC LIMIT 1) as mahnstufe,";
$sql .= " (SELECT m3.date_mahnung FROM ".MAIN_DB_PREFIX."mahnung_mahnung as m3";
$sql .= " WHERE m3.fk_facture = f.rowid AND m3.status != ".((int) Mahnung::STATUS_STORNIERT);
$sql .= " ORDER BY m3.stufe DESC LIMIT 1) as mahndatum,";
$sql .= " (SELECT m4.rowid FROM ".MAIN_DB_PREFIX."mahnung_mahnung as m4";
$sql .= " WHERE m4.fk_facture = f.rowid AND m4.status != ".((int) Mahnung::STATUS_STORNIERT);
$sql .= " ORDER BY m4.stufe DESC LIMIT 1) as mahnid";
$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = f.fk_soc";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
$sql .= " WHERE f.entity IN (".getEntity('invoice').")";
$sql .= " AND f.paye = 0";
$sql .= " AND f.fk_statut = 1";
if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
$sql .= " AND s.rowid = (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = s.rowid AND sc.fk_user = ".((int) $user->id)." LIMIT 1)";
}
if ($user->socid) {
$sql .= " AND s.rowid = ".((int) $user->socid);
}
$sql .= " GROUP BY s.rowid, s.nom, s.code_client, s.client, s.logo, s.email, s.entity,";
$sql .= " f.rowid, f.ref, f.type, f.datef, f.date_lim_reglement,";
$sql .= " f.total_ht, f.total_tva, f.total_ttc, f.paye, f.fk_statut";
$sql .= " ORDER BY f.date_lim_reglement ASC, f.ref ASC";
$sql .= $this->db->plimit($this->max + 1, 0);
$result = $this->db->query($sql);
if (!$result) {
$this->info_box_contents[0][0] = array(
'td' => '', 'maxlength' => 500,
'text' => $this->db->error().' sql='.$sql,
);
return;
}
$num = $this->db->num_rows($result);
$line = 0;
$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)) {
$objp = $this->db->fetch_object($result);
$datelimit = $this->db->jdate($objp->datelimit);
$facturestatic->id = $objp->facid;
$facturestatic->ref = $objp->ref;
$facturestatic->type = $objp->type;
$facturestatic->total_ht = $objp->total_ht;
$facturestatic->total_tva = $objp->total_tva;
$facturestatic->total_ttc = $objp->total_ttc;
$facturestatic->date = $this->db->jdate($objp->date);
$facturestatic->date_lim_reglement = $datelimit;
$facturestatic->statut = $objp->status;
$facturestatic->status = $objp->status;
$facturestatic->paye = $objp->paye;
$facturestatic->paid = $objp->paye;
$facturestatic->alreadypaid = $objp->am;
$facturestatic->totalpaid = $objp->am;
$societestatic->id = $objp->socid;
$societestatic->name = $objp->name;
$societestatic->code_client = $objp->code_client;
$societestatic->client = $objp->client;
$societestatic->logo = $objp->logo;
$societestatic->email = $objp->email;
$societestatic->entity = $objp->entity;
$late = '';
if ($facturestatic->hasDelay()) {
$late = img_warning(sprintf($l_due_date, dol_print_date($datelimit, 'day', 'tzuserrel')));
}
// Mahnstufe-Badge (mit Link zur Mahnung) oder Strich für keine Mahnung
$mahnCell = '<span class="opacitymedium">—</span>';
if (!empty($objp->mahnstufe)) {
$stufe = (int) $objp->mahnstufe;
$colors = array(1 => '#4a90d9', 2 => '#e68a00', 3 => '#cc3333');
$color = $colors[$stufe] ?? '#666';
$label = $langs->trans('MahnungBoxStufe', $stufe);
$mahnDatum = $objp->mahndatum ? dol_print_date($this->db->jdate($objp->mahndatum), 'day') : '';
$tooltip = $mahnDatum ? $langs->trans('MahnungBoxStufeVom', $stufe, $mahnDatum) : $label;
$badge = '<span class="badge" style="background-color:'.$color.';color:#fff;font-size:0.75em;" title="'.dol_escape_htmltag($tooltip).'">'.$label.'</span>';
if (!empty($objp->mahnid)) {
$mahnCell = '<a href="'.DOL_URL_ROOT.'/custom/mahnung/card.php?id='.((int) $objp->mahnid).'">'.$badge.'</a>';
} else {
$mahnCell = $badge;
}
}
// Spalte 1: Rechnung + Warnung
$this->info_box_contents[$line][] = array(
'td' => 'class="nowraponall"',
'text' => $facturestatic->getNomUrl(1),
'text2' => $late,
'asis' => 1,
);
// Spalte 2: Kunde
$this->info_box_contents[$line][] = array(
'td' => 'class="tdoverflowmax150 maxwidth150onsmartphone"',
'text' => $societestatic->getNomUrl(1, '', 44),
'asis' => 1,
);
// Spalte 3: Betrag
$this->info_box_contents[$line][] = array(
'td' => 'class="nowraponall right amount"',
'text' => price($objp->total_ht, 0, $langs, 0, -1, -1, $conf->currency),
);
// Spalte 4: Fälligkeitsdatum
$this->info_box_contents[$line][] = array(
'td' => 'class="center nowraponall" title="'.dol_escape_htmltag($langs->trans("DateDue").': '.dol_print_date($datelimit, 'day', 'tzuserrel')).'"',
'text' => dol_print_date($datelimit, 'day', 'tzuserrel'),
);
// Spalte 5: Mahnstufe
$this->info_box_contents[$line][] = array(
'td' => 'class="center nowraponall"',
'text' => $mahnCell,
'asis' => 1,
);
// Spalte 6: Status (rechts am Rand, schmal)
$this->info_box_contents[$line][] = array(
'td' => 'class="nowraponall right" width="16"',
'text' => $facturestatic->LibStatut($objp->paye, $objp->status, 3, $objp->am, $objp->type),
);
$line++;
}
if ($this->max < $num) {
$this->info_box_contents[$line][] = array('td' => 'colspan="6"', 'text' => '...');
$line++;
}
if ($num > 0) {
// Summe (wie Original: separate Query ohne LIMIT)
$sql2 = "SELECT SUM(f.total_ht) as total_ht";
$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').")";
$sql2 .= " AND f.paye = 0 AND f.fk_statut = 1";
if ($user->socid) {
$sql2 .= " AND s.rowid = ".((int) $user->socid);
}
$resTotal = $this->db->query($sql2);
$totalHt = 0;
if ($resTotal) {
$objTotal = $this->db->fetch_object($resTotal);
$totalHt = (float) $objTotal->total_ht;
$this->db->free($resTotal);
}
$this->info_box_contents[$line][] = array(
'tr' => 'class="liste_total"',
'td' => 'class="liste_total"',
'text' => $langs->trans("Total"),
);
$this->info_box_contents[$line][] = array(
'td' => 'class="liste_total"',
'text' => '&nbsp;',
);
$this->info_box_contents[$line][] = array(
'td' => 'class="nowraponall right liste_total"',
'text' => price($totalHt, 0, $langs, 0, -1, -1, $conf->currency),
);
$this->info_box_contents[$line][] = array(
'td' => 'class="liste_total"',
'text' => '&nbsp;',
);
$this->info_box_contents[$line][] = array(
'td' => 'class="liste_total"',
'text' => '&nbsp;',
);
$this->info_box_contents[$line][] = array(
'td' => 'class="liste_total"',
'text' => '&nbsp;',
);
}
$this->db->free($result);
}
/**
* @param array|null $head
* @param array|null $contents
* @param int $nooutput
* @return string
*/
public function showBox($head = null, $contents = null, $nooutput = 0)
{
return parent::showBox($this->info_box_head, $this->info_box_contents, $nooutput);
}
}