box_funnel_of_prospection.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <?php
  2. /* Copyright (C) 2012-2014 Charles-François BENKE <charles.fr@benke.fr>
  3. * Copyright (C) 2014 Marcos García <marcosgdf@gmail.com>
  4. * Copyright (C) 2015 Frederic France <frederic.france@free.fr>
  5. * Copyright (C) 2016 Juan José Menent <jmenent@2byte.es>
  6. * Copyright (C) 2020 Pierre Ardoin <mapiolca@me.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. /**
  22. * \file htdocs/core/boxes/box_funnel_of_prospection.php
  23. * \ingroup projet
  24. * \brief Module to show the funnel of prospection
  25. */
  26. include_once DOL_DOCUMENT_ROOT . "/core/boxes/modules_boxes.php";
  27. /**
  28. * Class to manage the box to show last projet
  29. */
  30. class box_funnel_of_prospection extends ModeleBoxes
  31. {
  32. public $boxcode = "FunnelOfProspection";
  33. public $boximg = "object_projectpub";
  34. public $boxlabel = "BoxTitleFunnelOfProspection";
  35. public $depends = array("projet");
  36. public $version = 'development';
  37. /**
  38. * @var DoliDB Database handler.
  39. */
  40. public $db;
  41. public $param;
  42. public $info_box_head = array();
  43. public $info_box_contents = array();
  44. /**
  45. * Constructor
  46. *
  47. * @param DoliDB $db Database handler
  48. * @param string $param More parameters
  49. */
  50. public function __construct($db, $param = '')
  51. {
  52. global $user, $langs, $conf;
  53. // Load translation files required by the page
  54. $langs->loadLangs(array('boxes', 'projects'));
  55. $this->db = $db;
  56. $this->enabled = ($conf->global->MAIN_FEATURES_LEVEL >= 1 ? 1 : 0); // Not enabled by default, still need some work
  57. $this->hidden = empty($user->rights->projet->lire);
  58. }
  59. /**
  60. * Load data for box to show them later
  61. *
  62. * @param int $max Maximum number of records to load
  63. * @return void
  64. */
  65. public function loadBox($max = 5)
  66. {
  67. global $conf;
  68. // default values
  69. $badgeStatus0 = '#cbd3d3'; // draft
  70. $badgeStatus1 = '#bc9526'; // validated
  71. $badgeStatus1b = '#bc9526'; // validated
  72. $badgeStatus2 = '#9c9c26'; // approved
  73. $badgeStatus3 = '#bca52b';
  74. $badgeStatus4 = '#25a580'; // Color ok
  75. $badgeStatus4b = '#25a580'; // Color ok
  76. $badgeStatus5 = '#cad2d2';
  77. $badgeStatus6 = '#cad2d2';
  78. $badgeStatus7 = '#baa32b';
  79. $badgeStatus8 = '#993013';
  80. $badgeStatus9 = '#e7f0f0';
  81. if (file_exists(DOL_DOCUMENT_ROOT . '/theme/' . $conf->theme . '/theme_vars.inc.php')) {
  82. include DOL_DOCUMENT_ROOT . '/theme/' . $conf->theme . '/theme_vars.inc.php';
  83. }
  84. $listofoppstatus = array();
  85. $listofopplabel = array();
  86. $listofoppcode = array();
  87. $colorseriesstat = array();
  88. $bordercolorseries = array();
  89. $sql = "SELECT cls.rowid, cls.code, cls.percent, cls.label";
  90. $sql .= " FROM " . MAIN_DB_PREFIX . "c_lead_status as cls";
  91. $sql .= " WHERE active=1";
  92. $sql .= " AND cls.code <> 'LOST'";
  93. $sql .= $this->db->order('cls.rowid', 'ASC');
  94. $resql = $this->db->query($sql);
  95. if ($resql) {
  96. $num = $this->db->num_rows($resql);
  97. $i = 0;
  98. while ($i < $num) {
  99. $objp = $this->db->fetch_object($resql);
  100. $listofoppstatus[$objp->rowid] = $objp->percent;
  101. $listofopplabel[$objp->rowid] = $objp->label;
  102. $listofoppcode[$objp->rowid] = $objp->code;
  103. switch ($objp->code) {
  104. case 'PROSP':
  105. $colorseriesstat[$objp->rowid] = '#FFFFFF';
  106. $bordercolorseries[$objp->rowid] = $badgeStatus0;
  107. break;
  108. case 'QUAL':
  109. $colorseriesstat[$objp->rowid] = '#FFFFFF';
  110. $bordercolorseries[$objp->rowid] = $badgeStatus1;
  111. break;
  112. case 'PROPO':
  113. $colorseriesstat[$objp->rowid] = $badgeStatus1;
  114. $bordercolorseries[$objp->rowid] = $badgeStatus1;
  115. break;
  116. case 'NEGO':
  117. $colorseriesstat[$objp->rowid] = $badgeStatus4;
  118. $bordercolorseries[$objp->rowid] = $badgeStatus4;
  119. break;
  120. case 'WON':
  121. $colorseriesstat[$objp->rowid] = $badgeStatus6;
  122. $bordercolorseries[$objp->rowid] = $badgeStatus6;
  123. break;
  124. default:
  125. break;
  126. }
  127. $i++;
  128. }
  129. } else {
  130. dol_print_error($this->db);
  131. }
  132. global $conf, $user, $langs;
  133. $this->max = $max;
  134. $this->info_box_head = array(
  135. 'text' => $langs->trans("Statistics") . ' - ' . $langs->trans("BoxTitleFunnelOfProspection"),
  136. 'graph' => '1'
  137. );
  138. if ($user->rights->projet->lire || !empty($conf->global->PROJECT_USE_OPPORTUNITIES)) {
  139. $sql = "SELECT p.fk_opp_status as opp_status, cls.code, COUNT(p.rowid) as nb, SUM(p.opp_amount) as opp_amount, SUM(p.opp_amount * p.opp_percent) as ponderated_opp_amount";
  140. $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p, " . MAIN_DB_PREFIX . "c_lead_status as cls";
  141. $sql .= " WHERE p.entity IN (" . getEntity('project') . ")";
  142. $sql .= " AND p.fk_opp_status = cls.rowid";
  143. $sql .= " AND p.fk_statut = 1"; // Opend projects only
  144. $sql .= " AND cls.code NOT IN ('LOST')";
  145. $sql .= " GROUP BY p.fk_opp_status, cls.code";
  146. $resql = $this->db->query($sql);
  147. $form = new Form($this->db);
  148. if ($resql) {
  149. $num = $this->db->num_rows($resql);
  150. $i = 0;
  151. $totalnb = 0;
  152. $totaloppnb = 0;
  153. $totalamount = 0;
  154. $ponderated_opp_amount = 0;
  155. $valsnb = array();
  156. $valsamount = array();
  157. $dataseries = array();
  158. while ($i < $num) {
  159. $obj = $this->db->fetch_object($resql);
  160. if ($obj) {
  161. $valsnb[$obj->opp_status] = $obj->nb;
  162. $valsamount[$obj->opp_status] = $obj->opp_amount;
  163. $totalnb += $obj->nb;
  164. if ($obj->opp_status) {
  165. $totaloppnb += $obj->nb;
  166. }
  167. if (!in_array($obj->code, array('WON', 'LOST'))) {
  168. $totalamount += $obj->opp_amount;
  169. $ponderated_opp_amount += $obj->ponderated_opp_amount;
  170. }
  171. }
  172. $i++;
  173. }
  174. $this->db->free($resql);
  175. $ponderated_opp_amount = $ponderated_opp_amount / 100;
  176. $stringtoprint = '';
  177. $stringtoprint .= '<div class="div-table-responsive-no-min ">';
  178. $listofstatus = array_keys($listofoppstatus);
  179. $liststatus = array();
  180. $data = array('');
  181. $customlabels = array();
  182. $total=0;
  183. foreach ($listofstatus as $status) {
  184. $customlabel = '';
  185. $labelStatus = '';
  186. if ($status != 7) {
  187. $code = dol_getIdFromCode($this->db, $status, 'c_lead_status', 'rowid', 'code');
  188. if ($code) {
  189. $labelStatus = $langs->transnoentitiesnoconv("OppStatus" . $code);
  190. }
  191. if (empty($labelStatus)) {
  192. $labelStatus = $listofopplabel[$status];
  193. }
  194. $amount = (isset($valsamount[$status]) ? (float) $valsamount[$status] : 0);
  195. $data[] = $amount;
  196. $customlabel = $amount;
  197. $liststatus[] = $labelStatus;
  198. if (!$conf->use_javascript_ajax) {
  199. $stringtoprint .= '<tr class="oddeven">';
  200. $stringtoprint .= '<td>' . $labelStatus . '</td>';
  201. $stringtoprint .= '<td class="right"><a href="list.php?statut=' . $status . '">' . price((isset($valsamount[$status]) ? (float) $valsamount[$status] : 0), 0, '', 1, -1, -1, $conf->currency) . '</a></td>';
  202. $stringtoprint .= "</tr>\n";
  203. }
  204. }
  205. $customlabels[]=$customlabel;
  206. }
  207. $dataseries[] = $data;
  208. if ($conf->use_javascript_ajax) {
  209. include_once DOL_DOCUMENT_ROOT . '/core/class/dolgraph.class.php';
  210. $dolgraph = new DolGraph();
  211. $dolgraph->SetMinValue(0);
  212. $dolgraph->SetData($dataseries);
  213. $dolgraph->SetLegend($liststatus);
  214. $dolgraph->setHideXValues(true);
  215. $dolgraph->SetDataColor(array_values($colorseriesstat));
  216. $dolgraph->setBorderColor(array_values($bordercolorseries));
  217. $dolgraph->setShowLegend(2);
  218. if (!empty($conf->dol_optimize_smallscreen)) {
  219. $dolgraph->SetWidth(320);
  220. }
  221. $dolgraph->setShowPercent(1);
  222. $dolgraph->setMirrorGraphValues(true);
  223. $dolgraph->setBorderWidth(2);
  224. $dolgraph->SetType(array('horizontalbars'));
  225. $dolgraph->SetHeight('200');
  226. $dolgraph->SetWidth('600');
  227. $dolgraph->setTooltipsTitles($liststatus);
  228. $dolgraph->setTooltipsLabels($customlabels);
  229. $dolgraph->mode = 'depth';
  230. $dolgraph->draw('idgraphleadfunnel');
  231. $stringtoprint .= $dolgraph->show($totaloppnb ? 0 : 1);
  232. }
  233. $stringtoprint .= '</div>';
  234. $line = 0;
  235. $this->info_box_contents[$line][] = array(
  236. 'tr' => 'class="nohover left "',
  237. 'text' => ''
  238. );
  239. $this->info_box_contents[$line][] = array(
  240. 'tr' => 'class="nohover left "',
  241. 'text' => ''
  242. );
  243. $line++;
  244. $this->info_box_contents[$line][] = array(
  245. 'tr' => '',
  246. 'td' => 'class="center nopaddingleftimp nopaddingrightimp" colspan="2"',
  247. 'text' => $stringtoprint
  248. );
  249. $line++;
  250. $this->info_box_contents[$line][] = array(
  251. 'tr' => 'class="oddeven"',
  252. 'td' => 'class="left "',
  253. 'maxlength' => 500,
  254. 'text' => $langs->trans("OpportunityTotalAmount") . ' (' . $langs->trans("WonLostExcluded") . ')'
  255. );
  256. $this->info_box_contents[$line][] = array(
  257. 'tr' => 'class="oddeven"',
  258. 'td' => 'class="right "',
  259. 'maxlength' => 500,
  260. 'text' => price($totalamount, 0, '', 1, -1, -1, $conf->currency)
  261. );
  262. $line++;
  263. $this->info_box_contents[$line][] = array(
  264. 'tr' => 'class="oddeven"',
  265. 'td' => 'class="left "',
  266. 'maxlength' => 500,
  267. 'text' => $form->textwithpicto($langs->trans("OpportunityPonderatedAmount") . ' (' . $langs->trans("WonLostExcluded") . ')', $langs->trans("OpportunityPonderatedAmountDesc"), 1)
  268. );
  269. $this->info_box_contents[$line][] = array(
  270. 'td' => 'class="right "',
  271. 'maxlength' => 500,
  272. 'text' => price(price2num($ponderated_opp_amount, 'MT'), 0, '', 1, -1, -1, $conf->currency)
  273. );
  274. } else {
  275. $this->info_box_contents[0][0] = array(
  276. 'td' => 'class="center opacitymedium"',
  277. 'text' => $langs->trans("NoRecordedCustomers")
  278. );
  279. }
  280. } else {
  281. $this->info_box_contents[0][0] = array(
  282. 'td' => '',
  283. 'text' => $langs->trans("ReadPermissionNotAllowed")
  284. );
  285. }
  286. }
  287. /**
  288. * Method to show box
  289. *
  290. * @param array $head Array with properties of box title
  291. * @param array $contents Array with properties of box lines
  292. * @param int $nooutput No $stringtoprint .=, only return string
  293. * @return string
  294. */
  295. public function showBox($head = null, $contents = null, $nooutput = 0)
  296. {
  297. return parent::showBox($this->info_box_head, $this->info_box_contents, $nooutput);
  298. }
  299. }