dolibarr 21.0.0-beta
customerMargins.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) 2024 MDW <mdeweerd@users.noreply.github.com>
5 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
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
42// Load translation files required by the page
43$langs->loadLangs(array('companies', 'bills', 'products', 'margins'));
44
45// Load variable for pagination
46$limit = GETPOSTINT('limit') ? GETPOSTINT('limit') : $conf->liste_limit;
47$sortfield = GETPOST('sortfield', 'aZ09comma');
48$sortorder = GETPOST('sortorder', 'aZ09comma');
49$page = GETPOSTISSET('pageplusone') ? (GETPOSTINT('pageplusone') - 1) : GETPOSTINT("page");
50if (empty($page) || $page == -1) {
51 $page = 0;
52} // If $page is not defined, or '' or -1
53$offset = $limit * $page;
54$pageprev = $page - 1;
55$pagenext = $page + 1;
56if (!$sortfield) {
57 $sortfield = "s.nom"; // Set here default search field
58}
59if (!$sortorder) {
60 $sortorder = "ASC";
61}
62
63$startdate = $enddate = '';
64if (GETPOST('startdatemonth')) {
65 $startdate = dol_mktime(0, 0, 0, GETPOSTINT('startdatemonth'), GETPOSTINT('startdateday'), GETPOSTINT('startdateyear'));
66}
67if (GETPOST('enddatemonth')) {
68 $enddate = dol_mktime(23, 59, 59, GETPOSTINT('enddatemonth'), GETPOSTINT('enddateday'), GETPOST('enddateyear'));
69}
70
71// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
72$object = new Societe($db);
73$hookmanager->initHooks(array('margincustomerlist'));
74
75// Security check
76$socid = GETPOSTINT('socid');
77$TSelectedProducts = GETPOST('products', 'array');
78$TSelectedCats = GETPOST('categories', 'array');
79
80if (!empty($user->socid)) {
81 $socid = $user->socid;
82}
83$result = restrictedArea($user, 'societe', '', '');
84$result = restrictedArea($user, 'margins');
85
86
87/*
88 * View
89 */
90
91$companystatic = new Societe($db);
92$invoicestatic = new Facture($db);
93
94$form = new Form($db);
95
96llxHeader('', $langs->trans("Margins").' - '.$langs->trans("Clients"), '', '', 0, 0, '', '', '', 'mod-margin page-customermargins');
97
98$text = $langs->trans("Margins");
99//print load_fiche_titre($text);
100
101// Show tabs
102$head = marges_prepare_head();
103
104$titre = $langs->trans("Margins");
105$picto = 'margin';
106
107
108print '<form method="post" name="sel" action="'.$_SERVER['PHP_SELF'].'">';
109print '<input type="hidden" name="token" value="'.newToken().'">';
110
111print dol_get_fiche_head($head, 'customerMargins', $titre, 0, $picto);
112
113print '<table class="border centpercent">';
114
115$client = false;
116if ($socid > 0) {
117 $soc = new Societe($db);
118 $soc->fetch($socid);
119
120 if ($soc->client) {
121 print '<tr><td class="titlefield">'.$langs->trans('ThirdPartyName').'</td>';
122 print '<td class="maxwidthonsmartphone" colspan="4">';
123 $filter = '(client:IN:1,3)';
124 print img_picto('', 'company').$form->select_company($socid, 'socid', $filter, 1, 0, 0);
125 print '</td></tr>';
126
127 $client = true;
128 if (!$sortorder) {
129 $sortorder = "DESC";
130 }
131 if (!$sortfield) {
132 $sortfield = "f.datef";
133 }
134 }
135} else {
136 print '<tr><td class="titlefield">'.$langs->trans('ThirdPartyName').'</td>';
137 print '<td class="maxwidthonsmartphone" colspan="4">';
138 print img_picto('', 'company').$form->select_company(null, 'socid', '((client:=:1) OR (client:=:3))', 1, 0, 0);
139 print '</td></tr>';
140}
141
142$sortfield = GETPOST('sortfield', 'aZ09comma');
143$sortorder = GETPOST('sortorder', 'aZ09comma');
144if (!$sortorder) {
145 $sortorder = "ASC";
146}
147if (!$sortfield) {
148 if ($client) {
149 $sortfield = "f.datef";
150 $sortorder = "DESC";
151 } else {
152 $sortfield = "s.nom";
153 $sortorder = "ASC";
154 }
155}
156
157// Products
158$TRes = $form->select_produits_list('', '', '', '', 0, '', 1, 2, 1, 0, '', 1);
159
160$TProducts = array();
161foreach ($TRes as $prod) {
162 $TProducts[$prod['key']] = $prod['label'];
163}
164
165print '<tr><td class="titlefield">'.$langs->trans('ProductOrService').'</td>';
166print '<td class="maxwidthonsmartphone" colspan="4">';
167print img_picto('', 'product').$form->multiselectarray('products', $TProducts, $TSelectedProducts, 0, 0, 'minwidth500');
168print '</td></tr>';
169
170// Categories
171$TCats = $form->select_all_categories('product', array(), '', 64, 0, 3);
172
173print '<tr>';
174print '<td class="titlefield">'.$langs->trans('Category').'</td>';
175print '<td class="maxwidthonsmartphone" colspan="4">';
176print img_picto('', 'category', 'class="pictofixedwidth"').$form->multiselectarray('categories', $TCats, $TSelectedCats, 0, 0, 'quatrevingtpercent widthcentpercentminusx');
177print '</td>';
178print '</tr>';
179
180// Start date
181print '<td>'.$langs->trans('DateStart').' ('.$langs->trans("DateValidation").')</td>';
182print '<td>';
183print $form->selectDate($startdate, 'startdate', 0, 0, 1, "sel", 1, 1);
184print '</td>';
185print '<td>'.$langs->trans('DateEnd').' ('.$langs->trans("DateValidation").')</td>';
186print '<td>';
187print $form->selectDate($enddate, 'enddate', 0, 0, 1, "sel", 1, 1);
188print '</td>';
189print '<td style="text-align: center;">';
190print '<input type="submit" class="button" value="'.dol_escape_htmltag($langs->trans('Refresh')).'" />';
191print '</td></tr>';
192
193print "</table>";
194
195print '<br>';
196
197print '<table class="border centpercent">';
198
199// Total Margin
200print '<tr><td class="titlefield">'.$langs->trans("TotalMargin").'</td><td colspan="4">';
201print '<span id="totalMargin" class="amount"></span> <span class="amount">'.$langs->getCurrencySymbol($conf->currency).'</span>'; // set by jquery (see below)
202print '</td></tr>';
203
204// Margin Rate
205if (getDolGlobalString('DISPLAY_MARGIN_RATES')) {
206 print '<tr><td>'.$langs->trans("MarginRate").'</td><td colspan="4">';
207 print '<span id="marginRate"></span>'; // set by jquery (see below)
208 print '</td></tr>';
209}
210
211// Mark Rate
212if (getDolGlobalString('DISPLAY_MARK_RATES')) {
213 print '<tr><td>'.$langs->trans("MarkRate").'</td><td colspan="4">';
214 print '<span id="markRate"></span>'; // set by jquery (see below)
215 print '</td></tr>';
216}
217
218print "</table>";
219
220print dol_get_fiche_end();
221
222print '</form>';
223
224$invoice_status_except_list = array(Facture::STATUS_DRAFT, Facture::STATUS_ABANDONED);
225
226$sql = "SELECT";
227$sql .= " s.rowid as socid, s.nom as name, s.code_client, s.client,";
228if ($client) {
229 $sql .= " f.rowid as facid, f.ref, f.total_ht, f.datef, f.paye, f.type, f.fk_statut as statut,";
230}
231$sql .= " sum(d.total_ht) as selling_price,";
232// Note: qty and buy_price_ht is always positive (if not, your database may be corrupted, you can update this)
233
234$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,";
235$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";
236
237$sql .= " FROM ".MAIN_DB_PREFIX."societe as s";
238$sql .= ", ".MAIN_DB_PREFIX."facture as f";
239$sql .= ", ".MAIN_DB_PREFIX."facturedet as d";
240if (!empty($TSelectedCats)) {
241 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_product as cp ON cp.fk_product=d.fk_product';
242}
243
244if (!$user->hasRight('societe', 'client', 'voir')) {
245 $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
246}
247$sql .= " WHERE f.fk_soc = s.rowid";
248if ($socid > 0) {
249 $sql .= ' AND s.rowid = '.((int) $socid);
250}
251if (!$user->hasRight('societe', 'client', 'voir')) {
252 $sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
253}
254$sql .= " AND f.fk_statut NOT IN (".$db->sanitize(implode(', ', $invoice_status_except_list)).")";
255$sql .= ' AND s.entity IN ('.getEntity('societe').')';
256$sql .= ' AND f.entity IN ('.getEntity('invoice').')';
257$sql .= " AND d.fk_facture = f.rowid";
258$sql .= " AND (d.product_type = 0 OR d.product_type = 1)";
259if (!empty($TSelectedProducts)) {
260 $sql .= ' AND d.fk_product IN ('.$db->sanitize(implode(',', $TSelectedProducts)).')';
261}
262if (!empty($TSelectedCats)) {
263 $sql .= ' AND cp.fk_categorie IN ('.$db->sanitize(implode(',', $TSelectedCats)).')';
264}
265if (!empty($startdate)) {
266 $sql .= " AND f.datef >= '".$db->idate($startdate)."'";
267}
268if (!empty($enddate)) {
269 $sql .= " AND f.datef <= '".$db->idate($enddate)."'";
270}
271$sql .= " AND d.buy_price_ht IS NOT NULL";
272// 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.
273// We keep it with value ForceBuyingPriceIfNull = 2 for retroactive effect but results are unpredictable.
274if (getDolGlobalInt('ForceBuyingPriceIfNull') == 2) {
275 $sql .= " AND d.buy_price_ht <> 0";
276}
277if ($client) {
278 $sql .= " GROUP BY s.rowid, s.nom, s.code_client, s.client, f.rowid, f.ref, f.total_ht, f.datef, f.paye, f.type, f.fk_statut";
279} else {
280 $sql .= " GROUP BY s.rowid, s.nom, s.code_client, s.client";
281}
282$sql .= $db->order($sortfield, $sortorder);
283// TODO: calculate total to display then restore pagination
284//$sql.= $db->plimit($conf->liste_limit +1, $offset);
285
286$param = '&socid='.((int) $socid);
287if (GETPOSTINT('startdatemonth')) {
288 $param .= '&startdateyear='.GETPOSTINT('startdateyear');
289 $param .= '&startdatemonth='.GETPOSTINT('startdatemonth');
290 $param .= '&startdateday='.GETPOSTINT('startdateday');
291}
292if (GETPOSTINT('enddatemonth')) {
293 $param .= '&enddateyear='.GETPOSTINT('enddateyear');
294 $param .= '&enddatemonth='.GETPOSTINT('enddatemonth');
295 $param .= '&enddateday='.GETPOSTINT('enddateday');
296}
297$listofproducts = GETPOST('products', 'array:int');
298if (is_array($listofproducts)) {
299 foreach ($listofproducts as $val) {
300 $param .= '&products[]='.$val;
301 }
302}
303$listofcateg = GETPOST('categories', 'array:int');
304if (is_array($listofcateg)) {
305 foreach ($listofcateg as $val) {
306 $param .= '&categories[]='.$val;
307 }
308}
309
310$totalMargin = 0;
311$marginRate = '';
312$markRate = '';
313dol_syslog('margin::customerMargins.php', LOG_DEBUG);
314$result = $db->query($sql);
315if ($result) {
316 $num = $db->num_rows($result);
317
318 print '<br>';
319 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition, PhanPluginSuspiciousParamOrder
320 print_barre_liste($langs->trans("MarginDetails"), $page, $_SERVER["PHP_SELF"], "", $sortfield, $sortorder, '', $num, $num, '', 0, '', '', 0, 1);
321
322 if (getDolGlobalString('MARGIN_TYPE') == "1") {
323 $labelcostprice = 'BuyingPrice';
324 } else { // value is 'costprice' or 'pmp'
325 $labelcostprice = 'CostPrice';
326 }
327
328 $moreforfilter = '';
329
330 $i = 0;
331 print '<div class="div-table-responsive">';
332 print '<table class="tagtable liste'.($moreforfilter ? " listwithfilterbefore" : "").'">'."\n";
333
334 print '<tr class="liste_titre">';
335 if (!empty($client)) {
336 print_liste_field_titre("Invoice", $_SERVER["PHP_SELF"], "f.ref", "", $param, '', $sortfield, $sortorder);
337 print_liste_field_titre("DateInvoice", $_SERVER["PHP_SELF"], "f.datef", "", $param, 'align="center"', $sortfield, $sortorder);
338 } else {
339 print_liste_field_titre("Customer", $_SERVER["PHP_SELF"], "s.nom", "", $param, '', $sortfield, $sortorder);
340 }
341 print_liste_field_titre("SellingPrice", $_SERVER["PHP_SELF"], "selling_price", "", $param, 'align="right"', $sortfield, $sortorder);
342 print_liste_field_titre($labelcostprice, $_SERVER["PHP_SELF"], "buying_price", "", $param, 'align="right"', $sortfield, $sortorder);
343 print_liste_field_titre("Margin", $_SERVER["PHP_SELF"], "marge", "", $param, 'align="right"', $sortfield, $sortorder);
344 if (getDolGlobalString('DISPLAY_MARGIN_RATES')) {
345 print_liste_field_titre("MarginRate", $_SERVER["PHP_SELF"], "", "", $param, 'align="right"', $sortfield, $sortorder);
346 }
347 if (getDolGlobalString('DISPLAY_MARK_RATES')) {
348 print_liste_field_titre("MarkRate", $_SERVER["PHP_SELF"], "", "", $param, 'align="right"', $sortfield, $sortorder);
349 }
350 print "</tr>\n";
351
352 $cumul_achat = 0;
353 $cumul_vente = 0;
354
355 if ($num > 0) {
356 while ($i < $num /*&& $i < $conf->liste_limit*/) {
357 $objp = $db->fetch_object($result);
358
359 $pa = $objp->buying_price;
360 $pv = $objp->selling_price;
361 $marge = $objp->marge;
362
363 if ($marge < 0) {
364 $marginRate = ($pa != 0) ? -1 * (100 * $marge / $pa) : '';
365 $markRate = ($pv != 0) ? -1 * (100 * $marge / $pv) : '';
366 } else {
367 $marginRate = ($pa != 0) ? (100 * $marge / $pa) : '';
368 $markRate = ($pv != 0) ? (100 * $marge / $pv) : '';
369 }
370
371 print '<tr class="oddeven">';
372 if ($client) {
373 $invoicestatic->id = $objp->facid;
374 $invoicestatic->ref = $objp->ref;
375 $invoicestatic->statut = $objp->statut;
376 $invoicestatic->type = $objp->type;
377
378 print '<td>';
379 print $invoicestatic->getNomUrl(1);
380 print '</td>';
381 print '<td class="center">';
382 print dol_print_date($db->jdate($objp->datef), 'day').'</td>';
383 } else {
384 $companystatic->id = $objp->socid;
385 $companystatic->name = $objp->name;
386 $companystatic->client = $objp->client;
387
388 print '<td>'.$companystatic->getNomUrl(1, 'margin').'</td>';
389 }
390
391 print '<td class="nowrap right"><span class="amount">'.price(price2num($pv, 'MT')).'</span></td>';
392 print '<td class="nowrap right"><span class="amount">'.price(price2num($pa, 'MT')).'</span></td>';
393 print '<td class="nowrap right"><span class="amount">'.price(price2num($marge, 'MT')).'</span></td>';
394 if (getDolGlobalString('DISPLAY_MARGIN_RATES')) {
395 print '<td class="nowrap right">'.(($marginRate === '') ? 'n/a' : price(price2num($marginRate, 'MT'))."%").'</td>';
396 }
397 if (getDolGlobalString('DISPLAY_MARK_RATES')) {
398 print '<td class="nowrap right">'.(($markRate === '') ? 'n/a' : price(price2num($markRate, 'MT'))."%").'</td>';
399 }
400 print "</tr>\n";
401
402 $i++;
403 $cumul_achat += $objp->buying_price;
404 $cumul_vente += $objp->selling_price;
405 }
406 }
407
408 // affichage totaux marges
409
410 $totalMargin = $cumul_vente - $cumul_achat;
411 /*if ($totalMargin < 0)
412 {
413 $marginRate = ($cumul_achat != 0)?-1*(100 * $totalMargin / $cumul_achat):'';
414 $markRate = ($cumul_vente != 0)?-1*(100 * $totalMargin / $cumul_vente):'';
415 }
416 else
417 {*/
418 $marginRate = ($cumul_achat != 0) ? (100 * $totalMargin / $cumul_achat) : '';
419 $markRate = ($cumul_vente != 0) ? (100 * $totalMargin / $cumul_vente) : '';
420 //}
421
422 print '<tr class="liste_total">';
423 if ($client) {
424 print '<td colspan="2">';
425 } else {
426 print '<td>';
427 }
428 print $langs->trans('TotalMargin')."</td>";
429 print '<td class="nowrap right">'.price(price2num($cumul_vente, 'MT')).'</td>';
430 print '<td class="nowrap right">'.price(price2num($cumul_achat, 'MT')).'</td>';
431 print '<td class="nowrap right">'.price(price2num($totalMargin, 'MT')).'</td>';
432 if (getDolGlobalString('DISPLAY_MARGIN_RATES')) {
433 print '<td class="nowrap right">'.(($marginRate === '') ? 'n/a' : price(price2num($marginRate, 'MT'))."%").'</td>';
434 }
435 if (getDolGlobalString('DISPLAY_MARK_RATES')) {
436 print '<td class="nowrap right">'.(($markRate === '') ? 'n/a' : price(price2num($markRate, 'MT'))."%").'</td>';
437 }
438 print '</tr>';
439
440 print '</table>';
441 print '</div>';
442} else {
443 dol_print_error($db);
444}
445$db->free($result);
446
447print '<script type="text/javascript">
448$(document).ready(function() {
449 console.log("Init some values");
450 $("#totalMargin").html("'.price(price2num($totalMargin, 'MT')).'");
451 $("#marginRate").html("'.(($marginRate === '') ? 'n/a' : price(price2num($marginRate, 'MT'))."%").'");
452 $("#markRate").html("'.(($markRate === '') ? 'n/a' : price(price2num($markRate, 'MT'))."%").'");
453});
454</script>
455';
456
457// End of page
458llxFooter();
459$db->close();
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:66
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:71
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...)
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_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
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.
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
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.