295 lines
11 KiB
PHP
295 lines
11 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 as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
/**
|
|
* \file htdocs/custom/buchaltungswidget/core/boxes/box_ust_uebersicht.php
|
|
* \ingroup buchaltungswidget
|
|
* \brief Widget showing VAT (Umsatzsteuer) overview with quarterly data
|
|
*/
|
|
|
|
include_once DOL_DOCUMENT_ROOT.'/core/boxes/modules_boxes.php';
|
|
|
|
/**
|
|
* Class to manage the VAT overview widget
|
|
*/
|
|
class box_ust_uebersicht extends ModeleBoxes
|
|
{
|
|
public $boxcode = "ust_uebersicht";
|
|
public $boximg = "accountancy";
|
|
public $boxlabel = "UStUebersicht";
|
|
public $depends = array("facture", "fournisseur");
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param DoliDB $db Database handler
|
|
* @param string $param More parameters
|
|
*/
|
|
public function __construct($db, $param = '')
|
|
{
|
|
global $user;
|
|
$this->db = $db;
|
|
$this->hidden = !($user->hasRight('facture', 'lire') || $user->hasRight('fournisseur', 'facture', 'lire'));
|
|
}
|
|
|
|
/**
|
|
* Load data into info_box_contents array to show a widget
|
|
*
|
|
* @param int $max Maximum number of records to load
|
|
* @return void
|
|
*/
|
|
public function loadBox($max = 5)
|
|
{
|
|
global $conf, $langs, $user;
|
|
|
|
$langs->loadLangs(array("buchaltungswidget@buchaltungswidget", "bills", "compta"));
|
|
|
|
$this->info_box_head = array(
|
|
'text' => $langs->trans("UStUebersicht"),
|
|
'sublink' => dol_buildpath('/buchaltungswidget/ust_detail.php', 1),
|
|
'subpicto' => 'chart',
|
|
'subtext' => $langs->trans("ShowDetails"),
|
|
'limit' => 0,
|
|
'graph' => false,
|
|
'nbcol' => 6,
|
|
);
|
|
|
|
if (!$user->hasRight('facture', 'lire') && !$user->hasRight('fournisseur', 'facture', 'lire')) {
|
|
$this->info_box_contents[0][0] = array(
|
|
'td' => 'class="center"',
|
|
'text' => $langs->trans("ReadPermissionNotAllowed"),
|
|
);
|
|
return;
|
|
}
|
|
|
|
$currentYear = date('Y');
|
|
$lastYear = $currentYear - 1;
|
|
$currentQuarter = ceil(date('n') / 3);
|
|
|
|
// Get VAT data for both years
|
|
$vatDataCurrentYear = $this->getVatDataByQuarter($currentYear);
|
|
$vatDataLastYear = $this->getVatDataByQuarter($lastYear);
|
|
|
|
// Build the output
|
|
$this->info_box_contents = array();
|
|
$line = 0;
|
|
|
|
// Mini chart area
|
|
$chartId = 'ust_chart_'.uniqid();
|
|
$chartData = $this->prepareChartData($vatDataCurrentYear, $vatDataLastYear, $currentYear, $lastYear);
|
|
|
|
$this->info_box_contents[$line][] = array(
|
|
'td' => 'colspan="6" class="buchaltung-chart-container"',
|
|
'text' => '<canvas id="'.$chartId.'" height="120"></canvas>
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
if (typeof Chart !== "undefined") {
|
|
var ctx = document.getElementById("'.$chartId.'").getContext("2d");
|
|
new Chart(ctx, {
|
|
type: "bar",
|
|
data: {
|
|
labels: ["Q1", "Q2", "Q3", "Q4"],
|
|
datasets: [{
|
|
label: "'.$currentYear.' '.$langs->trans("VATBalance").'",
|
|
data: '.json_encode($chartData['current']).',
|
|
backgroundColor: '.json_encode($chartData['currentColors']).',
|
|
borderWidth: 1
|
|
}, {
|
|
label: "'.$lastYear.' '.$langs->trans("VATBalance").'",
|
|
data: '.json_encode($chartData['last']).',
|
|
backgroundColor: "rgba(108, 117, 125, 0.5)",
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: { legend: { display: true, position: "bottom", labels: { boxWidth: 12, font: { size: 10 }, color: "#444" } } },
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
ticks: { font: { size: 9 }, color: "#555" },
|
|
grid: { color: "rgba(255, 255, 255, 0.8)", lineWidth: 1 },
|
|
border: { color: "rgba(255, 255, 255, 1)" }
|
|
},
|
|
x: {
|
|
ticks: { font: { size: 10 }, color: "#555" },
|
|
grid: { color: "rgba(255, 255, 255, 0.6)" },
|
|
border: { color: "rgba(255, 255, 255, 1)" }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
</script>',
|
|
'asis' => 1,
|
|
);
|
|
$line++;
|
|
|
|
// VAT Table Header
|
|
$this->info_box_contents[$line][] = array('td' => 'class="buchaltung-header"', 'text' => $langs->trans("Year"));
|
|
for ($q = 1; $q <= 4; $q++) {
|
|
$class = ($q == $currentQuarter) ? 'buchaltung-header right buchaltung-current-quarter' : 'buchaltung-header right';
|
|
$this->info_box_contents[$line][] = array('td' => 'class="'.$class.'"', 'text' => 'Q'.$q);
|
|
}
|
|
$this->info_box_contents[$line][] = array('td' => 'class="buchaltung-header right"', 'text' => $langs->trans("Total"));
|
|
$line++;
|
|
|
|
// Current Year VAT Collected
|
|
$this->info_box_contents[$line][] = array('td' => 'class="buchaltung-label"', 'text' => $currentYear.' '.$langs->trans("VATCollected"));
|
|
$totalCollected = 0;
|
|
for ($q = 1; $q <= 4; $q++) {
|
|
$value = isset($vatDataCurrentYear['collected'][$q]) ? $vatDataCurrentYear['collected'][$q] : 0;
|
|
$totalCollected += $value;
|
|
$isFuture = ($q > $currentQuarter);
|
|
$displayValue = $isFuture ? 0 : $value;
|
|
$class = 'right'.($q == $currentQuarter ? ' buchaltung-current-quarter' : '').($isFuture ? ' buchaltung-future' : '');
|
|
$this->info_box_contents[$line][] = array('td' => 'class="'.$class.'"', 'text' => price($displayValue, 0, $langs, 1, 0, 0, $conf->currency));
|
|
}
|
|
$this->info_box_contents[$line][] = array('td' => 'class="right buchaltung-total"', 'text' => price($totalCollected, 0, $langs, 1, 0, 0, $conf->currency));
|
|
$line++;
|
|
|
|
// Current Year VAT Paid
|
|
$this->info_box_contents[$line][] = array('td' => 'class="buchaltung-label"', 'text' => $currentYear.' '.$langs->trans("VATPaid"));
|
|
$totalPaid = 0;
|
|
for ($q = 1; $q <= 4; $q++) {
|
|
$value = isset($vatDataCurrentYear['paid'][$q]) ? $vatDataCurrentYear['paid'][$q] : 0;
|
|
$totalPaid += $value;
|
|
$isFuture = ($q > $currentQuarter);
|
|
$displayValue = $isFuture ? 0 : $value;
|
|
$class = 'right'.($q == $currentQuarter ? ' buchaltung-current-quarter' : '').($isFuture ? ' buchaltung-future' : '');
|
|
$this->info_box_contents[$line][] = array('td' => 'class="'.$class.'"', 'text' => price($displayValue, 0, $langs, 1, 0, 0, $conf->currency));
|
|
}
|
|
$this->info_box_contents[$line][] = array('td' => 'class="right buchaltung-total"', 'text' => price($totalPaid, 0, $langs, 1, 0, 0, $conf->currency));
|
|
$line++;
|
|
|
|
// Current Year VAT Balance
|
|
$this->info_box_contents[$line][] = array('td' => 'class="buchaltung-label"', 'text' => '<strong>'.$currentYear.' '.$langs->trans("VATBalance").'</strong>', 'asis' => 1);
|
|
$totalBalance = 0;
|
|
for ($q = 1; $q <= 4; $q++) {
|
|
$collected = isset($vatDataCurrentYear['collected'][$q]) ? $vatDataCurrentYear['collected'][$q] : 0;
|
|
$paid = isset($vatDataCurrentYear['paid'][$q]) ? $vatDataCurrentYear['paid'][$q] : 0;
|
|
$balance = $collected - $paid;
|
|
$totalBalance += $balance;
|
|
$isFuture = ($q > $currentQuarter);
|
|
$displayBalance = $isFuture ? 0 : $balance;
|
|
$colorClass = (!$isFuture && $displayBalance != 0) ? ($displayBalance > 0 ? 'buchaltung-negative' : 'buchaltung-positive') : '';
|
|
$class = 'right '.$colorClass.($q == $currentQuarter ? ' buchaltung-current-quarter' : '').($isFuture ? ' buchaltung-future' : '');
|
|
$this->info_box_contents[$line][] = array('td' => 'class="'.$class.'"', 'text' => '<strong>'.price($displayBalance, 0, $langs, 1, 0, 0, $conf->currency).'</strong>', 'asis' => 1);
|
|
}
|
|
$colorClass = $totalBalance > 0 ? 'buchaltung-negative' : 'buchaltung-positive';
|
|
$this->info_box_contents[$line][] = array('td' => 'class="right buchaltung-total '.$colorClass.'"', 'text' => '<strong>'.price($totalBalance, 0, $langs, 1, 0, 0, $conf->currency).'</strong>', 'asis' => 1);
|
|
$line++;
|
|
|
|
// Separator
|
|
$this->info_box_contents[$line][] = array('td' => 'colspan="6" class="buchaltung-separator"', 'text' => '');
|
|
$line++;
|
|
|
|
// Last Year Summary Row
|
|
$totalCollectedLast = array_sum($vatDataLastYear['collected']);
|
|
$totalPaidLast = array_sum($vatDataLastYear['paid']);
|
|
$totalBalanceLast = $totalCollectedLast - $totalPaidLast;
|
|
$colorClass = $totalBalanceLast > 0 ? 'buchaltung-negative' : 'buchaltung-positive';
|
|
|
|
$this->info_box_contents[$line][] = array('td' => 'class="buchaltung-label buchaltung-lastyear" colspan="5"', 'text' => $lastYear.' '.$langs->trans("VATBalance").' ('.$langs->trans("Total").')');
|
|
$this->info_box_contents[$line][] = array('td' => 'class="right buchaltung-total buchaltung-lastyear '.$colorClass.'"', 'text' => '<strong>'.price($totalBalanceLast, 0, $langs, 1, 0, 0, $conf->currency).'</strong>', 'asis' => 1);
|
|
}
|
|
|
|
/**
|
|
* Prepare chart data
|
|
*/
|
|
private function prepareChartData($currentData, $lastData, $currentYear, $lastYear)
|
|
{
|
|
$currentQuarter = ceil(date('n') / 3);
|
|
$current = array();
|
|
$currentColors = array();
|
|
$last = array();
|
|
|
|
for ($q = 1; $q <= 4; $q++) {
|
|
$collected = isset($currentData['collected'][$q]) ? $currentData['collected'][$q] : 0;
|
|
$paid = isset($currentData['paid'][$q]) ? $currentData['paid'][$q] : 0;
|
|
$balance = ($q > $currentQuarter) ? 0 : ($collected - $paid);
|
|
$current[] = round($balance, 2);
|
|
|
|
if ($q > $currentQuarter) {
|
|
$currentColors[] = 'rgba(200, 200, 200, 0.3)';
|
|
} elseif ($balance > 0) {
|
|
$currentColors[] = 'rgba(220, 53, 69, 0.7)'; // Red - have to pay
|
|
} else {
|
|
$currentColors[] = 'rgba(40, 167, 69, 0.7)'; // Green - get back
|
|
}
|
|
|
|
$collectedLast = isset($lastData['collected'][$q]) ? $lastData['collected'][$q] : 0;
|
|
$paidLast = isset($lastData['paid'][$q]) ? $lastData['paid'][$q] : 0;
|
|
$last[] = round($collectedLast - $paidLast, 2);
|
|
}
|
|
|
|
return array('current' => $current, 'currentColors' => $currentColors, 'last' => $last);
|
|
}
|
|
|
|
/**
|
|
* Get VAT data grouped by quarter
|
|
*/
|
|
private function getVatDataByQuarter($year)
|
|
{
|
|
global $conf;
|
|
|
|
$result = array(
|
|
'collected' => array(1 => 0, 2 => 0, 3 => 0, 4 => 0),
|
|
'paid' => array(1 => 0, 2 => 0, 3 => 0, 4 => 0),
|
|
);
|
|
|
|
// VAT collected from customer invoices
|
|
$sql = "SELECT QUARTER(f.datef) as quarter, SUM(fd.total_tva) as tva_amount";
|
|
$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
|
|
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."facturedet as fd ON fd.fk_facture = f.rowid";
|
|
$sql .= " WHERE f.fk_statut > 0 AND f.entity = ".((int) $conf->entity);
|
|
$sql .= " AND YEAR(f.datef) = ".((int) $year);
|
|
$sql .= " GROUP BY QUARTER(f.datef)";
|
|
|
|
$resql = $this->db->query($sql);
|
|
if ($resql) {
|
|
while ($obj = $this->db->fetch_object($resql)) {
|
|
if ($obj->quarter >= 1 && $obj->quarter <= 4) {
|
|
$result['collected'][$obj->quarter] = (float) $obj->tva_amount;
|
|
}
|
|
}
|
|
$this->db->free($resql);
|
|
}
|
|
|
|
// VAT paid from supplier invoices
|
|
$sql = "SELECT QUARTER(f.datef) as quarter, SUM(fd.total_tva) as tva_amount";
|
|
$sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
|
|
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."facture_fourn_det as fd ON fd.fk_facture_fourn = f.rowid";
|
|
$sql .= " WHERE f.fk_statut > 0 AND f.entity = ".((int) $conf->entity);
|
|
$sql .= " AND YEAR(f.datef) = ".((int) $year);
|
|
$sql .= " GROUP BY QUARTER(f.datef)";
|
|
|
|
$resql = $this->db->query($sql);
|
|
if ($resql) {
|
|
while ($obj = $this->db->fetch_object($resql)) {
|
|
if ($obj->quarter >= 1 && $obj->quarter <= 4) {
|
|
$result['paid'][$obj->quarter] = (float) $obj->tva_amount;
|
|
}
|
|
}
|
|
$this->db->free($resql);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Method to show the widget
|
|
*/
|
|
public function showBox($head = null, $contents = null, $nooutput = 0)
|
|
{
|
|
return parent::showBox($this->info_box_head, $this->info_box_contents, $nooutput);
|
|
}
|
|
}
|