From b8627e312a82394851e527ee7ec919bc5a2e032b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Sat, 4 Apr 2026 12:54:03 +0200 Subject: [PATCH] WIP Graph/stats view --- htdocs/langs/en_US/modulebuilder.lang | 4 +- htdocs/modulebuilder/index.php | 2 +- .../modulebuilder/template/myobject_list.php | 1 + .../template/stats/myobject_index.php | 456 ++++++++++++++++++ 4 files changed, 460 insertions(+), 3 deletions(-) create mode 100644 htdocs/modulebuilder/template/stats/myobject_index.php diff --git a/htdocs/langs/en_US/modulebuilder.lang b/htdocs/langs/en_US/modulebuilder.lang index a1fffa6199e..6e5f692ae18 100644 --- a/htdocs/langs/en_US/modulebuilder.lang +++ b/htdocs/langs/en_US/modulebuilder.lang @@ -140,8 +140,8 @@ UseSpecificFamily = Use a specific family UseSpecificAuthor = Use a specific author UseSpecificVersion = Use a specific initial version IncludeRefGeneration=The reference of this object must be generated automatically by custom numbering rules -IncludeRefGenerationHelp=Check this if you want to include code to manage the generation of the reference automatically using custom numbering rules -IncludeDocGeneration=I want the feature to generate some documents (PDF, ODT) from templates for this object +IncludeRefGenerationHelp=Check this if you want to include code to manage the generation of the reference automatically using custom numbering rules. If unckeck, a simpler +1 counter will be used. +IncludeDocGeneration=I want a feature to generate some documents (PDF, ODT) from templates for this object IncludeDocGenerationHelp=If you check this, some code will be generated to add a "Generate document" box on the record. ShowOnCombobox=Show value into combo boxes KeyForTooltip=Key for tooltip diff --git a/htdocs/modulebuilder/index.php b/htdocs/modulebuilder/index.php index 9f6abc39c25..50f39a1d3c7 100644 --- a/htdocs/modulebuilder/index.php +++ b/htdocs/modulebuilder/index.php @@ -4078,7 +4078,7 @@ if ($module == 'initmodule') { print '
'; print ''.$langs->trans("ObjectKey").'   '; print '
'; - print ''; + print ''; print $form->textwithpicto('', $langs->trans("Example").': MyObject, ACamelCaseName, ...'); print '
'; diff --git a/htdocs/modulebuilder/template/myobject_list.php b/htdocs/modulebuilder/template/myobject_list.php index 6b43622e793..867ca91e2ed 100644 --- a/htdocs/modulebuilder/template/myobject_list.php +++ b/htdocs/modulebuilder/template/myobject_list.php @@ -589,6 +589,7 @@ $newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars i $newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=kanban'.preg_replace('/(&|\?)*(mode|groupby)=[^&]+/', '', $param), '', ($mode == 'kanban' ? 2 : 1), array('morecss' => 'reposition')); //$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanbanGroupBy'), '', 'fa fa-grip-vertical imgforviewmode', $_SERVER["PHP_SELF"].'?mode=kanbangroupby&groupby=p.fk_opp_status'.preg_replace('/(&|\?)*(mode|groupby)=[^&]+/', '', $param), '', ($mode == 'kanbangroupby' ? 2 : 1), array('morecss' => 'reposition')); //$newcardbutton .= dolGetButtonTitle($langs->trans('HierarchicView'), '', 'fa fa-stream paddingleft imgforviewmode', $_SERVER["PHP_SELF"].'?mode=hierarchy'.preg_replace('/(&|\?)*(mode|groupby)=[^&]+/', '', $param), '', (($mode == 'hierarchy') ? 2 : 1), array('morecss' => 'reposition')); +$newcardbutton .= dolGetButtonTitle($langs->trans('Statistics'), '', 'fa fa-chart-bar imgforviewmode', dol_buildpath('/mymodule/stats/myobject_index.php', 1).'?'.preg_replace('/(&|\?)*(mode|groupby)=[^&]+/', '', $param), '', ($mode == 'statistics' ? 2 : 1), array('morecss' => 'reposition')); $newcardbutton .= dolGetButtonTitleSeparator(); $newcardbutton .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', dol_buildpath('/mymodule/myobject_card.php', 1).'?action=create&backtopage='.urlencode($_SERVER['PHP_SELF']), '', $permissiontoadd); diff --git a/htdocs/modulebuilder/template/stats/myobject_index.php b/htdocs/modulebuilder/template/stats/myobject_index.php new file mode 100644 index 00000000000..901f57468f4 --- /dev/null +++ b/htdocs/modulebuilder/template/stats/myobject_index.php @@ -0,0 +1,456 @@ + + * Copyright (C) 2004-2013 Laurent Destailleur + * Copyright (C) 2005-2012 Regis Houssin + * Copyright (C) 2012 Marcos García + * Copyright (C) 2015 Jean-François Ferry + * Copyright (C) 2020 Maxime DEMAREST + * Copyright (C) 2024-2025 MDW + * Copyright (C) 2024-2025 Frédéric France + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file htdocs/modulebuilder/template/stats/index.php + * \ingroup order + * \brief Page with statistics + */ + +// Load Dolibarr environment +require '../../main.inc.php'; +/** + * @var Conf $conf + * @var DoliDB $db + * @var HookManager $hookmanager + * @var Translate $langs + * @var User $user + */ +require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php'; +require_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php'; +dol_include_once('/mymodule/class/myobject.class.php'); +dol_include_once('/mymodule/lib/mymodule_myobject.lib.php'); + +$WIDTH = DolGraph::getDefaultGraphSizeForStats('width'); +$HEIGHT = DolGraph::getDefaultGraphSizeForStats('height'); + +$mode = GETPOSTISSET("mode") ? GETPOST("mode", 'aZ09') : 'customer'; + +$hookmanager->initHooks(array('mymodulestats', 'myobjectstats', 'globalcard')); + +$objecttype = 'myobject'; +$object = new MyObject($db); + +// List of object we want to manage statistics +$usercanreadstatistic = $user->hasRight($objecttype, 'read'); +if (getDolGlobalInt('MAIN_NEED_EXPORT_PERMISSION_TO_READ_STATISTICS')) { + $usercanreadstatistic = $user->hasRight($objecttype, 'export'); +} + +if (!$usercanreadstatistic) { + accessforbidden(); +} + +// Security check +if ($user->socid > 0) { + $action = ''; + $socid = $user->socid; +} + +$parameters = array(); +$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks +if ($reshook < 0) { + setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); +} + +$nowyear = (int) dol_print_date(dol_now('gmt'), "%Y", 'gmt'); +$year = GETPOSTINT('year') > 0 ? GETPOSTINT('year') : $nowyear; +$startyear = $year - (!getDolGlobalInt('MAIN_STATS_GRAPHS_SHOW_N_YEARS') ? 2 : max(1, min(10, getDolGlobalInt('MAIN_STATS_GRAPHS_SHOW_N_YEARS')))); +$endyear = $year; + +// Load translation files required by the page +$langs->loadLangs(array('companies', 'other', 'mymodule@mymodule')); + + +/* + * View + */ + +$form = new Form($db); + +$picto = 'graph'; +$title = $langs->trans("Statistics"); + + +llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-order page-stats'); + + +$newcardbutton = ''; +$newcardbutton .= dolGetButtonTitle($langs->trans('ViewList'), '', 'fa fa-bars imgforviewmode', $_SERVER["PHP_SELF"].'?mode=common'.preg_replace('/(&|\?)*(mode|groupby)=[^&]+/', '', $param), '', ((empty($mode) || $mode == 'common') ? 2 : 1), array('morecss' => 'reposition')); +$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanban'), '', 'fa fa-th-list imgforviewmode', $_SERVER["PHP_SELF"].'?mode=kanban'.preg_replace('/(&|\?)*(mode|groupby)=[^&]+/', '', $param), '', ($mode == 'kanban' ? 2 : 1), array('morecss' => 'reposition')); +//$newcardbutton .= dolGetButtonTitle($langs->trans('ViewKanbanGroupBy'), '', 'fa fa-grip-vertical imgforviewmode', $_SERVER["PHP_SELF"].'?mode=kanbangroupby&groupby=p.fk_opp_status'.preg_replace('/(&|\?)*(mode|groupby)=[^&]+/', '', $param), '', ($mode == 'kanbangroupby' ? 2 : 1), array('morecss' => 'reposition')); +//$newcardbutton .= dolGetButtonTitle($langs->trans('HierarchicView'), '', 'fa fa-stream paddingleft imgforviewmode', $_SERVER["PHP_SELF"].'?mode=hierarchy'.preg_replace('/(&|\?)*(mode|groupby)=[^&]+/', '', $param), '', (($mode == 'hierarchy') ? 2 : 1), array('morecss' => 'reposition')); +$newcardbutton .= dolGetButtonTitle($langs->trans('Statistics'), '', 'fa fa-chart-bar imgforviewmode', dol_buildpath('/mymodule/stats/index.php', 1).'?objecttype=myobject'.preg_replace('/(&|\?)*(mode|groupby)=[^&]+/', '', $param), '', ($mode == 'statistics' ? 2 : 1), array('morecss' => 'reposition')); +$newcardbutton .= dolGetButtonTitleSeparator(); +$newcardbutton .= dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', dol_buildpath('/mymodule/myobject_card.php', 1).'?action=create&backtopage='.urlencode($_SERVER['PHP_SELF']), '', $permissiontoadd); + +$param = ''; +$page = 0; +$sortfield = ''; +$sortorder = ''; +$massactionbutton = ''; +$num = 0; +$nbtotalofrecords = 0; +$limit = 0; + +print_barre_liste($title, $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, $massactionbutton, $num, $nbtotalofrecords, $object->picto, 0, $newcardbutton, '', $limit, 0, 0, 1); + + +print load_fiche_titre($title, '', $picto); + +/* +dol_mkdir($dir); + +$stats = new MyObjectStats($db, $socid, $mode, ($userid > 0 ? $userid : 0), ($typent_id > 0 ? $typent_id : 0), ($categ_id > 0 ? $categ_id : 0)); + +// Build graphic number of object +$data = $stats->getNbByMonthWithPrevYear($endyear, $startyear); + + +$filenamenb = $dir.'/myobjectnbinyear-'.$user->id.'-'.$year.'.png'; +$fileurlnb = DOL_URL_ROOT.'/viewimage.php?modulepart=orderstats&file=ordersnbinyear-'.$user->id.'-'.$year.'.png'; + + +$px1 = new DolGraph(); +$mesg = $px1->isGraphKo(); +if (!$mesg) { + $px1->SetData($data); + $i = $startyear; + $legend = array(); + while ($i <= $endyear) { + $legend[] = $i; + $i++; + } + $px1->SetLegend($legend); + $px1->SetMaxValue($px1->GetCeilMaxValue()); + $px1->SetMinValue(min(0, $px1->GetFloorMinValue())); + $px1->SetWidth($WIDTH); + $px1->SetHeight($HEIGHT); + $px1->SetYLabel($langs->trans("NbOfOrder")); + $px1->SetShading(3); + $px1->SetHorizTickIncrement(1); + $px1->mode = 'depth'; + $px1->SetTitle($langs->trans("NumberOfOrdersByMonth")); + + $px1->draw($filenamenb, $fileurlnb); +} + +// Build graphic amount of object +$data = $stats->getAmountByMonthWithPrevYear($endyear, $startyear); +//var_dump($data); +// $data = array(array('Lib',val1,val2,val3),...) + +$fileurlamount = ''; +if (!$user->hasRight('societe', 'client', 'voir')) { + $filenameamount = $dir.'/ordersamountinyear-'.$user->id.'-'.$year.'.png'; + if ($mode == 'customer') { + $fileurlamount = DOL_URL_ROOT.'/viewimage.php?modulepart=orderstats&file=ordersamountinyear-'.$user->id.'-'.$year.'.png'; + } + if ($mode == 'supplier') { + $fileurlamount = DOL_URL_ROOT.'/viewimage.php?modulepart=orderstatssupplier&file=ordersamountinyear-'.$user->id.'-'.$year.'.png'; + } +} else { + $filenameamount = $dir.'/ordersamountinyear-'.$year.'.png'; + if ($mode == 'customer') { + $fileurlamount = DOL_URL_ROOT.'/viewimage.php?modulepart=orderstats&file=ordersamountinyear-'.$year.'.png'; + } + if ($mode == 'supplier') { + $fileurlamount = DOL_URL_ROOT.'/viewimage.php?modulepart=orderstatssupplier&file=ordersamountinyear-'.$year.'.png'; + } +} + +$px2 = new DolGraph(); +$mesg = $px2->isGraphKo(); +if (!$mesg) { + $px2->SetData($data); + $i = $startyear; + $legend = array(); + while ($i <= $endyear) { + $legend[] = $i; + $i++; + } + $px2->SetLegend($legend); + $px2->SetMaxValue($px2->GetCeilMaxValue()); + $px2->SetMinValue(min(0, $px2->GetFloorMinValue())); + $px2->SetWidth($WIDTH); + $px2->SetHeight($HEIGHT); + $px2->SetYLabel($langs->trans("AmountOfOrders")); + $px2->SetShading(3); + $px2->SetHorizTickIncrement(1); + $px2->mode = 'depth'; + $px2->SetTitle($langs->trans("AmountOfOrdersByMonthHT")); + + $px2->draw($filenameamount, $fileurlamount); +} + + +$data = $stats->getAverageByMonthWithPrevYear($endyear, $startyear); + + +$fileurl_avg = ''; +if (!$user->hasRight('societe', 'client', 'voir')) { + $filename_avg = $dir.'/ordersaverage-'.$user->id.'-'.$year.'.png'; + if ($mode == 'customer') { + $fileurl_avg = DOL_URL_ROOT.'/viewimage.php?modulepart=orderstats&file=ordersaverage-'.$user->id.'-'.$year.'.png'; + } + if ($mode == 'supplier') { + $fileurl_avg = DOL_URL_ROOT.'/viewimage.php?modulepart=orderstatssupplier&file=ordersaverage-'.$user->id.'-'.$year.'.png'; + } +} else { + $filename_avg = $dir.'/ordersaverage-'.$year.'.png'; + if ($mode == 'customer') { + $fileurl_avg = DOL_URL_ROOT.'/viewimage.php?modulepart=orderstats&file=ordersaverage-'.$year.'.png'; + } + if ($mode == 'supplier') { + $fileurl_avg = DOL_URL_ROOT.'/viewimage.php?modulepart=orderstatssupplier&file=ordersaverage-'.$year.'.png'; + } +} + +$px3 = new DolGraph(); +$mesg = $px3->isGraphKo(); +if (!$mesg) { + $px3->SetData($data); + $i = $startyear; + $legend = array(); + while ($i <= $endyear) { + $legend[] = $i; + $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); +} + + + +// Show array +$data = $stats->getAllByYear(); +$arrayyears = array(); +foreach ($data as $val) { + if (!empty($val['year'])) { + $arrayyears[$val['year']] = $val['year']; + } +} +if (!count($arrayyears)) { + $arrayyears[$nowyear] = $nowyear; +} +*/ + + +$h = 0; +$head = array(); +$head[$h][0] = $_SERVER["PHP_SELF"].'?mode='.$mode; +$head[$h][1] = $langs->trans("ByMonthYear"); +$head[$h][2] = 'byyear'; +$h++; + +$type = 'myobject_stats'; + +complete_head_from_modules($conf, $langs, null, $head, $h, $type); + +print dol_get_fiche_head($head, 'byyear', '', -1); + +print '
'; + +/* +// Show filter box +print '
'; +print ''; +print ''; + +print ''; +print ''; +// Company +print ''; +// ThirdParty Type +print ''; +// Category societe +$cat_type = 0; +$cat_label = ''; +if ($mode == 'customer') { + $cat_type = Categorie::TYPE_CUSTOMER; + $cat_label = $langs->trans("Category").' '.lcfirst($langs->trans("Customer")); +} +if ($mode == 'supplier') { + $cat_type = Categorie::TYPE_SUPPLIER; + $cat_label = $langs->trans("Category").' '.lcfirst($langs->trans("Supplier")); +} +print ''; +// Category commande +if (isModEnabled('category')) { + $cat_type = ''; + $cat_label = ''; + if ($mode == 'customer') { + $cat_type = Categorie::TYPE_ORDER; + $cat_label = $langs->trans("Category").' '.lcfirst($langs->trans("CustomersOrders")); + } + if ($mode == 'supplier') { + $cat_type = Categorie::TYPE_SUPPLIER_ORDER; + $cat_label = $langs->trans("Category").' '.lcfirst($langs->trans("SuppliersOrders")); + } + print ''; +} +// User +print ''; +// Year +print ''; +print ''; +print '
'.$langs->trans("Filter").'
'.$langs->trans("ThirdParty").''; +$filter = ''; +if ($mode == 'customer') { + $filter = '(s.client:IN:1,2,3)'; +} +if ($mode == 'supplier') { + $filter = '(s.fournisseur:=:1)'; +} +print img_picto('', 'company', 'class="pictofixedwidth"'); +print $form->select_company($socid, 'socid', $filter, 1, 0, 0, array(), 0, 'widthcentpercentminusx maxwidth300'); +print '
'.$langs->trans("ThirdPartyType").''; +$sortparam_typent = (!getDolGlobalString('SOCIETE_SORT_ON_TYPEENT') ? 'ASC' : $conf->global->SOCIETE_SORT_ON_TYPEENT); // NONE means we keep sort of original array, so we sort on position. ASC, means next function will sort on label. +print $form->selectarray("typent_id", $formcompany->typent_array(0), $typent_id, 1, 0, 0, '', 0, 0, 0, $sortparam_typent, '', 1); +if ($user->admin) { + print ' '.info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1); +} +print '
'.$cat_label.''; +print img_picto('', 'category', 'class="pictofixedwidth"'); +print $formother->select_categories($cat_type, $categ_id, 'categ_id', 0, 1, 'widthcentpercentminusx maxwidth300'); +print '
'.$cat_label.''; + $cate_arbo = $form->select_all_categories($cat_type, '', 'parent', 0, 0, 1); + print img_picto('', 'category', 'class="pictofixedwidth"'); + print $form->multiselectarray('select_categ_comande_id', $cate_arbo, GETPOST('select_categ_comande_id', 'array'), 0, 0, 'widthcentpercentminusx maxwidth300'); + //print $formother->select_categories($cat_type, $categ_id, 'categ_id', true); + print '
'.$langs->trans("CreatedBy").''; +print img_picto('', 'user', 'class="pictofixedwidth"'); +print $form->select_dolusers($userid, 'userid', 1, null, 0, '', '', '0', 0, 0, '', 0, '', 'widthcentpercentminusx maxwidth300'); +// Status +print '
'.$langs->trans("Status").''; +if ($mode == 'customer') { + $liststatus = array( + Commande::STATUS_DRAFT => $langs->trans("StatusOrderDraft"), + Commande::STATUS_VALIDATED => $langs->trans("StatusOrderValidated"), + Commande::STATUS_SHIPMENTONPROCESS => $langs->trans("StatusOrderSent"), + Commande::STATUS_CLOSED => $langs->trans("StatusOrderDelivered"), + Commande::STATUS_CANCELED => $langs->trans("StatusOrderCanceled") + ); + print $form->selectarray('object_status', $liststatus, GETPOST('object_status', 'intcomma'), -4); +} +if ($mode == 'supplier') { + $formorder->selectSupplierOrderStatus((strstr($object_status, ',') ? -1 : $object_status), 0, 'object_status'); +} +print '
'.$langs->trans("Year").''; +if (!in_array($year, $arrayyears)) { + $arrayyears[$year] = $year; +} +if (!in_array($nowyear, $arrayyears)) { + $arrayyears[$nowyear] = $nowyear; +} +arsort($arrayyears); +print img_picto('', 'calendar', 'class="pictofixedwidth"'); +print $form->selectarray('year', $arrayyears, $year, 0, 0, 0, '', 0, 0, 0, '', 'width75'); +print '
'; +print '
'; +print '

'; + + +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + +$oldyear = 0; +foreach ($data as $val) { + $year = $val['year']; + while (!empty($year) && $oldyear > (int) $year + 1) { // If we have empty year + $oldyear--; + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } + + + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + $oldyear = $year; +} + +print '
'.$langs->trans("Year").''.$langs->trans("NbOfOrders").'%'.$langs->trans("AmountTotal").'%'.$langs->trans("AmountAverage").'%
0 ? '&userid='.$userid : '').'">'.$oldyear.'000
0 ? '&userid='.$userid : '').'">'.$year.''.$val['nb'].''.(isset($val['nb_diff']) ? round($val['nb_diff']) : "0").'%'.price(price2num($val['total'], 'MT'), 1).''.(isset($val['total_diff']) ? round($val['total_diff']) : "0").'%'.price(price2num($val['avg'], 'MT'), 1).''.(isset($val['avg_diff']) ? round($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 '
'; +*/ + +print dol_get_fiche_end(); + +// End of page +llxFooter(); +$db->close();