* * 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/invoice_category_stats.php * \ingroup buchaltungswidget * \brief Invoice statistics filtered by invoice category (Schlagwort/Kategorie Rechnung) */ // Load Dolibarr environment require '../../main.inc.php'; 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'; require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; // Security check if (!$user->hasRight('facture', 'lire')) { accessforbidden(); } $langs->loadLangs(array("buchaltungswidget@buchaltungswidget", "bills", "companies", "categories")); $WIDTH = DolGraph::getDefaultGraphSizeForStats('width'); $HEIGHT = DolGraph::getDefaultGraphSizeForStats('height'); // Get parameters $socid = GETPOSTINT('socid'); $userid = GETPOSTINT('userid'); $categ_invoice_id = GETPOST('categ_invoice_id', 'array'); $object_status = GETPOST('object_status', 'intcomma'); if ($user->socid > 0) { $socid = $user->socid; } $nowyear = dol_print_date(dol_now('gmt'), "%Y", 'gmt'); $year = GETPOST('year') > 0 ? GETPOSTINT('year') : $nowyear; $startyear = $year - (!getDolGlobalString('MAIN_STATS_GRAPHS_SHOW_N_YEARS') ? 2 : max(1, min(10, getDolGlobalString('MAIN_STATS_GRAPHS_SHOW_N_YEARS')))); $endyear = $year; /* * View */ $form = new Form($db); $formcompany = new FormCompany($db); $formother = new FormOther($db); $title = $langs->trans("InvoiceCategoryStatistics"); $dir = $conf->facture->dir_temp; llxHeader('', $title); print load_fiche_titre($title, '', 'bill'); dol_mkdir($dir); // Build WHERE clause for invoice category filter $sql_category_join = ''; $sql_category_where = ''; if (is_array($categ_invoice_id) && !empty($categ_invoice_id) && !in_array('0', $categ_invoice_id)) { // Sanitize category IDs as integers $categ_ids_sanitized = array_map('intval', $categ_invoice_id); $categ_ids_sanitized = array_filter($categ_ids_sanitized, function($v) { return $v > 0; }); if (!empty($categ_ids_sanitized)) { // Table is llx_categorie_invoice (not categorie_facture!) $sql_category_join = ' INNER JOIN '.MAIN_DB_PREFIX.'categorie_invoice as cf ON cf.fk_invoice = f.rowid'; $sql_category_where = ' AND cf.fk_categorie IN ('.implode(',', $categ_ids_sanitized).')'; } } // Get statistics data $data_nb = getInvoiceStatsByMonth($db, $conf, $startyear, $endyear, $socid, $userid, $object_status, $sql_category_join, $sql_category_where, 'nb'); $data_amount = getInvoiceStatsByMonth($db, $conf, $startyear, $endyear, $socid, $userid, $object_status, $sql_category_join, $sql_category_where, 'amount'); $data_avg = getInvoiceStatsByMonth($db, $conf, $startyear, $endyear, $socid, $userid, $object_status, $sql_category_join, $sql_category_where, 'avg'); $data_yearly = getInvoiceStatsByYear($db, $conf, $socid, $userid, $object_status, $sql_category_join, $sql_category_where); // Build graphs $filenamenb = $dir."/invoicescatnbinyear-".$year.".png"; $fileurlnb = DOL_URL_ROOT.'/viewimage.php?modulepart=billstats&file=invoicescatnbinyear-'.$year.'.png'; $px1 = new DolGraph(); $mesg = $px1->isGraphKo(); if (!$mesg && !empty($data_nb)) { $px1->SetData($data_nb); $legend = array(); for ($i = $startyear; $i <= $endyear; $i++) { $legend[] = $i; } $px1->SetLegend($legend); $px1->SetMaxValue($px1->GetCeilMaxValue()); $px1->SetWidth($WIDTH); $px1->SetHeight($HEIGHT); $px1->SetYLabel($langs->trans("NumberOfBills")); $px1->SetShading(3); $px1->SetHorizTickIncrement(1); $px1->mode = 'depth'; $px1->SetTitle($langs->trans("NumberOfBillsByMonth")); $px1->draw($filenamenb, $fileurlnb); } $filenameamount = $dir."/invoicescatamountinyear-".$year.".png"; $fileurlamount = DOL_URL_ROOT.'/viewimage.php?modulepart=billstats&file=invoicescatamountinyear-'.$year.'.png'; $px2 = new DolGraph(); if (!$mesg && !empty($data_amount)) { $px2->SetData($data_amount); $legend = array(); for ($i = $startyear; $i <= $endyear; $i++) { $legend[] = $i; } $px2->SetLegend($legend); $px2->SetMaxValue($px2->GetCeilMaxValue()); $px2->SetMinValue(min(0, $px2->GetFloorMinValue())); $px2->SetWidth($WIDTH); $px2->SetHeight($HEIGHT); $px2->SetYLabel($langs->trans("AmountOfBills")); $px2->SetShading(3); $px2->SetHorizTickIncrement(1); $px2->mode = 'depth'; $px2->SetTitle($langs->trans("AmountOfBillsByMonthHT")); $px2->draw($filenameamount, $fileurlamount); } $filename_avg = $dir."/invoicescatavginyear-".$year.".png"; $fileurl_avg = DOL_URL_ROOT.'/viewimage.php?modulepart=billstats&file=invoicescatavginyear-'.$year.'.png'; $px3 = new DolGraph(); if (!$mesg && !empty($data_avg)) { $px3->SetData($data_avg); $legend = array(); for ($i = $startyear; $i <= $endyear; $i++) { $legend[] = $i; } $px3->SetLegend($legend); $px3->SetYLabel($langs->trans("AmountAverage")); $px3->SetMaxValue($px3->GetCeilMaxValue()); $px3->SetMinValue((int) $px3->GetFloorMinValue()); $px3->SetWidth($WIDTH); $px3->SetHeight($HEIGHT); $px3->SetShading(3); $px3->SetHorizTickIncrement(1); $px3->mode = 'depth'; $px3->SetTitle($langs->trans("AmountAverage")); $px3->draw($filename_avg, $fileurl_avg); } // Get ALL available years from invoices (without category filter) $arrayyears = array(); $sql_years = "SELECT DISTINCT YEAR(f.datef) as year FROM ".MAIN_DB_PREFIX."facture as f"; $sql_years .= " WHERE f.entity = ".((int) $conf->entity); $sql_years .= " AND f.fk_statut > 0"; $sql_years .= " ORDER BY year DESC"; $resql_years = $db->query($sql_years); if ($resql_years) { while ($obj_year = $db->fetch_object($resql_years)) { if ($obj_year->year) { $arrayyears[$obj_year->year] = $obj_year->year; } } $db->free($resql_years); } if (!count($arrayyears)) { $arrayyears[$nowyear] = $nowyear; } print '
'; // Filter form print '
'; print ''; print ''; print ''; // Invoice Category - THE NEW FILTER print ''; // Company print ''; // User print ''; // Status print ''; // Year print ''; print ''; print '
'.$langs->trans("Filter").'
'.$langs->trans("Category").' '.$langs->trans("Invoice").''; $cate_arbo = $form->select_all_categories(Categorie::TYPE_INVOICE, '', 'parent', 0, 0, 1); print img_picto('', 'category', 'class="pictofixedwidth"'); print $form->multiselectarray('categ_invoice_id', $cate_arbo, $categ_invoice_id, 0, 0, 'widthcentpercentminusx maxwidth300'); print '
'.$langs->trans("ThirdParty").''; print img_picto('', 'company', 'class="pictofixedwidth"'); print $form->select_company($socid, 'socid', '(s.client:IN:1,2,3)', 1, 0, 0, array(), 0, 'widthcentpercentminusx maxwidth300'); print '
'.$langs->trans("CreatedBy").''; print img_picto('', 'user', 'class="pictofixedwidth"'); print $form->select_dolusers($userid ? $userid : -1, 'userid', 1, null, 0, '', '', '0', 0, 0, '', 0, '', 'widthcentpercentminusx maxwidth300'); print '
'.$langs->trans("Status").''; $liststatus = array( '0' => $langs->trans("BillStatusDraft"), '1' => $langs->trans("BillStatusNotPaid"), '2' => $langs->trans("BillStatusPaid"), '1,2' => $langs->trans("BillStatusNotPaid").' / '.$langs->trans("BillStatusPaid"), '3' => $langs->trans("BillStatusCanceled") ); print $form->selectarray('object_status', $liststatus, $object_status, 1); print '
'.$langs->trans("Year").''; if (!in_array($year, $arrayyears)) { $arrayyears[$year] = $year; } if (!in_array($nowyear, $arrayyears)) { $arrayyears[$nowyear] = $nowyear; } arsort($arrayyears); print $form->selectarray('year', $arrayyears, $year, 0, 0, 0, '', 0, 0, 0, '', 'width75'); print '
'; print '
'; print '

'; // Yearly table print '
'; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; $oldyear = 0; foreach ($data_yearly as $val) { $year_row = (int) $val['year']; while ($year_row && $oldyear > $year_row + 1) { $oldyear--; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; } $greennb = (empty($val['nb_diff']) || $val['nb_diff'] >= 0); $greentotal = (empty($val['total_diff']) || $val['total_diff'] >= 0); $greenavg = (empty($val['avg_diff']) || $val['avg_diff'] >= 0); print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; $oldyear = $year_row; } print '
'.$langs->trans("Year").''.$langs->trans("NumberOfBills").'%'.$langs->trans("AmountTotal").'%'.$langs->trans("AmountAverage").'%
'.$oldyear.'000
0 ? '&userid='.$userid : '').'">'.$year_row.''.$val['nb'].''.(!empty($val['nb_diff']) && $val['nb_diff'] < 0 ? '' : '+').round(!empty($val['nb_diff']) ? $val['nb_diff'] : 0).'%'.price(price2num($val['total'], 'MT'), 1).''.(!empty($val['total_diff']) && $val['total_diff'] < 0 ? '' : '+').round(!empty($val['total_diff']) ? $val['total_diff'] : 0).'%'.price(price2num($val['avg'], 'MT'), 1).''.(!empty($val['avg_diff']) && $val['avg_diff'] < 0 ? '' : '+').round(!empty($val['avg_diff']) ? $val['avg_diff'] : 0).'%
'; print '
'; print '
'; // Show graphs print '
'; if ($mesg) { print $mesg; } else { print $px1->show(); print "
\n"; print $px2->show(); print "
\n"; print $px3->show(); } print '
'; print '
'; print '
'; llxFooter(); $db->close(); /** * Get invoice statistics by month */ function getInvoiceStatsByMonth($db, $conf, $startyear, $endyear, $socid, $userid, $object_status, $sql_category_join, $sql_category_where, $mode = 'nb') { $data = array(); // Initialize data array with month labels $months = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'); for ($m = 0; $m < 12; $m++) { $data[$m] = array($months[$m]); for ($y = $startyear; $y <= $endyear; $y++) { $data[$m][] = 0; } } for ($year = $startyear; $year <= $endyear; $year++) { $yearIndex = $year - $startyear + 1; if ($mode == 'nb') { $sql = "SELECT MONTH(f.datef) as month, COUNT(DISTINCT f.rowid) as val"; } elseif ($mode == 'amount') { $sql = "SELECT MONTH(f.datef) as month, SUM(f.total_ht) as val"; } else { $sql = "SELECT MONTH(f.datef) as month, AVG(f.total_ht) as val"; } $sql .= " FROM ".MAIN_DB_PREFIX."facture as f"; $sql .= $sql_category_join; $sql .= " WHERE f.entity = ".((int) $conf->entity); $sql .= " AND YEAR(f.datef) = ".((int) $year); $sql .= " AND f.fk_statut > 0"; if ($socid > 0) { $sql .= " AND f.fk_soc = ".((int) $socid); } if ($userid > 0) { $sql .= " AND f.fk_user_author = ".((int) $userid); } if ($object_status != '' && $object_status >= 0) { $sql .= " AND f.fk_statut IN (".$db->sanitize($object_status).")"; } $sql .= $sql_category_where; $sql .= " GROUP BY MONTH(f.datef)"; $resql = $db->query($sql); if ($resql) { while ($obj = $db->fetch_object($resql)) { $monthIndex = $obj->month - 1; if ($monthIndex >= 0 && $monthIndex < 12) { $data[$monthIndex][$yearIndex] = round((float) $obj->val, 2); } } $db->free($resql); } } return $data; } /** * Get invoice statistics by year */ function getInvoiceStatsByYear($db, $conf, $socid, $userid, $object_status, $sql_category_join, $sql_category_where) { $data = array(); $sql = "SELECT YEAR(f.datef) as year, COUNT(DISTINCT f.rowid) as nb, SUM(f.total_ht) as total, AVG(f.total_ht) as avg"; $sql .= " FROM ".MAIN_DB_PREFIX."facture as f"; $sql .= $sql_category_join; $sql .= " WHERE f.entity = ".((int) $conf->entity); $sql .= " AND f.fk_statut > 0"; if ($socid > 0) { $sql .= " AND f.fk_soc = ".((int) $socid); } if ($userid > 0) { $sql .= " AND f.fk_user_author = ".((int) $userid); } if ($object_status != '' && $object_status >= 0) { $sql .= " AND f.fk_statut IN (".$db->sanitize($object_status).")"; } $sql .= $sql_category_where; $sql .= " GROUP BY YEAR(f.datef)"; $sql .= " ORDER BY year DESC"; $resql = $db->query($sql); if ($resql) { $prevnb = 0; $prevtotal = 0; $prevavg = 0; while ($obj = $db->fetch_object($resql)) { $row = 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); } return $data; }