dolibarr  20.0.0-alpha
stats.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2003 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3  * Copyright (c) 2008-2013 Laurent Destailleur <eldy@users.sourceforge.net>
4  * Copyright (C) 2012 Regis Houssin <regis.houssin@inodbox.com>
5  * Copyright (C) 2012 Marcos GarcĂ­a <marcosgdf@gmail.com>
6  * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
31 abstract class Stats
32 {
33  protected $db;
34  protected $lastfetchdate = array(); // Dates of cache file read by methods
35  public $cachefilesuffix = ''; // Suffix to add to name of cache file (to avoid file name conflicts)
36 
40  public $from;
41 
45  public $where;
49  public $from_line;
53  public $field_date;
57  public $field;
61  public $field_line;
62 
66  public $error;
67 
71  public $year;
72 
76  public $month;
77 
83  abstract protected function getNbByMonth($year, $format = 0);
84 
95  public function getNbByMonthWithPrevYear($endyear, $startyear, $cachedelay = 0, $format = 0, $startmonth = 1)
96  {
97  global $conf, $user, $langs;
98 
99  if ($startyear > $endyear) {
100  return array();
101  }
102 
103  $data = array(); // This is the return value
104  $datay = array(); // This is a work value
105 
106  // Search into cache
107  if (!empty($cachedelay)) {
108  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
109  include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
110  }
111 
112  $newpathofdestfile = $conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix) ? '' : $this->cachefilesuffix.'_').$langs->defaultlang.'_entity.'.$conf->entity.'_user'.$user->id.'.cache';
113  $newmask = '0644';
114 
115  $nowgmt = dol_now();
116 
117  $foundintocache = 0;
118  if ($cachedelay > 0) {
119  $filedate = dol_filemtime($newpathofdestfile);
120  if ($filedate >= ($nowgmt - $cachedelay)) {
121  $foundintocache = 1;
122 
123  $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $filedate;
124  } else {
125  dol_syslog(get_class($this).'::'.__FUNCTION__." cache file ".$newpathofdestfile." is not found or older than now - cachedelay (".$nowgmt." - ".$cachedelay.") so we can't use it.");
126  }
127  }
128  // Load file into $data
129  if ($foundintocache) { // Cache file found and is not too old
130  dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate.".");
131  $data = json_decode(file_get_contents($newpathofdestfile), true);
132  '@phan-var-force array<int<0,11>,array{0:int<1,12>,1:int}> $data';
133  } else {
134  $year = $startyear;
135  $sm = $startmonth - 1;
136  if ($sm != 0) {
137  $year = $year - 1;
138  }
139  while ($year <= $endyear) {
140  $datay[$year] = $this->getNbByMonth($year, $format);
141  $year++;
142  }
143 
144  for ($i = 0; $i < 12; $i++) {
145  $data[$i][] = $datay[$endyear][($i + $sm) % 12][0];
146  $year = $startyear;
147  while ($year <= $endyear) {
148  // floor(($i + $sm) / 12)) is 0 if we are after the month start $sm and same year, become 1 when we reach january of next year
149  $data[$i][] = $datay[$year - (1 - (int) floor(($i + $sm) / 12)) + ($sm == 0 ? 1 : 0)][($i + $sm) % 12][1];
150  $year++;
151  }
152  }
153  }
154 
155 
156  // Save cache file
157  if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1)) {
158  dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk.");
159  if (!dol_is_dir($conf->user->dir_temp)) {
160  dol_mkdir($conf->user->dir_temp);
161  }
162  $fp = @fopen($newpathofdestfile, 'w');
163  if ($fp) {
164  fwrite($fp, json_encode($data));
165  fclose($fp);
166  } else {
167  dol_syslog("Failed to save cache file ".$newpathofdestfile, LOG_ERR);
168  }
169  dolChmod($newpathofdestfile);
170 
171  $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $nowgmt;
172  }
173 
174  return $data;
175  }
176 
182  abstract protected function getAmountByMonth($year, $format = 0);
183 
197  public function getAmountByMonthWithPrevYear($endyear, $startyear, $cachedelay = 0, $format = 0, $startmonth = 1)
198  {
199  global $conf, $user, $langs;
200 
201  if ($startyear > $endyear) {
202  return array();
203  }
204 
205  $datay = array();
206  $data = array(); // Return value
207 
208  // Search into cache
209  if (!empty($cachedelay)) {
210  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
211  include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
212  }
213 
214  $newpathofdestfile = $conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix) ? '' : $this->cachefilesuffix.'_').$langs->defaultlang.'_entity.'.$conf->entity.'_user'.$user->id.'.cache';
215  $newmask = '0644';
216 
217  $nowgmt = dol_now();
218 
219  $foundintocache = 0;
220  if ($cachedelay > 0) {
221  $filedate = dol_filemtime($newpathofdestfile);
222  if ($filedate >= ($nowgmt - $cachedelay)) {
223  $foundintocache = 1;
224 
225  $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $filedate;
226  } else {
227  dol_syslog(get_class($this).'::'.__FUNCTION__." cache file ".$newpathofdestfile." is not found or older than now - cachedelay (".$nowgmt." - ".$cachedelay.") so we can't use it.");
228  }
229  }
230 
231  // Load file into $data
232  if ($foundintocache) { // Cache file found and is not too old
233  dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate.".");
234  $data = json_decode(file_get_contents($newpathofdestfile), true);
235  } else {
236  $year = $startyear;
237  $sm = $startmonth - 1;
238  if ($sm != 0) {
239  $year = $year - 1;
240  }
241  while ($year <= $endyear) {
242  $datay[$year] = $this->getAmountByMonth($year, $format);
243  $year++;
244  }
245 
246  // $data = array('xval'=>array(0=>xlabel,1=>yval1,2=>yval2...),...)
247  for ($i = 0; $i < 12; $i++) {
248  $data[$i][] = isset($datay[$endyear][($i + $sm) % 12]['label']) ? $datay[$endyear][($i + $sm) % 12]['label'] : $datay[$endyear][($i + $sm) % 12][0]; // set label
249  $year = $startyear;
250  while ($year <= $endyear) {
251  // floor(($i + $sm) / 12)) is 0 if we are after the month start $sm and same year, become 1 when we reach january of next year
252  $data[$i][] = $datay[$year - (1 - floor(($i + $sm) / 12)) + ($sm == 0 ? 1 : 0)][($i + $sm) % 12][1]; // set yval for x=i
253  $year++;
254  }
255  }
256  }
257 
258  // Save cache file
259  if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1)) {
260  dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk.");
261  if (!dol_is_dir($conf->user->dir_temp)) {
262  dol_mkdir($conf->user->dir_temp);
263  }
264  $fp = @fopen($newpathofdestfile, 'w');
265  if ($fp) {
266  fwrite($fp, json_encode($data));
267  fclose($fp);
268  } else {
269  dol_syslog("Failed to save cache file ".$newpathofdestfile, LOG_ERR);
270  }
271  dolChmod($newpathofdestfile);
272 
273  $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $nowgmt;
274  }
275 
276  return $data;
277  }
278 
283  abstract protected function getAverageByMonth($year);
284 
292  public function getAverageByMonthWithPrevYear($endyear, $startyear)
293  {
294  if ($startyear > $endyear) {
295  return array();
296  }
297 
298  $datay = array();
299  $data = array();
300 
301  $year = $startyear;
302  while ($year <= $endyear) {
303  $datay[$year] = $this->getAverageByMonth($year);
304  $year++;
305  }
306 
307  for ($i = 0; $i < 12; $i++) {
308  $data[$i][] = $datay[$endyear][$i][0];
309  $year = $startyear;
310  while ($year <= $endyear) {
311  $data[$i][] = $datay[$year][$i][1];
312  $year++;
313  }
314  }
315 
316  return $data;
317  }
318 
327  public function getAllByProductEntry($year, $cachedelay = 0, $limit = 10)
328  {
329  global $conf, $user, $langs;
330 
331  $data = array();
332 
333  // Search in cache
334  if (!empty($cachedelay)) {
335  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
336  include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
337  }
338 
339  $newpathofdestfile = $conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix) ? '' : $this->cachefilesuffix.'_').$langs->defaultlang.'_entity.'.$conf->entity.'_user'.$user->id.'.cache';
340  $newmask = '0644';
341 
342  $nowgmt = dol_now();
343 
344  $foundintocache = 0;
345  if ($cachedelay > 0) {
346  $filedate = dol_filemtime($newpathofdestfile);
347  if ($filedate >= ($nowgmt - $cachedelay)) {
348  $foundintocache = 1;
349 
350  $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $filedate;
351  } else {
352  dol_syslog(get_class($this).'::'.__FUNCTION__." cache file ".$newpathofdestfile." is not found or older than now - cachedelay (".$nowgmt." - ".$cachedelay.") so we can't use it.");
353  }
354  }
355 
356  // Load file into $data
357  if ($foundintocache) { // Cache file found and is not too old
358  dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate.".");
359  $data = json_decode(file_get_contents($newpathofdestfile), true);
360  '@phan-var-force array<int<0,11>,array{0:int<1,12>,1:int|float}> $data'; // Phan can't decode json_decode's return value
361  } else {
362  // This method is defined in parent object only, not into abstract, so we disable phpstan warning
364  $data = $this->getAllByProduct($year, $limit);
365  }
366 
367  // Save cache file
368  if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1)) {
369  dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk.");
370  if (!dol_is_dir($conf->user->dir_temp)) {
371  dol_mkdir($conf->user->dir_temp);
372  }
373  $fp = @fopen($newpathofdestfile, 'w');
374  if ($fp) {
375  fwrite($fp, json_encode($data));
376  fclose($fp);
377  } else {
378  dol_syslog("Failed to save cache file ".$newpathofdestfile, LOG_ERR);
379  }
380  dolChmod($newpathofdestfile);
381 
382  $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $nowgmt;
383  }
384 
385  return $data;
386  }
387 
388 
389  // Here we have low level of shared code called by XxxStats.class.php
390 
391 
392  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
399  protected function _getNbByYear($sql)
400  {
401  // phpcs:enable
402  $result = array();
403 
404  dol_syslog(get_class($this).'::'.__FUNCTION__, LOG_DEBUG);
405  $resql = $this->db->query($sql);
406  if ($resql) {
407  $num = $this->db->num_rows($resql);
408  $i = 0;
409  while ($i < $num) {
410  $row = $this->db->fetch_row($resql);
411  $result[$i] = $row;
412  $i++;
413  }
414  $this->db->free($resql);
415  } else {
416  dol_print_error($this->db);
417  }
418  return $result;
419  }
420 
421  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
428  protected function _getAllByYear($sql)
429  {
430  // phpcs:enable
431  $result = array();
432 
433  dol_syslog(get_class($this).'::'.__FUNCTION__, LOG_DEBUG);
434  $resql = $this->db->query($sql);
435  if ($resql) {
436  $num = $this->db->num_rows($resql);
437  $i = 0;
438  while ($i < $num) {
439  $row = $this->db->fetch_object($resql);
440  $result[$i]['year'] = $row->year;
441  $result[$i]['nb'] = $row->nb;
442  if ($i > 0 && $row->nb > 0) {
443  $result[$i - 1]['nb_diff'] = ($result[$i - 1]['nb'] - $row->nb) / $row->nb * 100;
444  }
445  // For some $sql only
446  if (property_exists($row, 'total')) {
447  $result[$i]['total'] = $row->total;
448  if ($i > 0 && $row->total > 0) {
449  $result[$i - 1]['total_diff'] = ($result[$i - 1]['total'] - $row->total) / $row->total * 100;
450  }
451  }
452  // For some $sql only
453  if (property_exists($row, 'total')) {
454  $result[$i]['avg'] = $row->avg;
455  if ($i > 0 && $row->avg > 0) {
456  $result[$i - 1]['avg_diff'] = ($result[$i - 1]['avg'] - $row->avg) / $row->avg * 100;
457  }
458  }
459  // For some $sql only
460  if (property_exists($row, 'weighted')) {
461  $result[$i]['weighted'] = $row->weighted;
462  if ($i > 0 && $row->weighted > 0) {
463  $result[$i - 1]['avg_weighted'] = ($result[$i - 1]['weighted'] - $row->weighted) / $row->weighted * 100;
464  }
465  }
466  $i++;
467  }
468  $this->db->free($resql);
469  } else {
470  dol_print_error($this->db);
471  }
472  return $result;
473  }
474 
475  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
485  protected function _getNbByMonth($year, $sql, $format = 0)
486  {
487  // phpcs:enable
488  global $langs;
489 
490  $result = array();
491  $res = array();
492 
493  dol_syslog(get_class($this).'::'.__FUNCTION__, LOG_DEBUG);
494  $resql = $this->db->query($sql);
495  if ($resql) {
496  $num = $this->db->num_rows($resql);
497  $i = 0;
498  $j = 0;
499  while ($i < $num) {
500  $row = $this->db->fetch_row($resql);
501  $j = $row[0] * 1;
502  $result[$j] = $row[1];
503  $i++;
504  }
505  $this->db->free($resql);
506  } else {
507  dol_print_error($this->db);
508  }
509 
510  for ($i = 1; $i < 13; $i++) {
511  $res[$i] = (isset($result[$i]) ? $result[$i] : 0);
512  }
513 
514  $data = array();
515 
516  for ($i = 1; $i < 13; $i++) {
517  $month = 'unknown';
518  if ($format == 0) {
519  $month = $langs->transnoentitiesnoconv('MonthShort'.sprintf("%02d", $i));
520  } elseif ($format == 1) {
521  $month = $i;
522  } elseif ($format == 2) {
523  $month = $langs->transnoentitiesnoconv('MonthVeryShort'.sprintf("%02d", $i));
524  }
525  //$month=dol_print_date(dol_mktime(12,0,0,$i,1,$year),($format?"%m":"%b"));
526  //$month=dol_substr($month,0,3);
527  $data[$i - 1] = array($month, $res[$i]);
528  }
529 
530  return $data;
531  }
532 
533  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
542  protected function _getAmountByMonth($year, $sql, $format = 0)
543  {
544  // phpcs:enable
545  global $langs;
546 
547  $result = array();
548  $res = array();
549 
550  dol_syslog(get_class($this).'::'.__FUNCTION__, LOG_DEBUG);
551 
552  $resql = $this->db->query($sql);
553  if ($resql) {
554  $num = $this->db->num_rows($resql);
555  $i = 0;
556  while ($i < $num) {
557  $row = $this->db->fetch_row($resql);
558  $j = $row[0] * 1;
559  $result[$j] = $row[1];
560  $i++;
561  }
562  $this->db->free($resql);
563  } else {
564  dol_print_error($this->db);
565  }
566 
567  for ($i = 1; $i < 13; $i++) {
568  $res[$i] = (int) round((isset($result[$i]) ? $result[$i] : 0));
569  }
570 
571  $data = array();
572 
573  for ($i = 1; $i < 13; $i++) {
574  $month = 'unknown';
575  if ($format == 0) {
576  $month = $langs->transnoentitiesnoconv('MonthShort'.sprintf("%02d", $i));
577  } elseif ($format == 1) {
578  $month = $i;
579  } elseif ($format == 2) {
580  $month = $langs->transnoentitiesnoconv('MonthVeryShort'.sprintf("%02d", $i));
581  }
582  //$month=dol_print_date(dol_mktime(12,0,0,$i,1,$year),($format?"%m":"%b"));
583  //$month=dol_substr($month,0,3);
584  $data[$i - 1] = array($month, $res[$i]);
585  }
586 
587  return $data;
588  }
589 
590  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
599  protected function _getAverageByMonth($year, $sql, $format = 0)
600  {
601  // phpcs:enable
602  global $langs;
603 
604  $result = array();
605  $res = array();
606 
607  dol_syslog(get_class($this).'::'.__FUNCTION__, LOG_DEBUG);
608  $resql = $this->db->query($sql);
609  if ($resql) {
610  $num = $this->db->num_rows($resql);
611  $i = 0;
612  $j = 0;
613  while ($i < $num) {
614  $row = $this->db->fetch_row($resql);
615  $j = $row[0] * 1;
616  $result[$j] = $row[1];
617  $i++;
618  }
619  $this->db->free($resql);
620  } else {
621  dol_print_error($this->db);
622  }
623 
624  for ($i = 1; $i < 13; $i++) {
625  $res[$i] = (isset($result[$i]) ? $result[$i] : 0);
626  }
627 
628  $data = array();
629 
630  for ($i = 1; $i < 13; $i++) {
631  $month = 'unknown';
632  if ($format == 0) {
633  $month = $langs->transnoentitiesnoconv('MonthShort'.sprintf("%02d", $i));
634  } elseif ($format == 1) {
635  $month = $i;
636  } elseif ($format == 2) {
637  $month = $langs->transnoentitiesnoconv('MonthVeryShort'.sprintf("%02d", $i));
638  }
639  //$month=dol_print_date(dol_mktime(12,0,0,$i,1,$year),($format?"%m":"%b"));
640  //$month=dol_substr($month,0,3);
641  $data[$i - 1] = array($month, $res[$i]);
642  }
643 
644  return $data;
645  }
646 
647 
648  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
656  protected function _getAllByProduct($sql, $limit = 10)
657  {
658  // phpcs:enable
659  global $langs;
660 
661  $result = array();
662 
663  dol_syslog(get_class($this).'::'.__FUNCTION__, LOG_DEBUG);
664  $resql = $this->db->query($sql);
665  if ($resql) {
666  $num = $this->db->num_rows($resql);
667  $i = 0;
668  $other = 0;
669  while ($i < $num) {
670  $row = $this->db->fetch_row($resql);
671  if ($i < $limit || $num == $limit) {
672  $result[$i] = array($row[0], $row[1]); // Ref of product, nb
673  } else {
674  $other += $row[1];
675  }
676  $i++;
677  }
678  if ($num > $limit) {
679  $result[$i] = array($langs->transnoentitiesnoconv("Other"), $other);
680  }
681  $this->db->free($resql);
682  } else {
683  dol_print_error($this->db);
684  }
685 
686  return $result;
687  }
688 
689  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
695  protected function _getAmountByYear($sql)
696  {
697  $result = array();
698  $resql = $this->db->query($sql);
699  if ($resql) {
700  $num = $this->db->num_rows($resql);
701  $i = 0;
702  while ($i < $num) {
703  $row = $this->db->fetch_row($resql);
704  $result[] = [
705  0 => (int) $row[0],
706  1 => (int) $row[1],
707  ];
708  $i++;
709  }
710  $this->db->free($resql);
711  }
712  return $result;
713  }
714 }
Parent class of statistics class.
Definition: stats.class.php:32
getNbByMonth($year, $format=0)
getAmountByMonth($year, $format=0)
getAverageByMonth($year)
_getAmountByYear($sql)
Returns the summed amounts per year for a given number of past years ending now.
_getAverageByMonth($year, $sql, $format=0)
Return the amount average par month for a given year.
_getAmountByMonth($year, $sql, $format=0)
Return the amount per month for a given year.
_getNbByYear($sql)
Return nb of elements by year.
_getAllByYear($sql)
Return nb of elements, total amount and avg amount each year.
getAmountByMonthWithPrevYear($endyear, $startyear, $cachedelay=0, $format=0, $startmonth=1)
Return amount of elements by month for several years.
getNbByMonthWithPrevYear($endyear, $startyear, $cachedelay=0, $format=0, $startmonth=1)
Return nb of elements by month for several years.
Definition: stats.class.php:95
getAllByProductEntry($year, $cachedelay=0, $limit=10)
Return count, and sum of products.
getAverageByMonthWithPrevYear($endyear, $startyear)
Return average of entity by month for several years.
_getAllByProduct($sql, $limit=10)
Return number or total of product refs.
_getNbByMonth($year, $sql, $format=0)
Renvoie le nombre de documents par mois pour une annee donnee Return number of documents per month fo...
if(isModEnabled('invoice') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD') && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') && $user->hasRight('tax', 'charges', 'lire')) if(isModEnabled('invoice') &&isModEnabled('order') && $user->hasRight("commande", "lire") &&!getDolGlobalString('WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER')) $sql
Social contributions to pay.
Definition: index.php:744
dol_filemtime($pathoffile)
Return time of a file.
Definition: files.lib.php:635
dol_is_dir($folder)
Test if filename is a directory.
Definition: files.lib.php:489
dolChmod($filepath, $newmask='')
Change mod of a file.
dol_now($mode='auto')
Return date for now.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)