feat: Präzise Berechnung über Buchungskonten (SKR03/SKR04)

- Gewinn/Verlust: Kontenklasse 8xxx (Erlöse) minus 3xxx (Wareneinsatz)
- Rentabilität: Kontenklasse 8xxx minus 3xxx + 4xxx (alle Kosten inkl. Betriebskosten)
- Automatischer Fallback auf Rechnungsdaten wenn keine Buchungen vorhanden
- Hilfe-Icons mit Tooltips bei allen Widgets
- Dynamisches Chart.js-Laden (Charts funktionieren jetzt auch auf Dashboard)
- README auf Version 1.3 aktualisiert

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-02-23 08:00:50 +01:00
parent 00c4792c17
commit 927ed2ec07
5 changed files with 303 additions and 108 deletions

View file

@ -1,6 +1,6 @@
# BUCHHALTUNGS-WIDGET / ACCOUNTING WIDGETS FOR [DOLIBARR ERP & CRM](https://www.dolibarr.org)
**Version:** 1.2
**Version:** 1.3
**Compatibility:** Dolibarr 19.0+
**Author:** Eduard Wisch - Data IT Solution
**License:** GPL v3+
@ -21,23 +21,33 @@ Das Buchhaltungs-Widget Modul erweitert Dolibarr um drei leistungsstarke Dashboa
- Aktuelles Quartal hervorgehoben
- Farbcodierung: Rot = Zahllast, Gruen = Erstattung
- Detailseite mit monatlicher/quartalsweiser Ansicht
- Hilfe-Icon mit Erklaerung der Berechnung
#### 2. Gewinn/Verlust
#### 2. Gewinn/Verlust (Rohertrag)
- Kumulierter Gewinn/Verlust im Jahresverlauf
- Beruecksichtigt nur kundenbezogene Materialkosten
- Keine Betriebskosten (Miete, Nebenkosten etc.)
- Berechnung ueber Buchungskonten (wenn vorhanden):
- Einnahmen: Kontenklasse 8xxx (Erloese)
- Materialkosten: Kontenklasse 3xxx (Wareneinsatz)
- Keine Betriebskosten (4xxx) - nur Rohertrag
- Fallback auf Rechnungsdaten wenn keine Buchungen vorhanden
- Schaetzung der Einkommensteuer
- Farbige Linie: Gruen = Gewinn, Rot = Verlust
- Hilfe-Icon mit Erklaerung der Berechnung
#### 3. Rentabilitaet
- Vergleich: Materialeinkauf vs. Rechnungsstellung
#### 3. Rentabilitaet (Echte Rentabilitaet)
- Zeigt echte Rentabilitaet inkl. ALLER Kosten
- Berechnung ueber Buchungskonten (wenn vorhanden):
- Einnahmen: Kontenklasse 8xxx (Erloese)
- Alle Ausgaben: Kontenklasse 3xxx + 4xxx (Wareneinsatz + Betriebskosten)
- Fallback auf Rechnungsdaten wenn keine Buchungen vorhanden
- Gewinnmarge in Prozent
- Produktivitaetsbewertung mit 5 Stufen:
- Ausgezeichnet (>50%)
- Gut (30-50%)
- Durchschnittlich (15-30%)
- Niedrig (0-15%)
- Ausgezeichnet (>100%)
- Gut (50-100%)
- Durchschnittlich (20-50%)
- Niedrig (0-20%)
- Kritisch (<0%)
- Hilfe-Icon mit Erklaerung der Berechnung
### Zahlungsstatistik (Kundenkarte)
@ -95,23 +105,33 @@ The Accounting Widgets module extends Dolibarr with three powerful dashboard wid
- Current quarter highlighted
- Color coding: Red = to pay, Green = refund
- Detail page with monthly/quarterly view
- Help icon explaining calculation
#### 2. Profit/Loss
#### 2. Profit/Loss (Gross Margin)
- Cumulative profit/loss throughout the year
- Only considers customer-related material costs
- Excludes operating costs (rent, utilities, etc.)
- Calculation via accounting accounts (if available):
- Income: Account class 8xxx (Revenue)
- Material costs: Account class 3xxx (Cost of goods)
- No operating costs (4xxx) - gross margin only
- Fallback to invoice data if no bookings exist
- Income tax estimation
- Colored line: Green = profit, Red = loss
- Help icon explaining calculation
#### 3. Profitability
- Comparison: Material purchases vs. invoiced amounts
#### 3. Profitability (Real Profitability)
- Shows real profitability including ALL costs
- Calculation via accounting accounts (if available):
- Income: Account class 8xxx (Revenue)
- All expenses: Account class 3xxx + 4xxx (Cost of goods + Operating costs)
- Fallback to invoice data if no bookings exist
- Profit margin percentage
- Productivity rating with 5 levels:
- Excellent (>50%)
- Good (30-50%)
- Average (15-30%)
- Low (0-15%)
- Excellent (>100%)
- Good (50-100%)
- Average (20-50%)
- Low (0-20%)
- Critical (<0%)
- Help icon explaining calculation
### Payment Statistics (Customer Card)
@ -157,6 +177,15 @@ The following options can be configured in the admin area:
## Changelog
### Version 1.3
- Neu: Praezise Berechnung ueber Buchungskonten (SKR03/SKR04)
- Gewinn/Verlust: Kontenklasse 8xxx (Erloese) minus 3xxx (Wareneinsatz)
- Rentabilitaet: Kontenklasse 8xxx minus 3xxx + 4xxx (alle Kosten)
- Neu: Automatischer Fallback auf Rechnungsdaten wenn keine Buchungen vorhanden
- Neu: Hilfe-Icons mit Tooltips bei allen Widgets
- Neu: Dynamisches Chart.js-Laden (Charts funktionieren jetzt auch auf Dashboard ohne vorheriges Laden)
- Fix: Charts wurden nicht angezeigt wenn Chart.js nicht geladen war
### 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

View file

@ -44,8 +44,9 @@ class box_gewinn_verlust extends ModeleBoxes
$langs->loadLangs(array("buchaltungswidget@buchaltungswidget", "bills", "compta"));
$helpText = $langs->trans("GewinnVerlustHelpText");
$this->info_box_head = array(
'text' => $langs->trans("GewinnVerlust"),
'text' => $langs->trans("GewinnVerlust").' <span class="classfortooltip" title="'.dol_escape_htmltag($helpText).'" style="cursor:help;">'.img_picto($helpText, 'help', 'class="opacitymedium"').'</span>',
'sublink' => dol_buildpath('/buchaltungswidget/gewinn_detail.php', 1),
'subpicto' => 'chart',
'subtext' => $langs->trans("ShowDetails"),
@ -91,12 +92,13 @@ class box_gewinn_verlust extends ModeleBoxes
'td' => 'colspan="4" class="buchaltung-chart-container"',
'text' => '<canvas id="'.$chartId.'" height="140"></canvas>
<script>
document.addEventListener("DOMContentLoaded", function() {
if (typeof Chart !== "undefined") {
var ctx = document.getElementById("'.$chartId.'").getContext("2d");
(function() {
function initGewinnChart() {
var ctx = document.getElementById("'.$chartId.'");
if (!ctx) return;
var profitData = '.json_encode($chartData['currentProfit']).';
new Chart(ctx, {
new Chart(ctx.getContext("2d"), {
type: "line",
data: {
labels: '.json_encode($chartData['labels']).',
@ -151,7 +153,22 @@ class box_gewinn_verlust extends ModeleBoxes
}
});
}
});
function loadChartAndInit() {
if (typeof Chart !== "undefined") {
initGewinnChart();
} else {
var script = document.createElement("script");
script.src = "'.DOL_URL_ROOT.'/includes/nnnick/chartjs/dist/chart.min.js";
script.onload = initGewinnChart;
document.head.appendChild(script);
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", loadChartAndInit);
} else {
loadChartAndInit();
}
})();
</script>',
'asis' => 1,
);
@ -274,8 +291,10 @@ class box_gewinn_verlust extends ModeleBoxes
/**
* Get income and customer-related expenses by month
* IMPORTANT: Only includes costs related to customer projects (materials for customers),
* NOT company overhead costs
* Uses accounting accounts (Buchungskonten) for precise filtering:
* - Income: Account 8xxx (Erlöse)
* - Material costs: Account 3xxx (Wareneinsatz/Materialkosten)
* Excludes: Operating costs (4xxx), overhead, tools, etc.
*/
private function getIncomeExpenseByMonth($year)
{
@ -286,48 +305,102 @@ class box_gewinn_verlust extends ModeleBoxes
'customer_expenses' => array_fill(1, 12, 0),
);
// 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 IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " GROUP BY MONTH(f.datef)";
// Check if accounting module is active and has data
$useAccounting = $this->hasAccountingData($year);
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$result['income'][$obj->month] = (float) $obj->total;
if ($useAccounting) {
// Income from accounting: Account 8xxx (Erlöse)
$sql = "SELECT MONTH(b.doc_date) as month, ABS(SUM(CASE WHEN b.sens = 'C' THEN b.montant ELSE -b.montant END)) as total";
$sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as b";
$sql .= " WHERE b.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(b.doc_date) = ".((int) $year);
$sql .= " AND b.numero_compte LIKE '8%'"; // Erlöskonten
$sql .= " GROUP BY MONTH(b.doc_date)";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$result['income'][$obj->month] = (float) $obj->total;
}
$this->db->free($resql);
}
$this->db->free($resql);
}
// Customer-related expenses only:
// - Products (materials) for customers (product_type = 0)
// - Services directly for customers
// Exclude: Company overhead, rent, utilities, etc.
$sql = "SELECT MONTH(f.datef) as month, SUM(fd.total_ht) as total";
$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 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
$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)"; // Materials only
$sql .= " GROUP BY MONTH(f.datef)";
// Material costs from accounting: Account 3xxx (Wareneinsatz)
$sql = "SELECT MONTH(b.doc_date) as month, SUM(CASE WHEN b.sens = 'D' THEN b.montant ELSE -b.montant END) as total";
$sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as b";
$sql .= " WHERE b.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(b.doc_date) = ".((int) $year);
$sql .= " AND b.numero_compte LIKE '3%'"; // Wareneinsatz/Materialkosten
$sql .= " GROUP BY MONTH(b.doc_date)";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$result['customer_expenses'][$obj->month] = (float) $obj->total;
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$result['customer_expenses'][$obj->month] = (float) $obj->total;
}
$this->db->free($resql);
}
} else {
// Fallback: Use invoice data if no accounting entries exist
// 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 IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " GROUP BY MONTH(f.datef)";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$result['income'][$obj->month] = (float) $obj->total;
}
$this->db->free($resql);
}
// Material costs from supplier invoices (products only)
$sql = "SELECT MONTH(f.datef) as month, SUM(fd.total_ht) as total";
$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 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)";
$sql .= " GROUP BY MONTH(f.datef)";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$result['customer_expenses'][$obj->month] = (float) $obj->total;
}
$this->db->free($resql);
}
$this->db->free($resql);
}
return $result;
}
/**
* Check if accounting data exists for the given year
*/
private function hasAccountingData($year)
{
global $conf;
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."accounting_bookkeeping";
$sql .= " WHERE entity = ".((int) $conf->entity);
$sql .= " AND YEAR(doc_date) = ".((int) $year);
$sql .= " LIMIT 1";
$resql = $this->db->query($sql);
if ($resql) {
$obj = $this->db->fetch_object($resql);
$this->db->free($resql);
return ($obj->cnt > 0);
}
return false;
}
/**
* Calculate statistical projection for next year based on trends
*/

View file

@ -44,8 +44,9 @@ class box_rentabilitaet extends ModeleBoxes
$langs->loadLangs(array("buchaltungswidget@buchaltungswidget", "bills", "compta"));
$helpText = $langs->trans("RentabilitaetHelpText");
$this->info_box_head = array(
'text' => $langs->trans("Rentabilitaet"),
'text' => $langs->trans("Rentabilitaet").' <span class="classfortooltip" title="'.dol_escape_htmltag($helpText).'" style="cursor:help;">'.img_picto($helpText, 'help', 'class="opacitymedium"').'</span>',
'sublink' => dol_buildpath('/buchaltungswidget/rentabilitaet_detail.php', 1),
'subpicto' => 'chart',
'subtext' => $langs->trans("ShowDetails"),
@ -86,10 +87,11 @@ class box_rentabilitaet extends ModeleBoxes
'td' => 'colspan="4" class="buchaltung-chart-container"',
'text' => '<canvas id="'.$chartId.'" height="140"></canvas>
<script>
document.addEventListener("DOMContentLoaded", function() {
if (typeof Chart !== "undefined") {
var ctx = document.getElementById("'.$chartId.'").getContext("2d");
new Chart(ctx, {
(function() {
function initRentabilitaetChart() {
var ctx = document.getElementById("'.$chartId.'");
if (!ctx) return;
new Chart(ctx.getContext("2d"), {
type: "bar",
data: {
labels: '.json_encode($chartData['labels']).',
@ -104,12 +106,12 @@ class box_rentabilitaet extends ModeleBoxes
pointRadius: 4,
pointBackgroundColor: '.json_encode($chartData['marginColors']).'
}, {
label: "'.$langs->trans("MaterialsPurchased").'",
label: "'.$langs->trans("AllExpenses").'",
data: '.json_encode($chartData['purchased']).',
backgroundColor: "rgba(220, 53, 69, 0.6)",
yAxisID: "y"
}, {
label: "'.$langs->trans("MaterialsServicesInvoiced").'",
label: "'.$langs->trans("Income").'",
data: '.json_encode($chartData['invoiced']).',
backgroundColor: "rgba(0, 123, 255, 0.6)",
yAxisID: "y"
@ -157,7 +159,22 @@ class box_rentabilitaet extends ModeleBoxes
}
});
}
});
function loadChartAndInit() {
if (typeof Chart !== "undefined") {
initRentabilitaetChart();
} else {
var script = document.createElement("script");
script.src = "'.DOL_URL_ROOT.'/includes/nnnick/chartjs/dist/chart.min.js";
script.onload = initRentabilitaetChart;
document.head.appendChild(script);
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", loadChartAndInit);
} else {
loadChartAndInit();
}
})();
</script>',
'asis' => 1,
);
@ -170,12 +187,12 @@ class box_rentabilitaet extends ModeleBoxes
$this->info_box_contents[$line][] = array('td' => 'class="buchaltung-header right buchaltung-future"', 'text' => $nextYear.' *');
$line++;
// Materials purchased (only for customers!)
// All expenses (materials + operating costs)
$purchasedLast = array_sum($dataLastYear['purchased']);
$purchasedCurrent = array_sum(array_slice($dataCurrentYear['purchased'], 0, $currentMonth, true));
$purchasedProjection = $projectionNextYear['purchased'];
$this->info_box_contents[$line][] = array('td' => 'class="buchaltung-label"', 'text' => $langs->trans("MaterialsPurchasedForCustomers"));
$this->info_box_contents[$line][] = array('td' => 'class="buchaltung-label"', 'text' => $langs->trans("AllExpenses"));
$this->info_box_contents[$line][] = array('td' => 'class="right buchaltung-lastyear"', 'text' => price($purchasedLast, 0, $langs, 1, 0, 0, $conf->currency));
$this->info_box_contents[$line][] = array('td' => 'class="right"', 'text' => price($purchasedCurrent, 0, $langs, 1, 0, 0, $conf->currency));
$this->info_box_contents[$line][] = array('td' => 'class="right buchaltung-future"', 'text' => price($purchasedProjection, 0, $langs, 1, 0, 0, $conf->currency));
@ -238,7 +255,7 @@ class box_rentabilitaet extends ModeleBoxes
// Footer note
$this->info_box_contents[$line][] = array(
'td' => 'colspan="4" class="buchaltung-footnote"',
'text' => '<small>* '.$langs->trans("StatisticalProjection").' | '.$langs->trans("OnlyCustomerMaterials").'</small>',
'text' => '<small>* '.$langs->trans("StatisticalProjection").' | '.$langs->trans("AllCostsIncluded").'</small>',
'asis' => 1,
);
}
@ -285,7 +302,10 @@ class box_rentabilitaet extends ModeleBoxes
/**
* Get profitability data by month
* IMPORTANT: Only materials purchased FOR CUSTOMERS, not general company expenses
* Uses accounting accounts for precise filtering:
* - Income: Account 8xxx (Erlöse)
* - ALL expenses: Account 3xxx (Wareneinsatz) + 4xxx (Betriebskosten)
* This shows REAL profitability including all overhead costs
*/
private function getProfitabilityByMonth($year)
{
@ -296,47 +316,98 @@ class box_rentabilitaet extends ModeleBoxes
'invoiced' => array_fill(1, 12, 0),
);
// Materials purchased FOR CUSTOMERS only (products, not services)
// This should be materials that are resold or used in customer projects
$sql = "SELECT MONTH(f.datef) as month, SUM(fd.total_ht) as total";
$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 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
$sql .= " AND p.fk_product_type = 0"; // Products only
$sql .= " AND (p.tobuy = 1 OR p.tosell = 1)"; // Products that are bought/sold
$sql .= " GROUP BY MONTH(f.datef)";
// Check if accounting module is active and has data
$useAccounting = $this->hasAccountingData($year);
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$result['purchased'][$obj->month] = (float) $obj->total;
if ($useAccounting) {
// Income from accounting: Account 8xxx (Erlöse)
$sql = "SELECT MONTH(b.doc_date) as month, ABS(SUM(CASE WHEN b.sens = 'C' THEN b.montant ELSE -b.montant END)) as total";
$sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as b";
$sql .= " WHERE b.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(b.doc_date) = ".((int) $year);
$sql .= " AND b.numero_compte LIKE '8%'"; // Erlöskonten
$sql .= " GROUP BY MONTH(b.doc_date)";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$result['invoiced'][$obj->month] = (float) $obj->total;
}
$this->db->free($resql);
}
$this->db->free($resql);
}
// All materials and services invoiced to customers
$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 IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " GROUP BY MONTH(f.datef)";
// ALL expenses from accounting: Account 3xxx + 4xxx
$sql = "SELECT MONTH(b.doc_date) as month, SUM(CASE WHEN b.sens = 'D' THEN b.montant ELSE -b.montant END) as total";
$sql .= " FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as b";
$sql .= " WHERE b.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(b.doc_date) = ".((int) $year);
$sql .= " AND (b.numero_compte LIKE '3%' OR b.numero_compte LIKE '4%')"; // Wareneinsatz + Betriebskosten
$sql .= " GROUP BY MONTH(b.doc_date)";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$result['invoiced'][$obj->month] = (float) $obj->total;
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$result['purchased'][$obj->month] = (float) $obj->total;
}
$this->db->free($resql);
}
} else {
// Fallback: Use invoice data if no accounting entries exist
// ALL supplier invoices (all expenses)
$sql = "SELECT MONTH(f.datef) as month, SUM(f.total_ht) as total";
$sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
$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)";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$result['purchased'][$obj->month] = (float) $obj->total;
}
$this->db->free($resql);
}
// All customer invoices (income)
$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 IN (1, 2) AND f.entity = ".((int) $conf->entity);
$sql .= " AND YEAR(f.datef) = ".((int) $year);
$sql .= " GROUP BY MONTH(f.datef)";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$result['invoiced'][$obj->month] = (float) $obj->total;
}
$this->db->free($resql);
}
$this->db->free($resql);
}
return $result;
}
/**
* Check if accounting data exists for the given year
*/
private function hasAccountingData($year)
{
global $conf;
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."accounting_bookkeeping";
$sql .= " WHERE entity = ".((int) $conf->entity);
$sql .= " AND YEAR(doc_date) = ".((int) $year);
$sql .= " LIMIT 1";
$resql = $this->db->query($sql);
if ($resql) {
$obj = $this->db->fetch_object($resql);
$this->db->free($resql);
return ($obj->cnt > 0);
}
return false;
}
/**
* Calculate statistical projection for next year
*/

View file

@ -50,8 +50,9 @@ class box_ust_uebersicht extends ModeleBoxes
$langs->loadLangs(array("buchaltungswidget@buchaltungswidget", "bills", "compta"));
$helpText = $langs->trans("UStUebersichtHelpText");
$this->info_box_head = array(
'text' => $langs->trans("UStUebersicht"),
'text' => $langs->trans("UStUebersicht").' <span class="classfortooltip" title="'.dol_escape_htmltag($helpText).'" style="cursor:help;">'.img_picto($helpText, 'help', 'class="opacitymedium"').'</span>',
'sublink' => dol_buildpath('/buchaltungswidget/ust_detail.php', 1),
'subpicto' => 'chart',
'subtext' => $langs->trans("ShowDetails"),
@ -88,10 +89,11 @@ class box_ust_uebersicht extends ModeleBoxes
'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, {
(function() {
function initUstChart() {
var ctx = document.getElementById("'.$chartId.'");
if (!ctx) return;
new Chart(ctx.getContext("2d"), {
type: "bar",
data: {
labels: ["Q1", "Q2", "Q3", "Q4"],
@ -127,7 +129,22 @@ class box_ust_uebersicht extends ModeleBoxes
}
});
}
});
function loadChartAndInit() {
if (typeof Chart !== "undefined") {
initUstChart();
} else {
var script = document.createElement("script");
script.src = "'.DOL_URL_ROOT.'/includes/nnnick/chartjs/dist/chart.min.js";
script.onload = initUstChart;
document.head.appendChild(script);
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", loadChartAndInit);
} else {
loadChartAndInit();
}
})();
</script>',
'asis' => 1,
);

View file

@ -45,6 +45,7 @@ MyWidgetDescription = Meine Widget Beschreibung
# USt (VAT) Widget
#
UStUebersicht = Umsatzsteuer-Uebersicht
UStUebersichtHelpText = Zeigt die Umsatzsteuer-Zahllast pro Quartal. Berechnung: Eingenommene USt (Kundenrechnungen) minus gezahlte Vorsteuer (Lieferantenrechnungen). Rot = Zahllast ans Finanzamt, Gruen = Erstattung vom Finanzamt.
VATOverview = Umsatzsteuer-Uebersicht
VATCollected = Eingenommene USt
VATPaid = Gezahlte VSt (Vorsteuer)
@ -62,6 +63,7 @@ VATRefundLegend = Gruen = Erstattung
# Gewinn/Verlust Widget
#
GewinnVerlust = Gewinn / Verlust
GewinnVerlustHelpText = Zeigt den Rohertrag: Einnahmen (Erloeskonten 8xxx) minus Materialkosten (Wareneinsatz Konten 3xxx). Betriebskosten wie Werkzeuge, Fahrzeug, Buero sind NICHT enthalten. Fuer die echte Rentabilitaet inkl. aller Kosten siehe das Rentabilitaets-Widget.
IncomeExpenseOverview = Einnahmen / Ausgaben
Income = Einnahmen
Expenses = Ausgaben
@ -80,10 +82,13 @@ Note = Hinweis
# Rentabilitaet Widget
#
Rentabilitaet = Rentabilitaet
RentabilitaetHelpText = Zeigt die echte Rentabilitaet inkl. ALLER Kosten: Einnahmen (8xxx) minus Wareneinsatz (3xxx) UND Betriebskosten (4xxx wie Werkzeuge, Fahrzeug, Buero, etc.). Die Gewinnmarge zeigt das Verhaeltnis von Gewinn zu Gesamtkosten in Prozent.
ProfitabilityAnalysis = Rentabilitaetsanalyse
MaterialsPurchased = Eingekaufte Materialien
MaterialsPurchasedForCustomers = Eingekaufte Materialien (fuer Kunden)
MaterialsServicesInvoiced = In Rechnung gestellt (Leistungen & Material)
AllExpenses = Alle Ausgaben
AllCostsIncluded = Alle Kosten inkl. Betriebsausgaben
ProfitMargin = Gewinnmarge
GrossProfit = Rohertrag
ProfitMarginTrend = Gewinnmarge-Trend