dolibarr 21.0.0-alpha
agentMargins.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2012-2013 Christophe Battarel <christophe.battarel@altairis.fr>
3 * Copyright (C) 2014 Ferran Marcet <fmarcet@2byte.es>
4 * Copyright (C) 2015 Marcos GarcĂ­a <marcosgdf@gmail.com>
5 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
27// Load Dolibarr environment
28require '../main.inc.php';
29require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
30require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
31require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
32require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
33
34// Load translation files required by the page
35$langs->loadLangs(array('companies', 'bills', 'products', 'margins'));
36
37$mesg = '';
38
39// Load variable for pagination
40$limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit;
41$sortfield = GETPOST('sortfield', 'aZ09comma');
42$sortorder = GETPOST('sortorder', 'aZ09comma');
43$page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT("page");
44if (empty($page) || $page == -1) {
45 $page = 0;
46} // If $page is not defined, or '' or -1
47$offset = $limit * $page;
48$pageprev = $page - 1;
49$pagenext = $page + 1;
50if (!$sortorder) {
51 $sortorder = "ASC";
52}
53if ($user->hasRight('margins', 'read', 'all')) {
54 $agentid = GETPOSTINT('agentid');
55} else {
56 $agentid = $user->id;
57}
58if (!$sortfield) {
59 if ($agentid > 0) {
60 $sortfield = "s.nom";
61 } else {
62 $sortfield = "u.lastname";
63 }
64}
65
66$startdate = $enddate = '';
67
68$startdateday = GETPOSTINT('startdateday');
69$startdatemonth = GETPOSTINT('startdatemonth');
70$startdateyear = GETPOSTINT('startdateyear');
71$enddateday = GETPOSTINT('enddateday');
72$enddatemonth = GETPOSTINT('enddatemonth');
73$enddateyear = GETPOSTINT('enddateyear');
74
75if (!empty($startdatemonth)) {
76 $startdate = dol_mktime(0, 0, 0, $startdatemonth, $startdateday, $startdateyear);
77}
78if (!empty($enddatemonth)) {
79 $enddate = dol_mktime(23, 59, 59, $enddatemonth, $enddateday, $enddateyear);
80}
81
82$hookmanager->initHooks(array('marginagentlist'));
83
84// Security check
85$result = restrictedArea($user, 'margins');
86
87// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
88$object = new User($db);
89
90/*
91 * Actions
92 */
93
94// None
95
96
97
98/*
99 * View
100 */
101
102$userstatic = new User($db);
103$companystatic = new Societe($db);
104$invoicestatic = new Facture($db);
105
106$form = new Form($db);
107
108llxHeader('', $langs->trans("Margins").' - '.$langs->trans("Agents"), '', '', 0, 0, '', '', '', 'mod-margin page-agentmargins');
109
110$text = $langs->trans("Margins");
111//print load_fiche_titre($text);
112
113// Show tabs
114$head = marges_prepare_head();
115
116$titre = $langs->trans("Margins");
117$picto = 'margin';
118
119print '<form method="post" name="sel" action="'.$_SERVER['PHP_SELF'].'">';
120print '<input type="hidden" name="token" value="'.newToken().'">';
121
122print dol_get_fiche_head($head, 'agentMargins', $titre, 0, $picto);
123
124print '<table class="border centpercent">';
125
126print '<tr><td class="titlefield">'.$langs->trans('ContactOfInvoice').'</td>';
127print '<td class="maxwidthonsmartphone" colspan="4">';
128print img_picto('', 'user').$form->select_dolusers($agentid, 'agentid', 1, '', $user->hasRight('margins', 'read', 'all') ? 0 : 1, '', '', 0, 0, 0, '', 0, '', 'maxwidth300');
129print '</td></tr>';
130
131// Start date
132print '<td>'.$langs->trans('DateStart').' ('.$langs->trans("DateValidation").')</td>';
133print '<td>';
134print $form->selectDate($startdate, 'startdate', 0, 0, 1, "sel", 1, 1);
135print '</td>';
136print '<td>'.$langs->trans('DateEnd').' ('.$langs->trans("DateValidation").')</td>';
137print '<td>';
138print $form->selectDate($enddate, 'enddate', 0, 0, 1, "sel", 1, 1);
139print '</td>';
140print '<td style="text-align: center;">';
141print '<input type="submit" class="button" value="'.dol_escape_htmltag($langs->trans('Refresh')).'" />';
142print '</td></tr>';
143print "</table>";
144
145print dol_get_fiche_end();
146
147print '</form>';
148
149$invoice_status_except_list = array(Facture::STATUS_DRAFT, Facture::STATUS_ABANDONED);
150
151$sql = "SELECT";
152$sql .= " s.rowid as socid, s.nom as name, s.code_client, s.client,";
153$sql .= " u.rowid as agent, u.login, u.lastname, u.firstname,";
154$sql .= " sum(d.total_ht) as selling_price,";
155// Note: qty and buy_price_ht is always positive (if not your database may be corrupted, you can update this)
156
157$sql .= " sum(".$db->ifsql('(d.total_ht < 0 OR (d.total_ht = 0 AND f.type = 2))', '-1 * d.qty * d.buy_price_ht * (d.situation_percent / 100)', 'd.qty * d.buy_price_ht * (d.situation_percent / 100)').") as buying_price,";
158$sql .= " sum(".$db->ifsql('(d.total_ht < 0 OR (d.total_ht = 0 AND f.type = 2))', '-1 * (abs(d.total_ht) - (d.buy_price_ht * d.qty * (d.situation_percent / 100)))', 'd.total_ht - (d.buy_price_ht * d.qty * (d.situation_percent / 100))').") as marge";
159
160$sql .= " FROM ".MAIN_DB_PREFIX."societe as s";
161$sql .= ", ".MAIN_DB_PREFIX."facture as f";
162$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_contact e ON e.element_id = f.rowid and e.statut = 4 and e.fk_c_type_contact = ".(!getDolGlobalString('AGENT_CONTACT_TYPE') ? -1 : $conf->global->AGENT_CONTACT_TYPE);
163$sql .= ", ".MAIN_DB_PREFIX."facturedet as d";
164$sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
165$sql .= ", ".MAIN_DB_PREFIX."user as u";
166$sql .= " WHERE f.fk_soc = s.rowid";
167$sql .= ' AND f.entity IN ('.getEntity('invoice').')';
168$sql .= " AND sc.fk_soc = f.fk_soc";
169$sql .= " AND (d.product_type = 0 OR d.product_type = 1)";
170if (getDolGlobalString('AGENT_CONTACT_TYPE')) {
171 $sql .= " AND ((e.fk_socpeople IS NULL AND sc.fk_user = u.rowid) OR (e.fk_socpeople IS NOT NULL AND e.fk_socpeople = u.rowid))";
172} else {
173 $sql .= " AND sc.fk_user = u.rowid";
174}
175$sql .= " AND f.fk_statut NOT IN (".$db->sanitize(implode(', ', $invoice_status_except_list)).")";
176$sql .= ' AND s.entity IN ('.getEntity('societe').')';
177$sql .= " AND d.fk_facture = f.rowid";
178if ($agentid > 0) {
179 if (getDolGlobalString('AGENT_CONTACT_TYPE')) {
180 $sql .= " AND ((e.fk_socpeople IS NULL AND sc.fk_user = ".((int) $agentid).") OR (e.fk_socpeople IS NOT NULL AND e.fk_socpeople = ".((int) $agentid)."))";
181 } else {
182 $sql .= " AND sc.fk_user = ".((int) $agentid);
183 }
184}
185if (!empty($startdate)) {
186 $sql .= " AND f.datef >= '".$db->idate($startdate)."'";
187}
188if (!empty($enddate)) {
189 $sql .= " AND f.datef <= '".$db->idate($enddate)."'";
190}
191$sql .= " AND d.buy_price_ht IS NOT NULL";
192// We should not use this here. Option ForceBuyingPriceIfNull should have effect only when inserting data. Once data is recorded, it must be used as it is for report.
193// We keep it with value ForceBuyingPriceIfNull = 2 for retroactive effect but results are unpredictable.
194if (getDolGlobalInt('ForceBuyingPriceIfNull') == 2) {
195 $sql .= " AND d.buy_price_ht <> 0";
196}
197//if ($agentid > 0) $sql.= " GROUP BY s.rowid, s.nom, s.code_client, s.client, u.rowid, u.login, u.lastname, u.firstname";
198//else $sql.= " GROUP BY u.rowid, u.login, u.lastname, u.firstname";
199$sql .= " GROUP BY s.rowid, s.nom, s.code_client, s.client, u.rowid, u.login, u.lastname, u.firstname";
200$sql .= $db->order($sortfield, $sortorder);
201// TODO: calculate total to display then restore pagination
202//$sql.= $db->plimit($conf->liste_limit +1, $offset);
203
204
205print '<br>';
206print '<span class="opacitymedium">'.$langs->trans("MarginPerSaleRepresentativeWarning").'</span><br>';
207
208$param = '';
209if (!empty($agentid)) {
210 $param .= "&amp;agentid=".urlencode((string) $agentid);
211}
212if (!empty($startdateday)) {
213 $param .= "&amp;startdateday=".urlencode((string) ($startdateday));
214}
215if (!empty($startdatemonth)) {
216 $param .= "&amp;startdatemonth=".urlencode((string) ($startdatemonth));
217}
218if (!empty($startdateyear)) {
219 $param .= "&amp;startdateyear=".urlencode((string) ($startdateyear));
220}
221if (!empty($enddateday)) {
222 $param .= "&amp;enddateday=".urlencode((string) ($enddateday));
223}
224if (!empty($enddatemonth)) {
225 $param .= "&amp;enddatemonth=".urlencode((string) ($enddatemonth));
226}
227if (!empty($enddateyear)) {
228 $param .= "&amp;enddateyear=".urlencode((string) ($enddateyear));
229}
230
231$totalMargin = 0;
232$marginRate = '';
233$markRate = '';
234dol_syslog('margin::agentMargins.php', LOG_DEBUG);
235$result = $db->query($sql);
236if ($result) {
237 $num = $db->num_rows($result);
238
239 print '<br>';
240 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition, PhanPluginSuspiciousParamOrder
241 print_barre_liste($langs->trans("MarginDetails"), $page, $_SERVER["PHP_SELF"], "", $sortfield, $sortorder, '', $num, $num, '', 0, '', '', 0, 1);
242
243 if (getDolGlobalString('MARGIN_TYPE') == "1") {
244 $labelcostprice = 'BuyingPrice';
245 } else { // value is 'costprice' or 'pmp'
246 $labelcostprice = 'CostPrice';
247 }
248
249 $moreforfilter = '';
250
251 $i = 0;
252 print '<div class="div-table-responsive">';
253 print '<table class="tagtable liste'.($moreforfilter ? " listwithfilterbefore" : "").'">'."\n";
254
255 print '<tr class="liste_titre">';
256 if ($agentid > 0) {
257 print_liste_field_titre("Customer", $_SERVER["PHP_SELF"], "s.nom", "", $param, '', $sortfield, $sortorder);
258 } else {
259 print_liste_field_titre("SalesRepresentative", $_SERVER["PHP_SELF"], "u.lastname", "", $param, '', $sortfield, $sortorder);
260 }
261
262 print_liste_field_titre("SellingPrice", $_SERVER["PHP_SELF"], "selling_price", "", $param, '', $sortfield, $sortorder, 'right ');
263 print_liste_field_titre($labelcostprice, $_SERVER["PHP_SELF"], "buying_price", "", $param, '', $sortfield, $sortorder, 'right ');
264 print_liste_field_titre("Margin", $_SERVER["PHP_SELF"], "marge", "", $param, '', $sortfield, $sortorder, 'right ');
265 if (getDolGlobalString('DISPLAY_MARGIN_RATES')) {
266 print_liste_field_titre("MarginRate", $_SERVER["PHP_SELF"], "", "", $param, '', $sortfield, $sortorder, 'right ');
267 }
268 if (getDolGlobalString('DISPLAY_MARK_RATES')) {
269 print_liste_field_titre("MarkRate", $_SERVER["PHP_SELF"], "", "", $param, '', $sortfield, $sortorder, 'right ');
270 }
271 print "</tr>\n";
272
273 if ($num > 0) {
274 $group_list = array();
275 while ($objp = $db->fetch_object($result)) {
276 if ($agentid > 0) {
277 $group_id = $objp->socid;
278 } else {
279 $group_id = $objp->agent;
280 }
281
282 if (!isset($group_list[$group_id])) {
283 if ($agentid > 0) {
284 $group_name = $objp->name;
285 $companystatic->id = $objp->socid;
286 $companystatic->name = $objp->name;
287 $companystatic->client = $objp->client;
288 $group_htmlname = $companystatic->getNomUrl(1, 'customer');
289 } else {
290 $group_name = $objp->lastname;
291 $userstatic->fetch($objp->agent);
292 $group_htmlname = $userstatic->getFullName($langs, 0, 0, 0);
293 }
294 $group_list[$group_id] = array('name' => $group_name, 'htmlname' => $group_htmlname, 'selling_price' => 0, 'buying_price' => 0, 'marge' => 0);
295 }
296
297 $seller_nb = 1;
298 if ($objp->socid > 0) {
299 // sql nb sellers
300 $sql_seller = "SELECT COUNT(sc.rowid) as nb";
301 $sql_seller .= " FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc";
302 $sql_seller .= " WHERE sc.fk_soc = ".((int) $objp->socid);
303 $sql_seller .= " LIMIT 1";
304
305 $resql_seller = $db->query($sql_seller);
306 if (!$resql_seller) {
307 dol_print_error($db);
308 } else {
309 if ($obj_seller = $db->fetch_object($resql_seller)) {
310 if ($obj_seller->nb > 0) {
311 $seller_nb = $obj_seller->nb;
312 }
313 }
314 }
315 }
316
317 $group_list[$group_id]['selling_price'] += $objp->selling_price / $seller_nb;
318 $group_list[$group_id]['buying_price'] += $objp->buying_price / $seller_nb;
319 $group_list[$group_id]['marge'] += $objp->marge / $seller_nb;
320 }
321
322 // sort group array by sortfield
323 if ($sortfield == 'u.lastname' || $sortfield == 's.nom') {
324 $sortfield = 'name';
325 }
326 $group_list = dol_sort_array($group_list, $sortfield, $sortorder);
327 $cumul_achat = 0;
328 $cumul_vente = 0;
329 foreach ($group_list as $group_id => $group_array) {
330 $pa = $group_array['buying_price'];
331 $pv = $group_array['selling_price'];
332 $marge = $group_array['marge'];
333
334 $marginRate = ($pa != 0) ? (100 * $marge / $pa) : '';
335 $markRate = ($pv != 0) ? (100 * $marge / $pv) : '';
336
337 print '<tr class="oddeven">';
338 print "<td>".$group_array['htmlname']."</td>\n";
339 print '<td class="nowrap right"><span class="amount">'.price(price2num($pv, 'MT')).'</span></td>';
340 print '<td class="nowrap right"><span class="amount">'.price(price2num($pa, 'MT')).'</span></td>';
341 print '<td class="nowrap right"><span class="amount">'.price(price2num($marge, 'MT')).'</span></td>';
342 if (getDolGlobalString('DISPLAY_MARGIN_RATES')) {
343 print '<td class="nowrap right">'.(($marginRate === '') ? 'n/a' : price(price2num($marginRate, 'MT'))."%").'</td>';
344 }
345 if (getDolGlobalString('DISPLAY_MARK_RATES')) {
346 print '<td class="nowrap right">'.(($markRate === '') ? 'n/a' : price(price2num($markRate, 'MT'))."%").'</td>';
347 }
348 print "</tr>\n";
349
350 $i++;
351 $cumul_achat += $pa;
352 $cumul_vente += $pv;
353 }
354 }
355
356 // Show total margin
357 if (!isset($cumul_achat)) {
358 $cumul_achat = 0;
359 }
360 if (!isset($cumul_vente)) {
361 $cumul_vente = 0;
362 }
363 $totalMargin = $cumul_vente - $cumul_achat;
364
365 $marginRate = ($cumul_achat != 0) ? (100 * $totalMargin / $cumul_achat) : '';
366 $markRate = ($cumul_vente != 0) ? (100 * $totalMargin / $cumul_vente) : '';
367
368 print '<tr class="liste_total">';
369 print '<td>';
370 print $langs->trans('TotalMargin')."</td>";
371 print '<td class="nowrap right">'.price(price2num($cumul_vente, 'MT')).'</td>';
372 print '<td class="nowrap right">'.price(price2num($cumul_achat, 'MT')).'</td>';
373 print '<td class="nowrap right">'.price(price2num($totalMargin, 'MT')).'</td>';
374 if (getDolGlobalString('DISPLAY_MARGIN_RATES')) {
375 print '<td class="nowrap right">'.(($marginRate === '') ? 'n/a' : price(price2num($marginRate, 'MT'))."%").'</td>';
376 }
377 if (getDolGlobalString('DISPLAY_MARK_RATES')) {
378 print '<td class="nowrap right">'.(($markRate === '') ? 'n/a' : price(price2num($markRate, 'MT'))."%").'</td>';
379 }
380 print '</tr>';
381
382 print '</table>';
383 print '</div>';
384} else {
385 dol_print_error($db);
386}
387$db->free($result);
388
389print "\n".'<script type="text/javascript">
390$(document).ready(function() {
391 console.log("Init some values");
392 $("#totalMargin").html("'.price(price2num($totalMargin, 'MT')).'");
393 $("#marginRate").html("'.(($marginRate === '') ? 'n/a' : price(price2num($marginRate, 'MT'))."%").'");
394 $("#markRate").html("'.(($markRate === '') ? 'n/a' : price(price2num($markRate, 'MT'))."%").'");
395});
396</script>'."\n";
397
398// End of page
399llxFooter();
400$db->close();
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:58
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:70
Class to manage invoices.
const STATUS_DRAFT
Draft status.
const STATUS_ABANDONED
Classified abandoned and no payment done.
Class to manage generation of HTML components Only common components must be here.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage Dolibarr users.
llxFooter()
Footer empty.
Definition document.php:107
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
print_barre_liste($title, $page, $file, $options='', $sortfield='', $sortorder='', $morehtmlcenter='', $num=-1, $totalnboflines='', $picto='generic', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limit=-1, $selectlimitsuffix=0, $hidenavigation=0, $pagenavastextinput=0, $morehtmlrightbeforearrow='')
Print a title with navigation controls for pagination.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0)
Show tabs of a record.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
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.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by the value of a given key, which produces ascending (default) or descending out...
print_liste_field_titre($name, $file="", $field="", $begin="", $moreparam="", $moreattrib="", $sortfield="", $sortorder="", $prefix="", $tooltip="", $forcenowrapcolumntitle=0)
Show title line of an array.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
marges_prepare_head()
Return array of tabs to used on pages for third parties cards.
restrictedArea(User $user, $features, $object=0, $tableandshare='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid', $isdraft=0, $mode=0)
Check permissions of a user to show a page and an object.