dolibarr 21.0.0-alpha
productMargins.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) 2020 Alexandre Spangaro <aspangaro@open-dsi.fr>
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$id = GETPOSTINT('id');
37$ref = GETPOST('ref', 'alpha');
38$action = GETPOST('action', 'aZ09');
39$confirm = GETPOST('confirm', 'alpha');
40$TSelectedCats = GETPOST('categories', 'array');
41$socid = 0;
42
43$mesg = '';
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 if ($id > 0) {
58 $sortfield = "f.datef";
59 $sortorder = "DESC";
60 } else {
61 $sortfield = "p.ref";
62 $sortorder = "ASC";
63 }
64}
65
66$startdate = $enddate = '';
67if (GETPOST('startdatemonth')) {
68 $startdate = dol_mktime(0, 0, 0, GETPOSTINT('startdatemonth'), GETPOSTINT('startdateday'), GETPOSTINT('startdateyear'));
69}
70if (GETPOST('enddatemonth')) {
71 $enddate = dol_mktime(23, 59, 59, GETPOSTINT('enddatemonth'), GETPOSTINT('enddateday'), GETPOST('enddateyear'));
72}
73
74// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
75$object = new Product($db);
76$hookmanager->initHooks(array('marginproductlist'));
77
78// Security check
79$fieldvalue = (!empty($id) ? $id : (!empty($ref) ? $ref : ''));
80$fieldtype = (!empty($ref) ? 'ref' : 'rowid');
81if (!empty($user->socid)) {
82 $socid = $user->socid;
83}
84$result = restrictedArea($user, 'produit|service', $fieldvalue, 'product&product', '', '', $fieldtype);
85if (!$user->hasRight('margins', 'liretous')) {
87}
88
89
90/*
91 * View
92 */
93
94$product_static = new Product($db);
95$invoicestatic = new Facture($db);
96
97$form = new Form($db);
98
99llxHeader('', $langs->trans("Margins").' - '.$langs->trans("Products"), '', '', 0, 0, '', '', '', 'mod-margin page-productmargins');
100
101$text = $langs->trans("Margins");
102//print load_fiche_titre($text);
103
104// Show tabs
105$head = marges_prepare_head();
106
107$titre = $langs->trans("Margins");
108$picto = 'margin';
109
110print '<form method="post" name="sel" action="'.$_SERVER['PHP_SELF'].'">';
111print '<input type="hidden" name="token" value="'.newToken().'">';
112
113print dol_get_fiche_head($head, 'productMargins', $titre, 0, $picto);
114
115print '<table class="border centpercent">';
116
117// Product
118print '<tr><td class="titlefield">'.$langs->trans('ProductOrService').'</td>';
119print '<td class="maxwidthonsmartphone" colspan="4">';
120print img_picto('', 'product').$form->select_produits(($id > 0 ? $id : ''), 'id', '', 20, 0, 1, 2, '', 1, array(), 0, 'All', 0, '', 0, '', null, 1);
121print '</td></tr>';
122
123// Categories
124$TCats = $form->select_all_categories('product', array(), '', 64, 0, 3);
125
126print '<tr>';
127print '<td class="titlefield">'.$langs->trans('Category').'</td>';
128print '<td class="maxwidthonsmartphone" colspan="4">';
129print img_picto('', 'category', 'class="pictofixedwidth"').$form->multiselectarray('categories', $TCats, $TSelectedCats, 0, 0, 'quatrevingtpercent widthcentpercentminusx');
130print '</td>';
131print '</tr>';
132
133// Start date
134print '<tr>';
135print '<td class="titlefield">'.$langs->trans('DateStart').' ('.$langs->trans("DateValidation").')</td>';
136print '<td>';
137print $form->selectDate($startdate, 'startdate', 0, 0, 1, "sel", 1, 1);
138print '</td>';
139print '<td>'.$langs->trans('DateEnd').' ('.$langs->trans("DateValidation").')</td>';
140print '<td>';
141print $form->selectDate($enddate, 'enddate', 0, 0, 1, "sel", 1, 1);
142print '</td>';
143print '<td style="text-align: center;">';
144print '<input type="submit" class="button" value="'.dol_escape_htmltag($langs->trans('Refresh')).'" />';
145print '</td></tr>';
146
147print "</table>";
148
149print '<br>';
150
151print '<table class="border centpercent">';
152
153// Total Margin
154print '<tr><td class="titlefield">'.$langs->trans("TotalMargin").'</td><td colspan="4">';
155print '<span id="totalMargin" class="amount"></span> <span class="amount">'.$langs->getCurrencySymbol($conf->currency).'</span>'; // set by jquery (see below)
156print '</td></tr>';
157
158// Margin Rate
159if (getDolGlobalString('DISPLAY_MARGIN_RATES')) {
160 print '<tr><td>'.$langs->trans("MarginRate").'</td><td colspan="4">';
161 print '<span id="marginRate"></span>'; // set by jquery (see below)
162 print '</td></tr>';
163}
164
165// Mark Rate
166if (getDolGlobalString('DISPLAY_MARK_RATES')) {
167 print '<tr><td>'.$langs->trans("MarkRate").'</td><td colspan="4">';
168 print '<span id="markRate"></span>'; // set by jquery (see below)
169 print '</td></tr>';
170}
171
172print "</table>";
173
174print dol_get_fiche_end();
175
176print '</form>';
177
178$invoice_status_except_list = array(Facture::STATUS_DRAFT, Facture::STATUS_ABANDONED);
179
180$sql = "SELECT p.label, p.rowid, p.fk_product_type, p.ref, p.entity as pentity,";
181if ($id > 0) {
182 $sql .= " d.fk_product,";
183}
184if ($id > 0) {
185 $sql .= " f.rowid as facid, f.ref, f.total_ht, f.datef, f.paye, f.fk_statut as statut,";
186}
187$sql .= " SUM(d.total_ht) as selling_price,";
188$sql .= " SUM(d.qty) as product_qty,";
189
190// Note: qty and buy_price_ht is always positive (if not your database may be corrupted, you can update this)
191$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,";
192$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";
193
194$sql .= " FROM ".MAIN_DB_PREFIX."societe as s";
195$sql .= ", ".MAIN_DB_PREFIX."facture as f";
196$sql .= ", ".MAIN_DB_PREFIX."facturedet as d";
197$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = d.fk_product";
198if (!empty($TSelectedCats)) {
199 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_product as cp ON cp.fk_product=p.rowid';
200}
201$sql .= " WHERE f.fk_soc = s.rowid";
202$sql .= ' AND f.entity IN ('.getEntity('invoice').')';
203$sql .= " AND f.fk_statut NOT IN (".$db->sanitize(implode(', ', $invoice_status_except_list)).")";
204$sql .= " AND d.fk_facture = f.rowid";
205if ($id > 0) {
206 $sql .= " AND d.fk_product =".((int) $id);
207}
208if (!empty($TSelectedCats)) {
209 $sql .= ' AND cp.fk_categorie IN ('.$db->sanitize(implode(',', $TSelectedCats)).')';
210}
211if (!empty($startdate)) {
212 $sql .= " AND f.datef >= '".$db->idate($startdate)."'";
213}
214if (!empty($enddate)) {
215 $sql .= " AND f.datef <= '".$db->idate($enddate)."'";
216}
217$sql .= " AND d.buy_price_ht IS NOT NULL";
218// 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.
219// We keep it with value ForceBuyingPriceIfNull = 2 for retroactive effect but results are unpredictable.
220if (getDolGlobalInt('ForceBuyingPriceIfNull') == 2) {
221 $sql .= " AND d.buy_price_ht <> 0";
222}
223if ($id > 0) {
224 $sql .= " GROUP BY p.label, p.rowid, p.fk_product_type, p.ref, p.entity, d.fk_product, f.rowid, f.ref, f.total_ht, f.datef, f.paye, f.fk_statut";
225} else {
226 $sql .= " GROUP BY p.label, p.rowid, p.fk_product_type, p.ref, p.entity";
227}
228$sql .= $db->order($sortfield, $sortorder);
229// TODO: calculate total to display then restore pagination
230//$sql.= $db->plimit($conf->liste_limit +1, $offset);
231
232$param = '&id='.((int) $id);
233if (GETPOSTINT('startdatemonth')) {
234 $param .= '&startdateyear='.GETPOSTINT('startdateyear');
235 $param .= '&startdatemonth='.GETPOSTINT('startdatemonth');
236 $param .= '&startdateday='.GETPOSTINT('startdateday');
237}
238if (GETPOSTINT('enddatemonth')) {
239 $param .= '&enddateyear='.GETPOSTINT('enddateyear');
240 $param .= '&enddatemonth='.GETPOSTINT('enddatemonth');
241 $param .= '&enddateday='.GETPOSTINT('enddateday');
242}
243$listofcateg = GETPOST('categories', 'array:int');
244if (is_array($listofcateg)) {
245 foreach ($listofcateg as $val) {
246 $param .= '&categories[]='.$val;
247 }
248}
249
250$totalMargin = 0;
251$marginRate = '';
252$markRate = '';
253dol_syslog('margin::productMargins.php', LOG_DEBUG);
254$result = $db->query($sql);
255if ($result) {
256 $num = $db->num_rows($result);
257
258 print '<br>';
259 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition, PhanPluginSuspiciousParamOrder
260 print_barre_liste($langs->trans("MarginDetails"), $page, $_SERVER["PHP_SELF"], $param, $sortfield, $sortorder, '', $num, $num, '', 0, '', '', 0, 1);
261
262 //var_dump($conf->global->MARGIN_TYPE);
263 if (getDolGlobalString('MARGIN_TYPE') == "1") {
264 $labelcostprice = 'BuyingPrice';
265 } else { // value is 'costprice' or 'pmp'
266 $labelcostprice = 'CostPrice';
267 }
268
269 $moreforfilter = '';
270
271 $i = 0;
272 print '<div class="div-table-responsive">';
273 print '<table class="tagtable liste'.($moreforfilter ? " listwithfilterbefore" : "").'">'."\n";
274
275 print '<tr class="liste_titre">';
276 if ($id > 0) {
277 print_liste_field_titre("Invoice", $_SERVER["PHP_SELF"], "f.ref", "", $param, '', $sortfield, $sortorder);
278 print_liste_field_titre("DateInvoice", $_SERVER["PHP_SELF"], "f.datef", "", $param, '', $sortfield, $sortorder, 'center ');
279 } else {
280 print_liste_field_titre("ProductService", $_SERVER["PHP_SELF"], "p.ref", "", $param, '', $sortfield, $sortorder);
281 }
282 print_liste_field_titre("Qty", $_SERVER["PHP_SELF"], "product_qty", "", $param, '', $sortfield, $sortorder, 'center ');
283 print_liste_field_titre("SellingPrice", $_SERVER["PHP_SELF"], "selling_price", "", $param, '', $sortfield, $sortorder, 'right ');
284 print_liste_field_titre($labelcostprice, $_SERVER["PHP_SELF"], "buying_price", "", $param, '', $sortfield, $sortorder, 'right ');
285 print_liste_field_titre("Margin", $_SERVER["PHP_SELF"], "marge", "", $param, '', $sortfield, $sortorder, 'right ');
286 if (getDolGlobalString('DISPLAY_MARGIN_RATES')) {
287 print_liste_field_titre("MarginRate", $_SERVER["PHP_SELF"], "", "", $param, '', $sortfield, $sortorder, 'right ');
288 }
289 if (getDolGlobalString('DISPLAY_MARK_RATES')) {
290 print_liste_field_titre("MarkRate", $_SERVER["PHP_SELF"], "", "", $param, '', $sortfield, $sortorder, 'right ');
291 }
292 print "</tr>\n";
293
294 $cumul_achat = 0;
295 $cumul_vente = 0;
296 $cumul_qty = 0;
297
298 if ($num > 0) {
299 while ($i < $num /*&& $i < $conf->liste_limit*/) {
300 $objp = $db->fetch_object($result);
301 $qty = $objp->product_qty;
302 $pa = $objp->buying_price;
303 $pv = $objp->selling_price;
304 $marge = $objp->marge;
305
306 if ($pa != 0) {
307 $marginRate = (100 * $marge / $pa);
308 // We invert the sign if the margin is negative, regardless of the sign of the purchase price
309 if ($marge < 0) {
310 $marginRate = -$marginRate;
311 }
312 } else {
313 $marginRate = '';
314 }
315
316 if ($pv != 0) {
317 $markRate = (100 * $marge / $pv);
318 // We invert the sign if the margin is negative, as in the original logic
319 if ($marge < 0) {
320 $markRate = -$markRate;
321 }
322 } else {
323 $markRate = '';
324 }
325
326 print '<tr class="oddeven">';
327 if ($id > 0) {
328 print '<td>';
329 $invoicestatic->id = $objp->facid;
330 $invoicestatic->ref = $objp->ref;
331 print $invoicestatic->getNomUrl(1);
332 print "</td>\n";
333 print "<td class=\"center\">";
334 print dol_print_date($db->jdate($objp->datef), 'day')."</td>";
335 } else {
336 print '<td>';
337 if ($objp->rowid > 0) {
338 $product_static->type = $objp->fk_product_type;
339 $product_static->id = $objp->rowid;
340 $product_static->ref = $objp->ref;
341 $product_static->label = $objp->label;
342 $product_static->entity = $objp->pentity;
343 $text = $product_static->getNomUrl(1);
344 print $text .= ' - '.$objp->label;
345 } else {
346 print img_object('', 'product').' '.$langs->trans("NotPredefinedProducts");
347 }
348 print "</td>\n";
349 //print "<td>".$product_static->getNomUrl(1)."</td>\n";
350 }
351 print '<td class="center">'.$qty.'</td>';
352 print '<td class="nowrap right"><span class="amount">'.price(price2num($pv, 'MT')).'</span></td>';
353 print '<td class="nowrap right"><span class="amount">'.price(price2num($pa, 'MT')).'</span></td>';
354 print '<td class="nowrap right"><span class="amount">'.price(price2num($marge, 'MT')).'</span></td>';
355 if (getDolGlobalString('DISPLAY_MARGIN_RATES')) {
356 print '<td class="nowrap right">'.(($marginRate === '') ? 'n/a' : price(price2num($marginRate, 'MT'))."%").'</td>';
357 }
358 if (getDolGlobalString('DISPLAY_MARK_RATES')) {
359 print '<td class="nowrap right">'.(($markRate === '') ? 'n/a' : price(price2num($markRate, 'MT'))."%").'</td>';
360 }
361 print "</tr>\n";
362
363 $i++;
364 $cumul_achat += $objp->buying_price;
365 $cumul_vente += $objp->selling_price;
366 $cumul_qty += $objp->product_qty;
367 }
368 }
369
370 // affichage totaux marges
371
372 $totalMargin = $cumul_vente - $cumul_achat;
373
374 $marginRate = ($cumul_achat != 0) ? (100 * $totalMargin / $cumul_achat) : '';
375 $markRate = ($cumul_vente != 0) ? (100 * $totalMargin / $cumul_vente) : '';
376
377 print '<tr class="liste_total">';
378 if ($id > 0) {
379 print '<td colspan=2>';
380 } else {
381 print '<td>';
382 }
383 print $langs->trans('TotalMargin').'</td>';
384 print '<td class="center">'.$cumul_qty.'</td>';
385 print '<td class="nowrap right">'.price(price2num($cumul_vente, 'MT')).'</td>';
386 print '<td class="nowrap right">'.price(price2num($cumul_achat, 'MT')).'</td>';
387 print '<td class="nowrap right">'.price(price2num($totalMargin, 'MT')).'</td>';
388 if (getDolGlobalString('DISPLAY_MARGIN_RATES')) {
389 print '<td class="nowrap right">'.(($marginRate === '') ? 'n/a' : price(price2num($marginRate, 'MT'))."%").'</td>';
390 }
391 if (getDolGlobalString('DISPLAY_MARK_RATES')) {
392 print '<td class="nowrap right">'.(($markRate === '') ? 'n/a' : price(price2num($markRate, 'MT'))."%").'</td>';
393 }
394 print "</tr>\n";
395
396 print "</table>";
397 print '</div>';
398} else {
399 dol_print_error($db);
400}
401$db->free($result);
402
403
404print '
405<script type="text/javascript">
406$(document).ready(function() {
407 console.log("Init some values");
408 $("#totalMargin").html("'.price(price2num($totalMargin, 'MT')).'");
409 $("#marginRate").html("'.(($marginRate === '') ? 'n/a' : price(price2num($marginRate, 'MT'))."%").'");
410 $("#markRate").html("'.(($markRate === '') ? 'n/a' : price(price2num($markRate, 'MT'))."%").'");
411});
412</script>
413';
414
415// End of page
416llxFooter();
417$db->close();
$id
Definition account.php:39
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 products or services.
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...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
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.
print_barre_liste($title, $page, $file, $options='', $sortfield='', $sortorder='', $morehtmlcenter='', $num=-1, $totalnboflines='', $picto='generic', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limit=-1, $hideselectlimit=0, $hidenavigation=0, $pagenavastextinput=0, $morehtmlrightbeforearrow='')
Print a title with navigation controls for pagination.
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.
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.