dolibarr 25.0.0-alpha
box_funnel_of_prospection.php
Go to the documentation of this file.
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-2026 Frédéric 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 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
28include_once DOL_DOCUMENT_ROOT."/core/boxes/modules_boxes.php";
29
30
35{
36 public $boxcode = "FunnelOfProspection";
37 public $boximg = "object_projectpub";
38 public $boxlabel = "BoxTitleFunnelOfProspection";
39 public $depends = array("projet");
40
41 public $version = 'dolibarr';
42
49 public function __construct($db, $param = '') // @phpstan-ignore constructor.unusedParameter
50 {
51 global $user, $langs;
52
53 // Load translation files required by the page
54 $langs->loadLangs(array('boxes', 'projects'));
55
56 $this->db = $db;
57
58 $this->enabled = getDolGlobalInt('PROJECT_USE_OPPORTUNITIES');
59
60 $this->hidden = !$user->hasRight('projet', 'lire');
61 }
62
69 public function loadBox($max = 5)
70 {
71 global $conf;
72
73 // default values
74 $badgeStatus0 = '#cbd3d3'; // draft
75 $badgeStatus1 = '#bc9526'; // validated
76 $badgeStatus1b = '#bc9526'; // validated
77 $badgeStatus2 = '#9c9c26'; // approved
78 $badgeStatus3 = '#bca52b';
79 $badgeStatus4 = '#25a580'; // Color ok
80 $badgeStatus4b = '#25a580'; // Color ok
81 $badgeStatus5 = '#cad2d2';
82 $badgeStatus6 = '#cad2d2';
83 $badgeStatus7 = '#baa32b';
84 $badgeStatus8 = '#993013';
85 $badgeStatus9 = '#e7f0f0';
86 $theme_vars_file = dol_getThemeFilePath('theme_vars.inc.php');
87 if ($theme_vars_file) {
88 include $theme_vars_file;
89 }
90
91 $listofoppstatus = array();
92 $listofopplabel = array();
93 $listofoppposition = array();
94
95 $colorseriesstat = array();
96
97 $sql = "SELECT cls.rowid, cls.code, cls.percent, cls.label, cls.position";
98 $sql .= " FROM ".MAIN_DB_PREFIX."c_lead_status as cls";
99 $sql .= " WHERE active = 1";
100 $sql .= " AND cls.code NOT IN ('LOST', 'WON')";
101 $sql .= $this->db->order('cls.position,cls.rowid', 'ASC,ASC');
102
103 $resql = $this->db->query($sql);
104 if ($resql) {
105 $num = $this->db->num_rows($resql);
106 $i = 0;
107
108 while ($i < $num) {
109 $objp = $this->db->fetch_object($resql);
110
111 $listofoppstatus[$objp->rowid] = $objp->percent;
112 $listofopplabel[$objp->rowid] = $objp->label;
113 $listofoppposition[$objp->rowid] = $objp->position;
114
115 switch ($objp->code) {
116 case 'PROSP':
117 $colorseriesstat[$objp->rowid] = '-'.$badgeStatus0;
118 break;
119 case 'QUAL':
120 $colorseriesstat[$objp->rowid] = '-'.$badgeStatus1;
121 break;
122 case 'PROPO':
123 $colorseriesstat[$objp->rowid] = $badgeStatus1;
124 break;
125 case 'NEGO':
126 $colorseriesstat[$objp->rowid] = $badgeStatus4;
127 break;
128 default:
129 break;
130 }
131 $i++;
132 }
133 } else {
134 dol_print_error($this->db);
135 }
136
137 global $conf, $user, $langs;
138 $this->max = $max;
139
140 $this->info_box_head = array(
141 'text' => $langs->trans("Opportunities").' - '.$langs->trans("BoxTitleFunnelOfProspection"),
142 'nbcol' => 2,
143 'graph' => 1
144 );
145
146 if ($user->hasRight('projet', 'lire') || getDolGlobalString('PROJECT_USE_OPPORTUNITIES')) {
147 $sql = "SELECT p.fk_opp_status as opp_status, cls.code, cls.position, COUNT(p.rowid) as nb, SUM(p.opp_amount) as opp_amount, SUM(p.opp_amount * p.opp_percent) as ponderated_opp_amount";
148 $sql .= " FROM ".MAIN_DB_PREFIX."projet as p, ".MAIN_DB_PREFIX."c_lead_status as cls";
149 $sql .= " WHERE p.entity IN (".getEntity('project').")";
150 $sql .= " AND p.fk_opp_status = cls.rowid";
151 $sql .= " AND p.fk_statut = 1"; // Opened projects only
152 $sql .= " AND cls.active = 1"; // Active opportunities status only
153 $sql .= " AND cls.code NOT IN ('LOST', 'WON')";
154 $sql .= " GROUP BY p.fk_opp_status, cls.code, cls.position";
155 $sql .= " ORDER BY cls.position, p.fk_opp_status, cls.code";
156
157 $resql = $this->db->query($sql);
158
159 $form = new Form($this->db);
160 if ($resql) {
161 $num = $this->db->num_rows($resql);
162 $i = 0;
163
164 //$totalnb = 0;
165 $totaloppnb = 0;
166 $totalamount = 0;
167 $ponderated_opp_amount = 0;
168 //$valsnb = array();
169 $valsamount = array();
170 $dataseries = array();
171
172 while ($i < $num) {
173 $obj = $this->db->fetch_object($resql);
174 if ($obj) {
175 //$valsnb[$obj->opp_status] = $obj->nb;
176 $valsamount[$obj->opp_status] = $obj->opp_amount;
177 //$totalnb += $obj->nb;
178 if ($obj->opp_status) {
179 $totaloppnb += $obj->nb;
180 }
181 if (!in_array($obj->code, array('WON', 'LOST'))) {
182 $totalamount += $obj->opp_amount;
183 $ponderated_opp_amount += $obj->ponderated_opp_amount;
184 }
185 }
186 $i++;
187 }
188 $this->db->free($resql);
189
190 $ponderated_opp_amount /= 100;
191
192 $stringtoprint = '';
193 $stringtoprint .= '<div class="div-table-responsive-no-min ">';
194 $listofstatus = array_keys($listofoppstatus);
195 $liststatus = array();
196 $data = array('');
197 $customlabels = array();
198 // Set array $data that contains the length of the bar (not the real value)
199 // and the array $customlabels that contains the real value to show
200 $maxamount = 0;
201
202 foreach ($listofstatus as $status) {
203 $customlabel = '';
204 $labelStatus = '';
205 $customlabelmore = '';
206
207 $code = dol_getIdFromCode($this->db, $status, 'c_lead_status', 'rowid', 'code');
208 if ($code) {
209 $labelStatus = $langs->transnoentitiesnoconv("OppStatus".$code);
210 }
211 if (empty($labelStatus)) {
212 $labelStatus = $listofopplabel[$status];
213 }
214 $amount = (isset($valsamount[$status]) ? (float) $valsamount[$status] : 0);
215 $customlabel = price($amount, 0, $langs, 1, -1, 'MT', $conf->currency);
216
217 $amountforgraph = $amount;
218 if (getDolGlobalString('PROJECT_OPPORTUNITIES_CUMULATIVE_MODE')) {
219 // Add all other amount for next levels
220 $amountafter = 0;
221 foreach ($listofstatus as $status2) {
222 if ($listofoppposition[$status2] > $listofoppposition[$status]) {
223 $amountafter += $valsamount[$status2];
224 }
225 }
226 $amountforgraph += $amountafter;
227 //var_dump("For current status ".$status." the amountforgraph=".$amountforgraph);
228 $customlabelmore = price($amountafter, 0, $langs, 1, -1, 'MT', $conf->currency);
229 }
230
231 $data[] = $amountforgraph;
232 $liststatus[] = $labelStatus;
233 if (!$conf->use_javascript_ajax) {
234 $stringtoprint .= '<tr class="oddeven">';
235 $stringtoprint .= '<td>'.$labelStatus.'</td>';
236 $stringtoprint .= '<td class="nowraponall right amount"><a href="list.php?statut='.$status.'">'.price((isset($valsamount[$status]) ? (float) $valsamount[$status] : 0), 0, '', 1, -1, -1, $conf->currency).'</a></td>';
237 $stringtoprint .= "</tr>\n";
238 }
239
240 if (getDolGlobalString('PROJECT_OPPORTUNITIES_CUMULATIVE_MODE')) {
241 $customlabels[] = $customlabel.' + '.$customlabelmore;
242 } else {
243 $customlabels[] = $customlabel;
244 }
245
246 if ($maxamount < $amountforgraph) {
247 $maxamount = $amountforgraph;
248 }
249 }
250
251 // Permit to have a bar if value inferior to a certain value
252 $valuetoaddtomindata = $maxamount / 100;
253 foreach ($data as $key => $value) {
254 if ($value != "") {
255 $data[$key] = $valuetoaddtomindata + $value;
256 }
257 }
258 foreach ($data as $key => $value) {
259 if ($data[$key] == 0) {
260 $data[$key] = $valuetoaddtomindata;
261 }
262 }
263
264
265 $dataseries[] = $data;
266 if ($conf->use_javascript_ajax) {
267 include_once DOL_DOCUMENT_ROOT.'/core/class/dolgraph.class.php';
268 $dolgraph = new DolGraph();
269 $dolgraph->SetMinValue(0);
270 $dolgraph->SetData($dataseries);
271 $dolgraph->SetLegend($liststatus);
272 $dolgraph->setHideXValues(false);
273 $dolgraph->setHideYValues(true);
274 $dolgraph->SetDataColor(array_values($colorseriesstat));
275 $dolgraph->setShowLegend(2);
276 $dolgraph->setShowPercent(1);
277 $dolgraph->setMirrorGraphValues(true);
278 $dolgraph->setBorderWidth(2);
279 $dolgraph->setBorderSkip('false');
280 $dolgraph->SetType(array('horizontalbars'));
281 $dolgraph->SetHeight('150');
282 $dolgraph->SetWidth($conf->dol_optimize_smallscreen ? '300' : ((empty($_SESSION["dol_screenwidth"]) || $_SESSION["dol_screenwidth"] > 1600) ? '600' : '500'));
283 $dolgraph->setTooltipsTitles($liststatus);
284 $dolgraph->setTooltipsLabels($customlabels);
285 $dolgraph->mode = 'depth';
286 $dolgraph->draw('idgraphleadfunnel');
287
288 $stringtoprint .= $dolgraph->show($totaloppnb ? 0 : 1);
289 }
290 $stringtoprint .= '</div>';
291
292 $line = 0;
293
294 $this->info_box_contents[$line][] = array(
295 'tr' => '',
296 'td' => 'class="center nopaddingleftimp nopaddingrightimp" colspan="2"',
297 'text' => $stringtoprint
298 );
299 $line++;
300 $this->info_box_contents[$line][] = array(
301 'tr' => 'class="oddeven"',
302 'td' => 'class="left "',
303 'maxlength' => 500,
304 'asis' => 1,
305 'text' => $langs->trans("OpportunityTotalAmount").' <span class="opacitymedium hideonsmartphone">('.$langs->trans("WonLostExcluded").')</span>'
306 );
307 $this->info_box_contents[$line][] = array(
308 'tr' => 'class="oddeven"',
309 'td' => 'class="nowraponall right amount"',
310 'maxlength' => 500,
311 'text' => price($totalamount, 0, '', 1, -1, -1, $conf->currency)
312 );
313 $line++;
314 $this->info_box_contents[$line][] = array(
315 'tr' => 'class="oddeven"',
316 'td' => 'class="left "',
317 'maxlength' => 500,
318 'asis' => 1,
319 'text' => $form->textwithpicto($langs->trans("OpportunityPonderatedAmount").' <span class="opacitymedium hideonsmartphone">('.$langs->trans("WonLostExcluded").')</span>', $langs->trans("OpportunityPonderatedAmountDesc"), 1)
320
321 );
322 $this->info_box_contents[$line][] = array(
323 'td' => 'class="nowraponall right amount"',
324 'maxlength' => 500,
325 'text' => price(price2num($ponderated_opp_amount, 'MT'), 0, '', 1, -1, -1, $conf->currency)
326 );
327 } else {
328 $this->info_box_contents[0][0] = array(
329 'td' => 'class="center"',
330 'text' => '<span class="opacitymedium">'.$langs->trans("NoRecordedCustomers").'</span>'
331 );
332 }
333 } else {
334 $this->info_box_contents[0][0] = array(
335 'td' => '',
336 'text' => $langs->trans("ReadPermissionNotAllowed")
337 );
338 }
339 }
340
341
342
351 public function showBox($head = null, $contents = null, $nooutput = 0)
352 {
353 return parent::showBox($this->info_box_head, $this->info_box_contents, $nooutput);
354 }
355}
Class to build graphs.
Class to manage generation of HTML components Only common components must be here.
Class ModeleBoxes.
Class to manage the box to show funnel of prospections.
showBox($head=null, $contents=null, $nooutput=0)
Method to show box.
__construct($db, $param='')
Constructor.
loadBox($max=5)
Load data for box to show them later.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='', $useCache=true)
Return an id or code from a code or id.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_getThemeFilePath($file, $theme='')
Return the full filesystem path of a file located in the currently selected theme directory.
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.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.