dolibarr  13.0.2
projectstats.class.php
1 <?php
2 /* Lead
3  * Copyright (C) 2014-2015 Florian HENRY <florian.henry@open-concept.pro>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <https://www.gnu.org/licenses/>.
17  */
18 include_once DOL_DOCUMENT_ROOT.'/core/class/stats.class.php';
19 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
20 
21 
25 class ProjectStats extends Stats
26 {
27  private $project;
28  public $userid;
29  public $socid;
30  public $year;
31 
37  public function __construct($db)
38  {
39  global $conf, $user;
40 
41  $this->db = $db;
42 
43  require_once 'project.class.php';
44  $this->project = new Project($this->db);
45  }
46 
47 
56  public function getAllProjectByStatus($limit = 5)
57  {
58  global $conf, $user, $langs;
59 
60  $datay = array();
61 
62  $sql = "SELECT";
63  $sql .= " SUM(t.opp_amount), t.fk_opp_status, cls.code, cls.label";
64  $sql .= " FROM ".MAIN_DB_PREFIX."projet as t";
65  // No check is done on company permission because readability is managed by public status of project and assignement.
66  //if (! $user->rights->societe->client->voir && ! $user->socid)
67  // $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON sc.fk_soc=t.fk_soc AND sc.fk_user=" . $user->id;
68  $sql .= ", ".MAIN_DB_PREFIX."c_lead_status as cls";
69  $sql .= $this->buildWhere();
70  // For external user, no check is done on company permission because readability is managed by public status of project and assignement.
71  //if ($socid > 0) $sql.= " AND t.fk_soc = ".$socid;
72  // No check is done on company permission because readability is managed by public status of project and assignement.
73  //if (! $user->rights->societe->client->voir && ! $socid) $sql.= " AND ((s.rowid = sc.fk_soc AND sc.fk_user = " .$user->id.") OR (s.rowid IS NULL))";
74  $sql .= " AND t.fk_opp_status = cls.rowid";
75  $sql .= " AND t.fk_statut <> 0"; // We want historic also, so all projects not draft
76  $sql .= " GROUP BY t.fk_opp_status, cls.code, cls.label";
77 
78  $result = array();
79 
80  dol_syslog(get_class($this).'::'.__METHOD__."", LOG_DEBUG);
81  $resql = $this->db->query($sql);
82  if ($resql) {
83  $num = $this->db->num_rows($resql);
84  $i = 0;
85  $other = 0;
86  while ($i < $num) {
87  $row = $this->db->fetch_row($resql);
88  if ($i < $limit || $num == $limit)
89  {
90  $label = (($langs->trans("OppStatus".$row[2]) != "OppStatus".$row[2]) ? $langs->trans("OppStatus".$row[2]) : $row[2]);
91  $result[$i] = array(
92  $label.' ('.price(price2num($row[0], 'MT'), 1, $langs, 1, -1, -1, $conf->currency).')',
93  $row[0]
94  );
95  } else $other += $row[1];
96  $i++;
97  }
98  if ($num > $limit)
99  $result[$i] = array(
100  $langs->transnoentitiesnoconv("Other"),
101  $other
102  );
103  $this->db->free($resql);
104  } else {
105  $this->error = "Error ".$this->db->lasterror();
106  dol_syslog(get_class($this).'::'.__METHOD__.' '.$this->error, LOG_ERR);
107  return -1;
108  }
109 
110  return $result;
111  }
112 
118  public function getAllByYear()
119  {
120  global $conf, $user, $langs;
121 
122  $datay = array();
123 
124  $wonlostfilter = 0; // No filter on status WON/LOST
125 
126  $sql = "SELECT date_format(t.datec,'%Y') as year, COUNT(t.rowid) as nb, SUM(t.opp_amount) as total, AVG(t.opp_amount) as avg,";
127  $sql .= " SUM(t.opp_amount * ".$this->db->ifsql("t.opp_percent IS NULL".($wonlostfilter ? " OR cls.code IN ('WON','LOST')" : ""), '0', 't.opp_percent')." / 100) as weighted";
128  $sql .= " FROM ".MAIN_DB_PREFIX."projet as t LEFT JOIN ".MAIN_DB_PREFIX."c_lead_status as cls ON cls.rowid = t.fk_opp_status";
129  // No check is done on company permission because readability is managed by public status of project and assignement.
130  //if (! $user->rights->societe->client->voir && ! $user->soc_id)
131  // $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON sc.fk_soc=t.fk_soc AND sc.fk_user=" . $user->id;
132  $sql .= $this->buildWhere();
133  // For external user, no check is done on company permission because readability is managed by public status of project and assignement.
134  //if ($socid > 0) $sql.= " AND t.fk_soc = ".$socid;
135  // No check is done on company permission because readability is managed by public status of project and assignement.
136  //if (! $user->rights->societe->client->voir && ! $socid) $sql.= " AND ((s.rowid = sc.fk_soc AND sc.fk_user = " .$user->id.") OR (s.rowid IS NULL))";
137  $sql .= " GROUP BY year";
138  $sql .= $this->db->order('year', 'DESC');
139 
140  return $this->_getAllByYear($sql);
141  }
142 
143 
149  public function buildWhere()
150  {
151  global $user;
152 
153  $sqlwhere_str = '';
154  $sqlwhere = array();
155 
156  // Get list of project id allowed to user (in a string list separated by coma)
157  $object = new Project($this->db);
158  $projectsListId = '';
159  if (!$user->rights->projet->all->lire) $projectsListId = $object->getProjectsAuthorizedForUser($user, 0, 1, $user->socid);
160 
161  $sqlwhere[] = ' t.entity IN ('.getEntity('project').')';
162 
163  if (!empty($this->userid))
164  $sqlwhere[] = ' t.fk_user_resp='.$this->userid;
165 
166  // Forced filter on socid is similar to forced filter on project. TODO Use project assignement to allow to not use filter on project
167  if (!empty($this->socid))
168  $sqlwhere[] = ' t.fk_soc='.$this->socid;
169  if (!empty($this->year) && empty($this->yearmonth))
170  $sqlwhere[] = " date_format(t.datec,'%Y')='".$this->db->escape($this->year)."'";
171  if (!empty($this->yearmonth))
172  $sqlwhere[] = " t.datec BETWEEN '".$this->db->idate(dol_get_first_day($this->yearmonth))."' AND '".$this->db->idate(dol_get_last_day($this->yearmonth))."'";
173 
174  if (!empty($this->status))
175  $sqlwhere[] = " t.fk_opp_status IN (".$this->status.")";
176 
177  if (!$user->rights->projet->all->lire) $sqlwhere[] = " t.rowid IN (".$projectsListId.")"; // public and assigned to, or restricted to company for external users
178 
179  if (count($sqlwhere) > 0) {
180  $sqlwhere_str = ' WHERE '.implode(' AND ', $sqlwhere);
181  }
182 
183  return $sqlwhere_str;
184  }
185 
193  public function getNbByMonth($year, $format = 0)
194  {
195  global $user;
196 
197  $this->yearmonth = $year;
198 
199  $sql = "SELECT date_format(t.datec,'%m') as dm, COUNT(*) as nb";
200  $sql .= " FROM ".MAIN_DB_PREFIX."projet as t";
201  // No check is done on company permission because readability is managed by public status of project and assignement.
202  //if (! $user->rights->societe->client->voir && ! $user->soc_id)
203  // $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON sc.fk_soc=t.fk_soc AND sc.fk_user=" . $user->id;
204  $sql .= $this->buildWhere();
205  $sql .= " GROUP BY dm";
206  $sql .= $this->db->order('dm', 'DESC');
207 
208  $this->yearmonth = 0;
209 
210  $res = $this->_getNbByMonth($year, $sql, $format);
211  // var_dump($res);print '<br>';
212  return $res;
213  }
214 
222  public function getAmountByMonth($year, $format = 0)
223  {
224  global $user;
225 
226  $this->yearmonth = $year;
227 
228  $sql = "SELECT date_format(t.datec,'%m') as dm, SUM(t.opp_amount)";
229  $sql .= " FROM ".MAIN_DB_PREFIX."projet as t";
230  // No check is done on company permission because readability is managed by public status of project and assignement.
231  //if (! $user->rights->societe->client->voir && ! $user->soc_id)
232  // $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON sc.fk_soc=t.fk_soc AND sc.fk_user=" . $user->id;
233  $sql .= $this->buildWhere();
234  $sql .= " GROUP BY dm";
235  $sql .= $this->db->order('dm', 'DESC');
236  $this->yearmonth = 0;
237 
238  $res = $this->_getAmountByMonth($year, $sql, $format);
239  // var_dump($res);print '<br>';
240  return $res;
241  }
242 
243 
253  public function getWeightedAmountByMonthWithPrevYear($endyear, $startyear, $cachedelay = 0, $wonlostfilter = 1)
254  {
255  global $conf, $user, $langs;
256 
257  if ($startyear > $endyear) return -1;
258 
259  $datay = array();
260 
261  // Search into cache
262  if (!empty($cachedelay))
263  {
264  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
265  include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
266  }
267 
268  $newpathofdestfile = $conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix) ? '' : $this->cachefilesuffix.'_').$langs->defaultlang.'_user'.$user->id.'.cache';
269  $newmask = '0644';
270 
271  $nowgmt = dol_now();
272 
273  $foundintocache = 0;
274  if ($cachedelay > 0)
275  {
276  $filedate = dol_filemtime($newpathofdestfile);
277  if ($filedate >= ($nowgmt - $cachedelay))
278  {
279  $foundintocache = 1;
280 
281  $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $filedate;
282  } else {
283  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.");
284  }
285  }
286 
287  // Load file into $data
288  if ($foundintocache) // Cache file found and is not too old
289  {
290  dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate.".");
291  $data = json_decode(file_get_contents($newpathofdestfile), true);
292  } else {
293  $year = $startyear;
294  while ($year <= $endyear)
295  {
296  $datay[$year] = $this->getWeightedAmountByMonth($year, $wonlostfilter);
297  $year++;
298  }
299 
300  $data = array();
301  // $data = array('xval'=>array(0=>xlabel,1=>yval1,2=>yval2...),...)
302  for ($i = 0; $i < 12; $i++)
303  {
304  $data[$i][] = $datay[$endyear][$i][0]; // set label
305  $year = $startyear;
306  while ($year <= $endyear)
307  {
308  $data[$i][] = $datay[$year][$i][1]; // set yval for x=i
309  $year++;
310  }
311  }
312  }
313 
314  // Save cache file
315  if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == -1))
316  {
317  dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk.");
318  if (!dol_is_dir($conf->user->dir_temp)) dol_mkdir($conf->user->dir_temp);
319  $fp = fopen($newpathofdestfile, 'w');
320  if ($fp)
321  {
322  fwrite($fp, json_encode($data));
323  fclose($fp);
324  if (!empty($conf->global->MAIN_UMASK)) $newmask = $conf->global->MAIN_UMASK;
325  @chmod($newpathofdestfile, octdec($newmask));
326  } else dol_syslog("Failed to write cache file", LOG_ERR);
327  $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $nowgmt;
328  }
329 
330  return $data;
331  }
332 
333 
341  public function getWeightedAmountByMonth($year, $wonlostfilter = 1)
342  {
343  global $user;
344 
345  $this->yearmonth = $year;
346 
347  $sql = "SELECT date_format(t.datec,'%m') as dm, SUM(t.opp_amount * ".$this->db->ifsql("t.opp_percent IS NULL".($wonlostfilter ? " OR cls.code IN ('WON','LOST')" : ""), '0', 't.opp_percent')." / 100)";
348  $sql .= " FROM ".MAIN_DB_PREFIX."projet as t LEFT JOIN ".MAIN_DB_PREFIX.'c_lead_status as cls ON t.fk_opp_status = cls.rowid';
349  // No check is done on company permission because readability is managed by public status of project and assignement.
350  //if (! $user->rights->societe->client->voir && ! $user->soc_id)
351  // $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON sc.fk_soc=t.fk_soc AND sc.fk_user=" . $user->id;
352  $sql .= $this->buildWhere();
353  $sql .= " GROUP BY dm";
354  $sql .= $this->db->order('dm', 'DESC');
355  $this->yearmonth = 0;
356 
357  $res = $this->_getAmountByMonth($year, $sql);
358  // var_dump($res);print '<br>';
359  return $res;
360  }
361 
370  public function getTransformRateByMonthWithPrevYear($endyear, $startyear, $cachedelay = 0)
371  {
372  global $conf, $user, $langs;
373 
374  if ($startyear > $endyear) return -1;
375 
376  $datay = array();
377 
378  // Search into cache
379  if (!empty($cachedelay))
380  {
381  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
382  include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
383  }
384 
385  $newpathofdestfile = $conf->user->dir_temp.'/'.get_class($this).'_'.__FUNCTION__.'_'.(empty($this->cachefilesuffix) ? '' : $this->cachefilesuffix.'_').$langs->defaultlang.'_user'.$user->id.'.cache';
386  $newmask = '0644';
387 
388  $nowgmt = dol_now();
389 
390  $foundintocache = 0;
391  if ($cachedelay > 0) {
392  $filedate = dol_filemtime($newpathofdestfile);
393  if ($filedate >= ($nowgmt - $cachedelay)) {
394  $foundintocache = 1;
395 
396  $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $filedate;
397  } else {
398  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.");
399  }
400  }
401 
402  // Load file into $data
403  if ($foundintocache) // Cache file found and is not too old
404  {
405  dol_syslog(get_class($this).'::'.__FUNCTION__." read data from cache file ".$newpathofdestfile." ".$filedate.".");
406  $data = json_decode(file_get_contents($newpathofdestfile), true);
407  } else {
408  $year = $startyear;
409  while ($year <= $endyear) {
410  $datay[$year] = $this->getTransformRateByMonth($year);
411  $year++;
412  }
413 
414  $data = array();
415  // $data = array('xval'=>array(0=>xlabel,1=>yval1,2=>yval2...),...)
416  for ($i = 0; $i < 12; $i++) {
417  $data[$i][] = $datay[$endyear][$i][0]; // set label
418  $year = $startyear;
419  while ($year <= $endyear) {
420  $data[$i][] = $datay[$year][$i][1]; // set yval for x=i
421  $year++;
422  }
423  }
424  }
425 
426  // Save cache file
427  if (empty($foundintocache) && ($cachedelay > 0 || $cachedelay == - 1)) {
428  dol_syslog(get_class($this).'::'.__FUNCTION__." save cache file ".$newpathofdestfile." onto disk.");
429  if (!dol_is_dir($conf->user->dir_temp))
430  dol_mkdir($conf->user->dir_temp);
431  $fp = fopen($newpathofdestfile, 'w');
432  fwrite($fp, json_encode($data));
433  fclose($fp);
434  if (!empty($conf->global->MAIN_UMASK))
435  $newmask = $conf->global->MAIN_UMASK;
436  @chmod($newpathofdestfile, octdec($newmask));
437 
438  $this->lastfetchdate[get_class($this).'_'.__FUNCTION__] = $nowgmt;
439  }
440 
441  return $data;
442  }
443 
451  public function getTransformRateByMonth($year, $format = 0)
452  {
453  global $user;
454 
455  $this->yearmonth = $year;
456 
457  $sql = "SELECT date_format(t.datec,'%m') as dm, count(t.opp_amount)";
458  $sql .= " FROM ".MAIN_DB_PREFIX."projet as t";
459  // No check is done on company permission because readability is managed by public status of project and assignement.
460  //if (! $user->rights->societe->client->voir && ! $user->soc_id)
461  // $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON sc.fk_soc=t.fk_soc AND sc.fk_user=" . $user->id;
462  $sql .= $this->buildWhere();
463  $sql .= " GROUP BY dm";
464  $sql .= $this->db->order('dm', 'DESC');
465 
466  $res_total = $this->_getNbByMonth($year, $sql, $format);
467 
468  $this->status = 6;
469 
470  $sql = "SELECT date_format(t.datec,'%m') as dm, count(t.opp_amount)";
471  $sql .= " FROM ".MAIN_DB_PREFIX."projet as t";
472  // No check is done on company permission because readability is managed by public status of project and assignement.
473  //if (! $user->rights->societe->client->voir && ! $user->soc_id)
474  // $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON sc.fk_soc=t.fk_soc AND sc.fk_user=" . $user->id;
475  $sql .= $this->buildWhere();
476  $sql .= " GROUP BY dm";
477  $sql .= $this->db->order('dm', 'DESC');
478 
479  $this->status = 0;
480  $this->yearmonth = 0;
481 
482  $res_only_wined = $this->_getNbByMonth($year, $sql, $format);
483 
484  $res = array();
485 
486  foreach ($res_total as $key=>$total_row) {
487  //var_dump($total_row);
488  if (!empty($total_row[1])) {
489  $res[$key] = array($total_row[0], (100 * $res_only_wined[$key][1]) / $total_row[1]);
490  } else {
491  $res[$key] = array($total_row[0], 0);
492  }
493  }
494  // var_dump($res);print '<br>';
495  return $res;
496  }
497 }
Parent class of statistics class.
Definition: stats.class.php:30
getAllProjectByStatus($limit=5)
Return all leads grouped by opportunity status.
dol_now($mode= 'auto')
Return date for now.
getWeightedAmountByMonthWithPrevYear($endyear, $startyear, $cachedelay=0, $wonlostfilter=1)
Return amount of elements by month for several years.
dol_get_first_day($year, $month=1, $gm=false)
Return GMT time for first day of a month or year.
Definition: date.lib.php:481
getTransformRateByMonthWithPrevYear($endyear, $startyear, $cachedelay=0)
Return amount of elements by month for several years.
dol_is_dir($folder)
Test if filename is a directory.
Definition: files.lib.php:432
getTransformRateByMonth($year, $format=0)
Return the Project transformation rate by month for a year.
_getNbByMonth($year, $sql, $format=0)
Renvoie le nombre de documents par mois pour une annee donnee Return number of documents per month fo...
$conf db
API class for accounts.
Definition: inc.php:54
price($amount, $form=0, $outlangs= '', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code= '')
Function to format a value into an amount for visual output Function used into PDF and HTML pages...
getAmountByMonth($year, $format=0)
Return the Project amount by month for a year.
buildWhere()
Build the where part.
Class to manage projects.
price2num($amount, $rounding= '', $option=0)
Function that return a number with universal decimal format (decimal separator is &#39;...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename= '', $restricttologhandler= '', $logcontext=null)
Write log message into outputs.
_getAllByYear($sql)
Return nb of elements, total amount and avg amount each year.
getWeightedAmountByMonth($year, $wonlostfilter=1)
Return the Project weighted opp amount by month for a year.
getNbByMonth($year, $format=0)
Return Project number by month for a year.
Class to manage statistics on projects.
dol_get_last_day($year, $month=12, $gm=false)
Return GMT time for last day of a month or year.
Definition: date.lib.php:498
dol_filemtime($pathoffile)
Return time of a file.
Definition: files.lib.php:567
getAllByYear()
Return count, and sum of products.
if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if((!empty($conf->fournisseur->enabled)&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)||!empty($conf->supplier_invoice->enabled))&&$user->rights->fournisseur->facture->lire) if(!empty($conf->don->enabled)&&$user->rights->don->lire) if(!empty($conf->tax->enabled)&&$user->rights->tax->charges->lire) if(!empty($conf->facture->enabled)&&!empty($conf->commande->enabled)&&$user->rights->commande->lire &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if((!empty($conf->fournisseur->enabled)&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)||!empty($conf->supplier_invoice->enabled))&&$user->rights->fournisseur->facture->lire) $resql
Social contributions to pay.
Definition: index.php:1232
_getAmountByMonth($year, $sql, $format=0)
Return the amount per month for a given year.
dol_mkdir($dir, $dataroot= '', $newmask=null)
Creation of a directory (this can create recursive subdir)
__construct($db)
Constructor.