* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. */ /** * \file htdocs/custom/buchaltungswidget/class/actions_buchaltungswidget.class.php * \ingroup buchaltungswidget * \brief Hook actions for displaying payment statistics on thirdparty card */ require_once DOL_DOCUMENT_ROOT.'/core/class/commonhookactions.class.php'; /** * Class ActionsBuchaltungsWidget */ class ActionsBuchaltungsWidget extends CommonHookActions { /** * @var DoliDB Database handler */ public $db; /** * @var string Error message */ public $error = ''; /** * @var array Errors array */ public $errors = array(); /** * @var string HTML output to be printed */ public $resprints; /** * Constructor * * @param DoliDB $db Database handler */ public function __construct($db) { $this->db = $db; } /** * Hook to add payment statistics on thirdparty card view * * @param array $parameters Hook parameters * @param object $object The object being processed (Societe) * @param string $action Current action * @return int 0 = continue, > 0 = replace */ public function tabContentViewThirdparty($parameters, &$object, &$action) { global $conf, $langs, $user; // Check if feature is enabled in settings (default: enabled) if (!getDolGlobalInt('BUCHALTUNGSWIDGET_SHOW_PAYMENT_STATS', 1)) { return 0; } // Check if user has rights if (!$user->hasRight('facture', 'lire')) { return 0; } // Only show for customers (client = 1, 2 or 3) if (empty($object->client)) { return 0; } $langs->load("buchaltungswidget@buchaltungswidget"); // Get payment statistics $stats = $this->getPaymentStatistics($object->id); if ($stats['invoice_count'] == 0) { // No invoices yet return 0; } // Determine color based on payment behavior $colorClass = $this->getPaymentColorClass($stats); $icon = $this->getPaymentIcon($stats); // Build HTML output $html = ''; $html .= '
'; $html .= '
'; // Icon $html .= '
'.$icon.'
'; // Stats content $html .= '
'; $html .= '
'.$langs->trans("PaymentBehavior").'
'; $html .= '
'; // Average payment days $html .= '
'; $html .= ''.$langs->trans("AvgPaymentDays").': '; $html .= ''.round($stats['avg_payment_days'], 1).' '.$langs->trans("Days").''; $html .= '
'; // Average due days (payment terms) $html .= '
'; $html .= ''.$langs->trans("AvgDueDays").': '; $html .= ''.round($stats['avg_due_days'], 1).' '.$langs->trans("Days").''; $html .= '
'; // Difference $diff = $stats['avg_payment_days'] - $stats['avg_due_days']; $diffText = $diff > 0 ? '+'.round($diff, 1) : round($diff, 1); $html .= '
'; $html .= ''.$langs->trans("Difference").': '; $html .= ''.$diffText.' '.$langs->trans("Days").''; $html .= '
'; // Invoice count $html .= '
'; $html .= ''.$langs->trans("PaidInvoices").': '; $html .= ''.$stats['invoice_count'].''; $html .= '
'; $html .= '
'; // End stats flex $html .= '
'; // End content // Rating badge $html .= '
'; $html .= '
'.$langs->trans("Rating").'
'; $html .= '
'.$this->getPaymentRatingText($stats, $langs).'
'; $html .= '
'; $html .= '
'; // End box $html .= '
'; // End fichecenter // Add CSS $html .= $this->getPaymentStatsCSS(); // Print directly since Dolibarr doesn't auto-print resprints for this hook print $html; $this->resprints = $html; return 0; } /** * Get payment statistics for a thirdparty * * @param int $socid Thirdparty ID * @return array Statistics array */ private function getPaymentStatistics($socid) { global $conf; $stats = array( 'invoice_count' => 0, 'avg_payment_days' => 0, 'avg_due_days' => 0, 'on_time_count' => 0, 'late_count' => 0, ); // Get all paid invoices for this customer with payment dates $sql = "SELECT f.rowid, f.datef as invoice_date, f.date_lim_reglement as due_date,"; $sql .= " (SELECT MAX(p.datep) FROM ".MAIN_DB_PREFIX."paiement_facture as pf"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."paiement as p ON p.rowid = pf.fk_paiement"; $sql .= " WHERE pf.fk_facture = f.rowid) as payment_date"; $sql .= " FROM ".MAIN_DB_PREFIX."facture as f"; $sql .= " WHERE f.fk_soc = ".((int) $socid); $sql .= " AND f.fk_statut = 2"; // Paid invoices only $sql .= " AND f.entity = ".((int) $conf->entity); $resql = $this->db->query($sql); if ($resql) { $totalPaymentDays = 0; $totalDueDays = 0; $count = 0; while ($obj = $this->db->fetch_object($resql)) { if ($obj->payment_date && $obj->invoice_date) { $invoiceDate = strtotime($obj->invoice_date); $paymentDate = strtotime($obj->payment_date); $dueDate = $obj->due_date ? strtotime($obj->due_date) : $invoiceDate; $paymentDays = ($paymentDate - $invoiceDate) / 86400; $dueDays = ($dueDate - $invoiceDate) / 86400; if ($paymentDays >= 0) { // Only count valid positive payment days $totalPaymentDays += $paymentDays; $totalDueDays += $dueDays; $count++; if ($paymentDate <= $dueDate) { $stats['on_time_count']++; } else { $stats['late_count']++; } } } } $stats['invoice_count'] = $count; if ($count > 0) { $stats['avg_payment_days'] = $totalPaymentDays / $count; $stats['avg_due_days'] = $totalDueDays / $count; } $this->db->free($resql); } return $stats; } /** * Get color class based on payment statistics * * @param array $stats Payment statistics * @return string CSS color class */ private function getPaymentColorClass($stats) { $diff = $stats['avg_payment_days'] - $stats['avg_due_days']; if ($diff <= -5) { return 'excellent'; // Pays well before due date } elseif ($diff <= 0) { return 'good'; // Pays on time or slightly early } elseif ($diff <= 7) { return 'warning'; // Slightly late } elseif ($diff <= 14) { return 'late'; // Late } else { return 'critical'; // Very late } } /** * Get icon based on payment statistics * * @param array $stats Payment statistics * @return string Icon HTML */ private function getPaymentIcon($stats) { $diff = $stats['avg_payment_days'] - $stats['avg_due_days']; if ($diff <= -5) { return ''; // Star } elseif ($diff <= 0) { return ''; // Checkmark } elseif ($diff <= 7) { return ''; // Warning } elseif ($diff <= 14) { return ''; // Clock } else { return ''; // X } } /** * Get rating text based on payment statistics * * @param array $stats Payment statistics * @param Translate $langs Translation object * @return string Rating text */ private function getPaymentRatingText($stats, $langs) { $diff = $stats['avg_payment_days'] - $stats['avg_due_days']; if ($diff <= -5) { return $langs->trans("PaymentExcellent"); } elseif ($diff <= 0) { return $langs->trans("PaymentGood"); } elseif ($diff <= 7) { return $langs->trans("PaymentWarning"); } elseif ($diff <= 14) { return $langs->trans("PaymentLate"); } else { return $langs->trans("PaymentCritical"); } } /** * Get CSS for payment statistics box * * @return string CSS HTML */ private function getPaymentStatsCSS() { return ' '; } }