v1.2: Bugfixes USt-Widget, Statusfilter und Kategoriestatistik

- USt-Widget: Spalte fd.total_tva -> fd.tva fuer Lieferantenrechnungen
  (facture_fourn_det nutzt 'tva' statt 'total_tva' wie facturedet)
- Stornierte Rechnungen (Status 3) aus allen Berechnungen ausgeschlossen
  (fk_statut > 0 -> fk_statut IN (1, 2) in 10 SQL-Queries)
- Kategoriestatistik: Jahresvergleich berechnet jetzt korrekt zum Vorjahr
- invoice_category_stats.php: Robuste Pfaderkennung statt hartem require
- Fehlende en_US Uebersetzungen fuer Kategoriestatistik ergaenzt
- Version 1.1 -> 1.2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-02-19 20:23:09 +01:00
parent 67776723a3
commit 00c4792c17
11 changed files with 106 additions and 42 deletions

View file

@ -1,5 +1,15 @@
# CHANGELOG MODULE BUCHALTUNGSWIDGET FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
## 1.2
- Bugfix: USt-Widget zeigte bezahlte USt (Vorsteuer) immer als 0 an
- Ursache: Falsche Spalte `total_tva` statt `tva` in Tabelle `facture_fourn_det`
- Bugfix: Stornierte Rechnungen (Status 3) wurden in allen Berechnungen mitgezaehlt
- Betrifft: USt, Gewinn/Verlust, Rentabilitaet (Widgets + Detailseiten)
- Bugfix: Jahresvergleich in Kategoriestatistik berechnete Prozente in falscher Richtung
- Bugfix: Robuste Pfaderkennung fuer invoice_category_stats.php
- Fehlende englische Uebersetzungen fuer Kategoriestatistik ergaenzt
## 1.1
- Neue Statistikseite: Rechnungsstatistik nach Kategorie

View file

@ -1,6 +1,6 @@
# BUCHHALTUNGS-WIDGET / ACCOUNTING WIDGETS FOR [DOLIBARR ERP & CRM](https://www.dolibarr.org)
**Version:** 1.1
**Version:** 1.2
**Compatibility:** Dolibarr 19.0+
**Author:** Eduard Wisch - Data IT Solution
**License:** GPL v3+
@ -157,6 +157,13 @@ The following options can be configured in the admin area:
## Changelog
### Version 1.2
- Fix: VAT widget showed paid VAT (input tax) always as 0 (wrong column name in supplier invoice detail table)
- Fix: Cancelled invoices (status 3) were included in all financial calculations
- Fix: Year comparison percentages in category statistics were calculated in wrong direction
- Fix: Robust path detection for invoice_category_stats.php
- Added missing English translations for category statistics
### Version 1.1
- New: Invoice Category Statistics page
- Filter invoices by category/tag

View file

@ -289,7 +289,7 @@ class box_gewinn_verlust extends ModeleBoxes
// Income from customer invoices
$sql = "SELECT MONTH(f.datef) as month, SUM(f.total_ht) as total";
$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
$sql .= " WHERE f.fk_statut > 0 AND f.entity = ".((int) $conf->entity);
$sql .= " WHERE f.fk_statut IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " GROUP BY MONTH(f.datef)";
@ -309,7 +309,7 @@ class box_gewinn_verlust extends ModeleBoxes
$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 .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = fd.fk_product";
$sql .= " WHERE f.fk_statut > 0 AND f.entity = ".((int) $conf->entity);
$sql .= " WHERE f.fk_statut IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
// Only include products (materials) - product_type 0 = product, 1 = service
// Also include invoice lines with product linked

View file

@ -302,7 +302,7 @@ class box_rentabilitaet extends ModeleBoxes
$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 .= " INNER JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = fd.fk_product";
$sql .= " WHERE f.fk_statut > 0 AND f.entity = ".((int) $conf->entity);
$sql .= " WHERE f.fk_statut IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
// Only products (type 0), not services (type 1)
// And only products that are meant for resale or customer projects
@ -322,7 +322,7 @@ class box_rentabilitaet extends ModeleBoxes
$sql = "SELECT MONTH(f.datef) as month, SUM(fd.total_ht) as total";
$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 .= " WHERE f.fk_statut IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " GROUP BY MONTH(f.datef)";

View file

@ -250,7 +250,7 @@ class box_ust_uebersicht extends ModeleBoxes
$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 .= " WHERE f.fk_statut IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " GROUP BY QUARTER(f.datef)";
@ -265,10 +265,11 @@ class box_ust_uebersicht extends ModeleBoxes
}
// VAT paid from supplier invoices
$sql = "SELECT QUARTER(f.datef) as quarter, SUM(fd.total_tva) as tva_amount";
// Hinweis: In facture_fourn_det heißt die USt-Spalte 'tva', nicht 'total_tva' wie bei facturedet
$sql = "SELECT QUARTER(f.datef) as quarter, SUM(fd.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 .= " WHERE f.fk_statut IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " GROUP BY QUARTER(f.datef)";

View file

@ -76,7 +76,7 @@ class modBuchaltungsWidget extends DolibarrModules
$this->editor_squarred_logo = ''; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@buchaltungswidget'
// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z'
$this->version = '1.1';
$this->version = '1.2';
// Url to the file with your last numberversion of this module
//$this->url_last_version = 'http://www.example.com/versionmodule.txt';

View file

@ -320,7 +320,7 @@ function getIncomeExpenseByMonth($db, $year)
// Income from customer invoices
$sql = "SELECT MONTH(f.datef) as month, SUM(f.total_ht) as total";
$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
$sql .= " WHERE f.fk_statut > 0 AND f.entity = ".((int) $conf->entity);
$sql .= " WHERE f.fk_statut IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " GROUP BY MONTH(f.datef)";
@ -337,7 +337,7 @@ function getIncomeExpenseByMonth($db, $year)
$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 .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = fd.fk_product";
$sql .= " WHERE f.fk_statut > 0 AND f.entity = ".((int) $conf->entity);
$sql .= " WHERE f.fk_statut IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " AND (fd.fk_product IS NOT NULL AND fd.fk_product > 0)";
$sql .= " AND (p.fk_product_type = 0 OR fd.product_type = 0)";

83
invoice_category_stats.php Normal file → Executable file
View file

@ -14,7 +14,37 @@
*/
// Load Dolibarr environment
require '../../main.inc.php';
$res = 0;
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
}
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
$tmp2 = realpath(__FILE__);
$i = strlen($tmp) - 1;
$j = strlen($tmp2) - 1;
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
$i--;
$j--;
}
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
$res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
}
if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) {
$res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
}
if (!$res && file_exists("../main.inc.php")) {
$res = @include "../main.inc.php";
}
if (!$res && file_exists("../../main.inc.php")) {
$res = @include "../../main.inc.php";
}
if (!$res && file_exists("../../../main.inc.php")) {
$res = @include "../../../main.inc.php";
}
if (!$res) {
die("Include of main fails");
}
require_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
@ -382,39 +412,40 @@ function getInvoiceStatsByYear($db, $conf, $socid, $userid, $object_status, $sql
$resql = $db->query($sql);
if ($resql) {
$prevnb = 0;
$prevtotal = 0;
$prevavg = 0;
// Zuerst alle Zeilen einlesen (absteigend sortiert)
$rows = array();
while ($obj = $db->fetch_object($resql)) {
$row = array(
$rows[] = array(
'year' => $obj->year,
'nb' => (int) $obj->nb,
'total' => (float) $obj->total,
'avg' => (float) $obj->avg,
'nb_diff' => 0,
'total_diff' => 0,
'avg_diff' => 0,
);
// Calculate percentage differences
if ($prevnb > 0) {
$row['nb_diff'] = (($obj->nb - $prevnb) / $prevnb) * 100;
}
if ($prevtotal > 0) {
$row['total_diff'] = (($obj->total - $prevtotal) / $prevtotal) * 100;
}
if ($prevavg > 0) {
$row['avg_diff'] = (($obj->avg - $prevavg) / $prevavg) * 100;
}
$data[] = $row;
$prevnb = $obj->nb;
$prevtotal = $obj->total;
$prevavg = $obj->avg;
}
$db->free($resql);
// Prozentuale Veraenderung zum Vorjahr berechnen (jedes Jahr mit dem aelteren vergleichen)
for ($i = 0; $i < count($rows); $i++) {
$rows[$i]['nb_diff'] = 0;
$rows[$i]['total_diff'] = 0;
$rows[$i]['avg_diff'] = 0;
// Das naechste Element im Array ist das aeltere Jahr (DESC-Sortierung)
if (isset($rows[$i + 1])) {
$prev = $rows[$i + 1];
if ($prev['nb'] > 0) {
$rows[$i]['nb_diff'] = (($rows[$i]['nb'] - $prev['nb']) / $prev['nb']) * 100;
}
if ($prev['total'] > 0) {
$rows[$i]['total_diff'] = (($rows[$i]['total'] - $prev['total']) / $prev['total']) * 100;
}
if ($prev['avg'] > 0) {
$rows[$i]['avg_diff'] = (($rows[$i]['avg'] - $prev['avg']) / $prev['avg']) * 100;
}
}
}
$data = $rows;
}
return $data;

View file

@ -122,6 +122,20 @@ PaymentWarning = Slow Payer
PaymentLate = Late
PaymentCritical = Critical
#
# Invoice Category Statistics
#
StatistikenNachKategorie = Statistics by Category
NachRechnungskategorie = By Invoice Category
Statistik = Statistics
InvoiceCategoryStatistics = Invoice Statistics by Category
SelectInvoiceCategory = Select Invoice Category
NoInvoiceCategoriesFound = No invoice categories found
AllCategories = All Categories
CategoryFilter = Category Filter
InvoicesInCategory = Invoices in this Category
AmountInCategory = Amount in this Category
#
# Months
#

View file

@ -340,7 +340,7 @@ function getProfitabilityByMonth($db, $year)
$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 .= " INNER JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = fd.fk_product";
$sql .= " WHERE f.fk_statut > 0 AND f.entity = ".((int) $conf->entity);
$sql .= " WHERE f.fk_statut IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " AND p.fk_product_type = 0";
$sql .= " AND (p.tobuy = 1 OR p.tosell = 1)";
@ -358,7 +358,7 @@ function getProfitabilityByMonth($db, $year)
$sql = "SELECT MONTH(f.datef) as month, SUM(fd.total_ht) as total";
$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 .= " WHERE f.fk_statut IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " GROUP BY MONTH(f.datef)";

View file

@ -304,7 +304,7 @@ function getVatData($db, $year, $mode = 'quarterly')
$sql = "SELECT ".$groupBy." as period, 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 .= " WHERE f.fk_statut IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " GROUP BY ".$groupBy;
@ -320,10 +320,11 @@ function getVatData($db, $year, $mode = 'quarterly')
}
// VAT paid
$sql = "SELECT ".$groupBy." as period, SUM(fd.total_tva) as tva_amount";
// Hinweis: In facture_fourn_det heißt die USt-Spalte 'tva', nicht 'total_tva' wie bei facturedet
$sql = "SELECT ".$groupBy." as period, SUM(fd.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 .= " WHERE f.fk_statut IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " GROUP BY ".$groupBy;