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