All checks were successful
Deploy mahnung / deploy (push) Successful in 12s
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>
283 lines
10 KiB
PHP
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' => ' ',
|
|
);
|
|
$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' => ' ',
|
|
);
|
|
$this->info_box_contents[$line][] = array(
|
|
'td' => 'class="liste_total"',
|
|
'text' => ' ',
|
|
);
|
|
$this->info_box_contents[$line][] = array(
|
|
'td' => 'class="liste_total"',
|
|
'text' => ' ',
|
|
);
|
|
}
|
|
|
|
$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);
|
|
}
|
|
}
|