dolibarr  19.0.0-dev
product.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2001-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3  * Copyright (C) 2004-2020 Laurent Destailleur <eldy@users.sourceforge.net>
4  * Copyright (C) 2004 Eric Seigne <eric.seigne@ryxeo.com>
5  * Copyright (C) 2005 Simon TOSSER <simon@kornog-computing.com>
6  * Copyright (C) 2005-2009 Regis Houssin <regis.houssin@inodbox.com>
7  * Copyright (C) 2013 Cédric Salvador <csalvador.gpcsolutions.fr>
8  * Copyright (C) 2013-2018 Juanjo Menent <jmenent@2byte.es>
9  * Copyright (C) 2014-2015 Cédric Gross <c.gross@kreiz-it.fr>
10  * Copyright (C) 2015 Marcos García <marcosgdf@gmail.com>
11  * Copyright (C) 2018-2019 Frédéric France <frederic.france@netlogic.fr>
12  * Copyright (C) 2021 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
13  *
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program. If not, see <https://www.gnu.org/licenses/>.
26  */
27 
34 // Load Dolibarr environment
35 require '../../main.inc.php';
36 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
37 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
38 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
39 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
40 require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
41 require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
42 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productstockentrepot.class.php';
43 if (isModEnabled('productbatch')) {
44  require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
45 }
46 if (isModEnabled('project')) {
47  require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php';
48  require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
49 }
50 
51 if (isModEnabled('variants')) {
52  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
53  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
54  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
55  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
56 }
57 
58 // Load translation files required by the page
59 $langs->loadlangs(array('products', 'suppliers', 'orders', 'bills', 'stocks', 'sendings', 'margins'));
60 if (isModEnabled('productbatch')) {
61  $langs->load("productbatch");
62 }
63 
64 $backtopage = GETPOST('backtopage', 'alpha');
65 $action = GETPOST('action', 'aZ09');
66 $cancel = GETPOST('cancel', 'alpha');
67 
68 $id = GETPOST('id', 'int');
69 $ref = GETPOST('ref', 'alpha');
70 $stocklimit = GETPOST('seuil_stock_alerte');
71 $desiredstock = GETPOST('desiredstock');
72 $cancel = GETPOST('cancel', 'alpha');
73 $fieldid = isset($_GET["ref"]) ? 'ref' : 'rowid';
74 $d_eatby = dol_mktime(0, 0, 0, GETPOST('eatbymonth', 'int'), GETPOST('eatbyday', 'int'), GETPOST('eatbyyear', 'int'));
75 $d_sellby = dol_mktime(0, 0, 0, GETPOST('sellbymonth', 'int'), GETPOST('sellbyday', 'int'), GETPOST('sellbyyear', 'int'));
76 $pdluoid = GETPOST('pdluoid', 'int');
77 $batchnumber = GETPOST('batch_number', 'san_alpha');
78 if (!empty($batchnumber)) {
79  $batchnumber = trim($batchnumber);
80 }
81 $cost_price = GETPOST('cost_price', 'alpha');
82 
83 // Security check
84 if ($user->socid) {
85  $socid = $user->socid;
86 }
87 
88 $object = new Product($db);
89 $extrafields = new ExtraFields($db);
90 
91 // fetch optionals attributes and labels
92 $extrafields->fetch_name_optionals_label($object->table_element);
93 
94 if ($id > 0 || !empty($ref)) {
95  $result = $object->fetch($id, $ref);
96 }
97 
98 if (empty($id) && !empty($object->id)) {
99  $id = $object->id;
100 }
101 
102 $modulepart = 'product';
103 
104 // Get object canvas (By default, this is not defined, so standard usage of dolibarr)
105 $canvas = !empty($object->canvas) ? $object->canvas : GETPOST("canvas");
106 $objcanvas = null;
107 if (!empty($canvas)) {
108  require_once DOL_DOCUMENT_ROOT.'/core/class/canvas.class.php';
109  $objcanvas = new Canvas($db, $action);
110  $objcanvas->getCanvas('stockproduct', 'card', $canvas);
111 }
112 
113 // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
114 $hookmanager->initHooks(array('stockproductcard', 'globalcard'));
115 
116 $error = 0;
117 
118 $usercanread = (($object->type == Product::TYPE_PRODUCT && $user->rights->produit->lire) || ($object->type == Product::TYPE_SERVICE && $user->hasRight('service', 'lire')));
119 $usercancreate = (($object->type == Product::TYPE_PRODUCT && $user->rights->produit->creer) || ($object->type == Product::TYPE_SERVICE && $user->hasRight('service', 'creer')));
120 $usercancreadprice = getDolGlobalString('MAIN_USE_ADVANCED_PERMS')?$user->hasRight('product', 'product_advance', 'read_prices'):$user->hasRight('product', 'lire');
121 
122 if ($object->isService()) {
123  $label = $langs->trans('Service');
124  $usercancreadprice = getDolGlobalString('MAIN_USE_ADVANCED_PERMS')?$user->hasRight('service', 'service_advance', 'read_prices'):$user->hasRight('service', 'lire');
125 }
126 
127 if ($object->id > 0) {
128  if ($object->type == $object::TYPE_PRODUCT) {
129  restrictedArea($user, 'produit', $object->id, 'product&product', '', '');
130  }
131  if ($object->type == $object::TYPE_SERVICE) {
132  restrictedArea($user, 'service', $object->id, 'product&product', '', '');
133  }
134 } else {
135  restrictedArea($user, 'produit|service', $id, 'product&product', '', '', $fieldid);
136 }
137 
138 
139 /*
140  * Actions
141  */
142 
143 if ($cancel) {
144  $action = '';
145 }
146 
147 $parameters = array('id'=>$id, 'ref'=>$ref, 'objcanvas'=>$objcanvas);
148 $reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
149 if ($reshook < 0) {
150  setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
151 }
152 
153 if ($action == 'setcost_price') {
154  if ($id) {
155  $result = $object->fetch($id);
156  $object->cost_price = price2num($cost_price);
157  $result = $object->update($object->id, $user);
158  if ($result > 0) {
159  setEventMessages($langs->trans("RecordSaved"), null, 'mesgs');
160  $action = '';
161  } else {
162  $error++;
163  setEventMessages($object->error, $object->errors, 'errors');
164  }
165  }
166 }
167 
168 if ($action == 'addlimitstockwarehouse' && $user->hasRight('produit', 'creer')) {
169  $seuil_stock_alerte = GETPOST('seuil_stock_alerte');
170  $desiredstock = GETPOST('desiredstock');
171 
172  $maj_ok = true;
173  if ($seuil_stock_alerte == '') {
174  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("StockLimit")), null, 'errors');
175  $maj_ok = false;
176  }
177  if ($desiredstock == '') {
178  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("DesiredStock")), null, 'errors');
179  $maj_ok = false;
180  }
181 
182  if ($maj_ok) {
183  $pse = new ProductStockEntrepot($db);
184  if ($pse->fetch(0, $id, GETPOST('fk_entrepot', 'int')) > 0) {
185  // Update
186  $pse->seuil_stock_alerte = $seuil_stock_alerte;
187  $pse->desiredstock = $desiredstock;
188  if ($pse->update($user) > 0) {
189  setEventMessages($langs->trans('ProductStockWarehouseUpdated'), null, 'mesgs');
190  }
191  } else {
192  // Create
193  $pse->fk_entrepot = GETPOST('fk_entrepot', 'int');
194  $pse->fk_product = $id;
195  $pse->seuil_stock_alerte = GETPOST('seuil_stock_alerte');
196  $pse->desiredstock = GETPOST('desiredstock');
197  if ($pse->create($user) > 0) {
198  setEventMessages($langs->trans('ProductStockWarehouseCreated'), null, 'mesgs');
199  }
200  }
201  }
202 
203  header("Location: ".$_SERVER["PHP_SELF"]."?id=".$id);
204  exit;
205 }
206 
207 if ($action == 'delete_productstockwarehouse' && $user->hasRight('produit', 'creer')) {
208  $pse = new ProductStockEntrepot($db);
209 
210  $pse->fetch(GETPOST('fk_productstockwarehouse', 'int'));
211  if ($pse->delete($user) > 0) {
212  setEventMessages($langs->trans('ProductStockWarehouseDeleted'), null, 'mesgs');
213  }
214 
215  $action = '';
216 }
217 
218 // Set stock limit
219 if ($action == 'setseuil_stock_alerte' && $user->hasRight('produit', 'creer')) {
220  $object = new Product($db);
221  $result = $object->fetch($id);
222  $object->seuil_stock_alerte = $stocklimit;
223  $result = $object->update($object->id, $user, 0, 'update');
224  if ($result < 0) {
225  setEventMessages($object->error, $object->errors, 'errors');
226  }
227  //else
228  // setEventMessages($lans->trans("SavedRecordSuccessfully"), null, 'mesgs');
229  $action = '';
230 }
231 
232 // Set desired stock
233 if ($action == 'setdesiredstock' && $user->hasRight('produit', 'creer')) {
234  $object = new Product($db);
235  $result = $object->fetch($id);
236  $object->desiredstock = $desiredstock;
237  $result = $object->update($object->id, $user, 0, 'update');
238  if ($result < 0) {
239  setEventMessages($object->error, $object->errors, 'errors');
240  }
241  $action = '';
242 }
243 
244 
245 // Correct stock
246 if ($action == "correct_stock" && !$cancel) {
247  if (!(GETPOST("id_entrepot", 'int') > 0)) {
248  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Warehouse")), null, 'errors');
249  $error++;
250  $action = 'correction';
251  }
252  if (!GETPOST("nbpiece")) {
253  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("NumberOfUnit")), null, 'errors');
254  $error++;
255  $action = 'correction';
256  }
257 
258  if (isModEnabled('productbatch')) {
259  $object = new Product($db);
260  $result = $object->fetch($id);
261 
262  if ($object->hasbatch() && !$batchnumber) {
263  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("batch_number")), null, 'errors');
264  $error++;
265  $action = 'correction';
266  }
267  }
268 
269  if (!$error) {
270  $priceunit = price2num(GETPOST("unitprice"));
271  $nbpiece = price2num(GETPOST("nbpiece", 'alphanohtml'));
272  if (is_numeric($nbpiece) && $nbpiece != 0 && $id) {
273  $origin_element = '';
274  $origin_id = null;
275 
276  if (GETPOST('projectid', 'int')) {
277  $origin_element = 'project';
278  $origin_id = GETPOST('projectid', 'int');
279  }
280 
281  if (empty($object)) {
282  $object = new Product($db);
283  $result = $object->fetch($id);
284  }
285 
286  $disablestockchangeforsubproduct = 0;
287  if (GETPOST('disablesubproductstockchange')) {
288  $disablestockchangeforsubproduct = 1;
289  }
290 
291  if ($object->hasbatch()) {
292  $result = $object->correct_stock_batch(
293  $user,
294  GETPOST("id_entrepot", 'int'),
295  $nbpiece,
296  GETPOST("mouvement", 'int'),
297  GETPOST("label", 'alphanohtml'), // label movement
298  $priceunit,
299  $d_eatby,
300  $d_sellby,
301  $batchnumber,
302  GETPOST('inventorycode', 'alphanohtml'),
303  $origin_element,
304  $origin_id,
305  $disablestockchangeforsubproduct
306  ); // We do not change value of stock for a correction
307  } else {
308  $result = $object->correct_stock(
309  $user,
310  GETPOST("id_entrepot", 'int'),
311  $nbpiece,
312  GETPOST("mouvement", 'int'),
313  GETPOST("label", 'alphanohtml'),
314  $priceunit,
315  GETPOST('inventorycode', 'alphanohtml'),
316  $origin_element,
317  $origin_id,
318  $disablestockchangeforsubproduct
319  ); // We do not change value of stock for a correction
320  }
321 
322  if ($result > 0) {
323  if ($backtopage) {
324  header("Location: ".$backtopage);
325  exit;
326  } else {
327  header("Location: ".$_SERVER["PHP_SELF"]."?id=".$object->id);
328  exit;
329  }
330  } else {
331  setEventMessages($object->error, $object->errors, 'errors');
332  $action = 'correction';
333  }
334  }
335  }
336 }
337 
338 // Transfer stock from a warehouse to another warehouse
339 if ($action == "transfert_stock" && !$cancel) {
340  if (!(GETPOST("id_entrepot", 'int') > 0) || !(GETPOST("id_entrepot_destination", 'int') > 0)) {
341  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Warehouse")), null, 'errors');
342  $error++;
343  $action = 'transfert';
344  }
345  if (!GETPOST("nbpiece", 'int')) {
346  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("NumberOfUnit")), null, 'errors');
347  $error++;
348  $action = 'transfert';
349  }
350  if (GETPOST("id_entrepot", 'int') == GETPOST("id_entrepot_destination", 'int')) {
351  setEventMessages($langs->trans("ErrorSrcAndTargetWarehouseMustDiffers"), null, 'errors');
352  $error++;
353  $action = 'transfert';
354  }
355  if (isModEnabled('productbatch')) {
356  $object = new Product($db);
357  $result = $object->fetch($id);
358 
359  if ($object->hasbatch() && !$batchnumber) {
360  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("batch_number")), null, 'errors');
361  $error++;
362  $action = 'transfert';
363  }
364  }
365 
366  if (!$error) {
367  if ($id) {
368  $object = new Product($db);
369  $result = $object->fetch($id);
370 
371  $db->begin();
372 
373  $object->load_stock('novirtual'); // Load array product->stock_warehouse
374 
375  // Define value of products moved
376  $pricesrc = 0;
377  if (isset($object->pmp)) {
378  $pricesrc = $object->pmp;
379  }
380  $pricedest = $pricesrc;
381 
382  $nbpiece = price2num(GETPOST("nbpiece", 'alphanohtml'));
383 
384  if ($object->hasbatch()) {
385  $pdluo = new Productbatch($db);
386 
387  if ($pdluoid > 0) {
388  $result = $pdluo->fetch($pdluoid);
389  if ($result) {
390  $srcwarehouseid = $pdluo->warehouseid;
391  $batch = $pdluo->batch;
392  $eatby = $pdluo->eatby;
393  $sellby = $pdluo->sellby;
394  } else {
395  setEventMessages($pdluo->error, $pdluo->errors, 'errors');
396  $error++;
397  }
398  } else {
399  $srcwarehouseid = GETPOST('id_entrepot', 'int');
400  $batch = $batchnumber;
401  $eatby = $d_eatby;
402  $sellby = $d_sellby;
403  }
404 
405  $nbpiece = price2num(GETPOST("nbpiece", 'alphanohtml'));
406 
407  if (!$error) {
408  // Remove stock
409  $result1 = $object->correct_stock_batch(
410  $user,
411  $srcwarehouseid,
412  $nbpiece,
413  1,
414  GETPOST("label", 'alphanohtml'),
415  $pricesrc,
416  $eatby,
417  $sellby,
418  $batch,
419  GETPOST('inventorycode', 'alphanohtml')
420  );
421  if ($result1 < 0) {
422  $error++;
423  }
424  }
425  if (!$error) {
426  // Add stock
427  $result2 = $object->correct_stock_batch(
428  $user,
429  GETPOST("id_entrepot_destination", 'int'),
430  $nbpiece,
431  0,
432  GETPOST("label", 'alphanohtml'),
433  $pricedest,
434  $eatby,
435  $sellby,
436  $batch,
437  GETPOST('inventorycode', 'alphanohtml')
438  );
439  if ($result2 < 0) {
440  $error++;
441  }
442  }
443  } else {
444  if (!$error) {
445  // Remove stock
446  $result1 = $object->correct_stock(
447  $user,
448  GETPOST("id_entrepot", 'int'),
449  $nbpiece,
450  1,
451  GETPOST("label", 'alphanohtml'),
452  $pricesrc,
453  GETPOST('inventorycode', 'alphanohtml')
454  );
455  if ($result1 < 0) {
456  $error++;
457  }
458  }
459  if (!$error) {
460  // Add stock
461  $result2 = $object->correct_stock(
462  $user,
463  GETPOST("id_entrepot_destination", 'int'),
464  $nbpiece,
465  0,
466  GETPOST("label", 'alphanohtml'),
467  $pricedest,
468  GETPOST('inventorycode', 'alphanohtml')
469  );
470  if ($result2 < 0) {
471  $error++;
472  }
473  }
474  }
475 
476 
477  if (!$error && $result1 >= 0 && $result2 >= 0) {
478  $db->commit();
479 
480  if ($backtopage) {
481  header("Location: ".$backtopage);
482  exit;
483  } else {
484  header("Location: product.php?id=".$object->id);
485  exit;
486  }
487  } else {
488  setEventMessages($object->error, $object->errors, 'errors');
489  $db->rollback();
490  $action = 'transfert';
491  }
492  }
493  }
494 }
495 
496 // Update batch information
497 if ($action == 'updateline' && GETPOST('save') == $langs->trans("Save")) {
498  $pdluo = new Productbatch($db);
499  $result = $pdluo->fetch(GETPOST('pdluoid', 'int'));
500 
501  if ($result > 0) {
502  if ($pdluo->id) {
503  if ((!GETPOST("sellby")) && (!GETPOST("eatby")) && (!$batchnumber)) {
504  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("atleast1batchfield")), null, 'errors');
505  } else {
506  $d_eatby = dol_mktime(0, 0, 0, GETPOST('eatbymonth', 'int'), GETPOST('eatbyday', 'int'), GETPOST('eatbyyear', 'int'));
507  $d_sellby = dol_mktime(0, 0, 0, GETPOST('sellbymonth', 'int'), GETPOST('sellbyday', 'int'), GETPOST('sellbyyear', 'int'));
508  $pdluo->batch = $batchnumber;
509  $pdluo->eatby = $d_eatby;
510  $pdluo->sellby = $d_sellby;
511  $result = $pdluo->update($user);
512  if ($result < 0) {
513  setEventMessages($pdluo->error, $pdluo->errors, 'errors');
514  }
515  }
516  } else {
517  setEventMessages($langs->trans('BatchInformationNotfound'), null, 'errors');
518  }
519  } else {
520  setEventMessages($pdluo->error, null, 'errors');
521  }
522  header("Location: product.php?id=".$id);
523  exit;
524 }
525 
526 
527 
528 /*
529  * View
530  */
531 
532 $form = new Form($db);
533 $formproduct = new FormProduct($db);
534 if (isModEnabled('project')) {
535  $formproject = new FormProjets($db);
536 }
537 
538 if ($id > 0 || $ref) {
539  $object = new Product($db);
540  $result = $object->fetch($id, $ref);
541 
542  $variants = $object->hasVariants();
543 
544  $object->load_stock();
545 
546  $title = $langs->trans('ProductServiceCard');
547  $helpurl = '';
548  $shortlabel = dol_trunc($object->label, 16);
549  if (GETPOST("type") == '0' || ($object->type == Product::TYPE_PRODUCT)) {
550  $title = $langs->trans('Product')." ".$shortlabel." - ".$langs->trans('Stock');
551  $helpurl = 'EN:Module_Products|FR:Module_Produits|ES:M&oacute;dulo_Productos';
552  }
553  if (GETPOST("type") == '1' || ($object->type == Product::TYPE_SERVICE)) {
554  $title = $langs->trans('Service')." ".$shortlabel." - ".$langs->trans('Stock');
555  $helpurl = 'EN:Module_Services_En|FR:Module_Services|ES:M&oacute;dulo_Servicios';
556  }
557 
558  llxHeader('', $title, $helpurl);
559 
560  if (!empty($conf->use_javascript_ajax)) {
561  ?>
562  <script type="text/javascript">
563  $(document).ready(function() {
564  $(".collapse_batch").click(function() {
565  console.log("We click on collapse_batch");
566  var id_entrepot = $(this).attr('id').replace('ent', '');
567 
568  if($(this).text().indexOf('+') > 0) {
569  $(".batch_warehouse" + id_entrepot).show();
570  $(this).html('(-)');
571  jQuery("#show_all").hide();
572  jQuery("#hide_all").show();
573  }
574  else {
575  $(".batch_warehouse" + id_entrepot).hide();
576  $(this).html('(+)');
577  }
578 
579  return false;
580  });
581 
582  $("#show_all").click(function() {
583  console.log("We click on show_all");
584  $("[class^=batch_warehouse]").show();
585  $("[class^=collapse_batch]").html('(-)');
586  jQuery("#show_all").hide();
587  jQuery("#hide_all").show();
588  return false;
589  });
590 
591  $("#hide_all").click(function() {
592  console.log("We click on hide_all");
593  $("[class^=batch_warehouse]").hide();
594  $("[class^=collapse_batch]").html('(+)');
595  jQuery("#hide_all").hide();
596  jQuery("#show_all").show();
597  return false;
598  });
599 
600  });
601  </script>
602  <?php
603  }
604 
605  if ($result > 0) {
606  $head = product_prepare_head($object);
607  $titre = $langs->trans("CardProduct".$object->type);
608  $picto = ($object->type == Product::TYPE_SERVICE ? 'service' : 'product');
609 
610  print dol_get_fiche_head($head, 'stock', $titre, -1, $picto);
611 
613 
614  $linkback = '<a href="'.DOL_URL_ROOT.'/product/list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
615 
616  $shownav = 1;
617  if ($user->socid && !in_array('stock', explode(',', $conf->global->MAIN_MODULES_FOR_EXTERNAL))) {
618  $shownav = 0;
619  }
620 
621  dol_banner_tab($object, 'ref', $linkback, $shownav, 'ref');
622 
623  if (!$variants) {
624  print '<div class="fichecenter">';
625 
626  print '<div class="fichehalfleft">';
627  print '<div class="underbanner clearboth"></div>';
628 
629  print '<table class="border tableforfield centpercent">';
630 
631  // Type
632  if (isModEnabled("product") && isModEnabled("service")) {
633  $typeformat = 'select;0:'.$langs->trans("Product").',1:'.$langs->trans("Service");
634  print '<tr><td class="">';
635  print (empty($conf->global->PRODUCT_DENY_CHANGE_PRODUCT_TYPE)) ? $form->editfieldkey("Type", 'fk_product_type', $object->type, $object, 0, $typeformat) : $langs->trans('Type');
636  print '</td><td>';
637  print $form->editfieldval("Type", 'fk_product_type', $object->type, $object, 0, $typeformat);
638  print '</td></tr>';
639  }
640 
641  if (isModEnabled('productbatch')) {
642  print '<tr><td class="">'.$langs->trans("ManageLotSerial").'</td><td>';
643  print $object->getLibStatut(0, 2);
644  print '</td></tr>';
645  }
646 
647  // Cost price. Can be used for margin module for option "calculate margin on explicit cost price
648  print '<tr><td>';
649  $textdesc = $langs->trans("CostPriceDescription");
650  $textdesc .= "<br>".$langs->trans("CostPriceUsage");
651  $text = $form->textwithpicto($langs->trans("CostPrice"), $textdesc, 1, 'help', '');
652  if (!$usercancreadprice) {
653  print $form->editfieldkey($text, 'cost_price', '', $object, 0, 'amount:6');
654  print '</td><td>';
655  print $form->editfieldval($text, 'cost_price', '', $object, 0, 'amount:6');
656  } else {
657  print $form->editfieldkey($text, 'cost_price', $object->cost_price, $object, $usercancreate, 'amount:6');
658  print '</td><td>';
659  print $form->editfieldval($text, 'cost_price', $object->cost_price, $object, $usercancreate, 'amount:6');
660  }
661  print '</td></tr>';
662 
663 
664 
665  // AWP
666  print '<tr><td class="titlefield">';
667  print $form->textwithpicto($langs->trans("AverageUnitPricePMPShort"), $langs->trans("AverageUnitPricePMPDesc"));
668  print '</td>';
669  print '<td>';
670  if ($object->pmp > 0 && $usercancreadprice) {
671  print price($object->pmp).' '.$langs->trans("HT");
672  }
673  print '</td>';
674  print '</tr>';
675 
676  // Minimum Price
677  print '<tr><td>'.$langs->trans("BuyingPriceMin").'</td>';
678  print '<td>';
679  $product_fourn = new ProductFournisseur($db);
680  if ($product_fourn->find_min_price_product_fournisseur($object->id) > 0) {
681  if ($product_fourn->product_fourn_price_id > 0 && $usercancreadprice) {
682  print $product_fourn->display_price_product_fournisseur();
683  } else {
684  print $langs->trans("NotDefined");
685  }
686  }
687  print '</td></tr>';
688 
689  if (empty($conf->global->PRODUIT_MULTIPRICES)) {
690  // Price
691  print '<tr><td>'.$langs->trans("SellingPrice").'</td><td>';
692  if ($usercancreadprice) {
693  if ($object->price_base_type == 'TTC') {
694  print price($object->price_ttc).' '.$langs->trans($object->price_base_type);
695  } else {
696  print price($object->price).' '.$langs->trans($object->price_base_type);
697  }
698  }
699  print '</td></tr>';
700 
701  // Price minimum
702  print '<tr><td>'.$langs->trans("MinPrice").'</td><td>';
703  if ($usercancreadprice) {
704  if ($object->price_base_type == 'TTC') {
705  print price($object->price_min_ttc).' '.$langs->trans($object->price_base_type);
706  } else {
707  print price($object->price_min).' '.$langs->trans($object->price_base_type);
708  }
709  }
710  print '</td></tr>';
711  } else {
712  // Price
713  print '<tr><td>'.$langs->trans("SellingPrice").'</td><td>';
714  print '<span class="opacitymedium">'.$langs->trans("Variable").'</span>';
715  print '</td></tr>';
716 
717  // Price minimum
718  print '<tr><td>'.$langs->trans("MinPrice").'</td><td>';
719  print '<span class="opacitymedium">'.$langs->trans("Variable").'</span>';
720  print '</td></tr>';
721  }
722 
723  // Hook formObject
724  $parameters = array();
725  $reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
726  print $hookmanager->resPrint;
727 
728  print '</table>';
729 
730  print '</div>';
731  print '<div class="fichehalfright"><div class="underbanner clearboth"></div>';
732 
733  print '<table class="border tableforfield centpercent">';
734 
735  // Stock alert threshold
736  print '<tr><td>'.$form->editfieldkey($form->textwithpicto($langs->trans("StockLimit"), $langs->trans("StockLimitDesc"), 1), 'seuil_stock_alerte', $object->seuil_stock_alerte, $object, $user->rights->produit->creer).'</td><td>';
737  print $form->editfieldval("StockLimit", 'seuil_stock_alerte', $object->seuil_stock_alerte, $object, $user->rights->produit->creer, 'string');
738  print '</td></tr>';
739 
740  // Desired stock
741  print '<tr><td>'.$form->editfieldkey($form->textwithpicto($langs->trans("DesiredStock"), $langs->trans("DesiredStockDesc"), 1), 'desiredstock', $object->desiredstock, $object, $user->rights->produit->creer);
742  print '</td><td>';
743  print $form->editfieldval("DesiredStock", 'desiredstock', $object->desiredstock, $object, $user->rights->produit->creer, 'string');
744  print '</td></tr>';
745 
746  // Real stock
747  $text_stock_options = $langs->trans("RealStockDesc").'<br>';
748  $text_stock_options .= $langs->trans("RealStockWillAutomaticallyWhen").'<br>';
749  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE) ? '- '.$langs->trans("DeStockOnShipment").'<br>' : '');
750  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER) ? '- '.$langs->trans("DeStockOnValidateOrder").'<br>' : '');
751  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_BILL) ? '- '.$langs->trans("DeStockOnBill").'<br>' : '');
752  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL) ? '- '.$langs->trans("ReStockOnBill").'<br>' : '');
753  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER) ? '- '.$langs->trans("ReStockOnValidateOrder").'<br>' : '');
754  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER) ? '- '.$langs->trans("ReStockOnDispatchOrder").'<br>' : '');
755  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_RECEPTION) || !empty($conf->global->STOCK_CALCULATE_ON_RECEPTION_CLOSE) ? '- '.$langs->trans("StockOnReception").'<br>' : '');
756  $parameters = array();
757  $reshook = $hookmanager->executeHooks('physicalStockTextStockOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
758  if ($reshook > 0) {
759  $text_stock_options = $hookmanager->resPrint;
760  } elseif ($reshook == 0) {
761  $text_stock_options .= $hookmanager->resPrint;
762  } else {
763  setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
764  }
765 
766  print '<tr><td>';
767  print $form->textwithpicto($langs->trans("PhysicalStock"), $text_stock_options, 1);
768  print '</td>';
769  print '<td>'.price2num($object->stock_reel, 'MS');
770  if ($object->seuil_stock_alerte != '' && ($object->stock_reel < $object->seuil_stock_alerte)) {
771  print ' '.img_warning($langs->trans("StockLowerThanLimit", $object->seuil_stock_alerte));
772  }
773 
774  print ' &nbsp; &nbsp;<a href="'.DOL_URL_ROOT.'/product/stock/stockatdate.php?productid='.$object->id.'">'.$langs->trans("StockAtDate").'</a>';
775  print '</td>';
776  print '</tr>';
777 
778  $stocktheo = price2num($object->stock_theorique, 'MS');
779 
780  $found = 0;
781  $helpondiff = '<strong>'.$langs->trans("StockDiffPhysicTeoric").':</strong><br>';
782  // Number of sales orders running
783  if (isModEnabled('commande')) {
784  if ($found) {
785  $helpondiff .= '<br>';
786  } else {
787  $found = 1;
788  }
789  $helpondiff .= $langs->trans("ProductQtyInCustomersOrdersRunning").': '.$object->stats_commande['qty'];
790  $result = $object->load_stats_commande(0, '0', 1);
791  if ($result < 0) {
792  dol_print_error($db, $object->error);
793  }
794  $helpondiff .= ' <span class="opacitymedium">('.$langs->trans("ProductQtyInDraft").': '.$object->stats_commande['qty'].')</span>';
795  }
796 
797  // Number of product from sales order already sent (partial shipping)
798  if (isModEnabled("expedition")) {
799  require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
800  $filterShipmentStatus = '';
801  if (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)) {
802  $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
803  } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
804  $filterShipmentStatus = Expedition::STATUS_CLOSED;
805  }
806  if ($found) {
807  $helpondiff .= '<br>';
808  } else {
809  $found = 1;
810  }
811  $result = $object->load_stats_sending(0, '2', 1, $filterShipmentStatus);
812  $helpondiff .= $langs->trans("ProductQtyInShipmentAlreadySent").': '.$object->stats_expedition['qty'];
813  }
814 
815  // Number of supplier order running
816  if (isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) {
817  if ($found) {
818  $helpondiff .= '<br>';
819  } else {
820  $found = 1;
821  }
822  $result = $object->load_stats_commande_fournisseur(0, '3,4', 1);
823  $helpondiff .= $langs->trans("ProductQtyInSuppliersOrdersRunning").': '.$object->stats_commande_fournisseur['qty'];
824  $result = $object->load_stats_commande_fournisseur(0, '0,1,2', 1);
825  if ($result < 0) {
826  dol_print_error($db, $object->error);
827  }
828  $helpondiff .= ' <span class="opacitymedium">('.$langs->trans("ProductQtyInDraftOrWaitingApproved").': '.$object->stats_commande_fournisseur['qty'].')</span>';
829  }
830 
831  // Number of product from supplier order already received (partial receipt)
832  if (isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) {
833  if ($found) {
834  $helpondiff .= '<br>';
835  } else {
836  $found = 1;
837  }
838  $helpondiff .= $langs->trans("ProductQtyInSuppliersShipmentAlreadyRecevied").': '.$object->stats_reception['qty'];
839  }
840 
841  // Number of product in production
842  if (isModEnabled('mrp')) {
843  if ($found) {
844  $helpondiff .= '<br>';
845  } else {
846  $found = 1;
847  }
848  $helpondiff .= $langs->trans("ProductQtyToConsumeByMO").': '.$object->stats_mrptoconsume['qty'].'<br>';
849  $helpondiff .= $langs->trans("ProductQtyToProduceByMO").': '.$object->stats_mrptoproduce['qty'];
850  }
851  $parameters = array('found' => &$found, 'id' => $object->id, 'includedraftpoforvirtual' => null);
852  $reshook = $hookmanager->executeHooks('virtualStockHelpOnDiff', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
853  if ($reshook > 0) {
854  $helpondiff = $hookmanager->resPrint;
855  } elseif ($reshook == 0) {
856  $helpondiff .= $hookmanager->resPrint;
857  } else {
858  setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
859  }
860 
861 
862  // Calculating a theorical value
863  print '<tr><td>';
864  print $form->textwithpicto($langs->trans("VirtualStock"), $langs->trans("VirtualStockDesc"));
865  print '</td>';
866  print "<td>";
867  //print (empty($stocktheo)?0:$stocktheo);
868  print $form->textwithpicto((empty($stocktheo) ? 0 : $stocktheo), $helpondiff);
869  if ($object->seuil_stock_alerte != '' && ($object->stock_theorique < $object->seuil_stock_alerte)) {
870  print ' '.img_warning($langs->trans("StockLowerThanLimit", $object->seuil_stock_alerte));
871  }
872  print ' &nbsp; &nbsp;<a href="'.DOL_URL_ROOT.'/product/stock/stockatdate.php?mode=future&productid='.$object->id.'">'.$langs->trans("VirtualStockAtDate").'</a>';
873  print '</td>';
874  print '</tr>';
875 
876  // Last movement
877  if (!empty($user->rights->stock->mouvement->lire)) {
878  $sql = "SELECT max(m.datem) as datem";
879  $sql .= " FROM ".MAIN_DB_PREFIX."stock_mouvement as m";
880  $sql .= " WHERE m.fk_product = ".((int) $object->id);
881  $resqlbis = $db->query($sql);
882  if ($resqlbis) {
883  $obj = $db->fetch_object($resqlbis);
884  $lastmovementdate = $db->jdate($obj->datem);
885  } else {
886  dol_print_error($db);
887  }
888  print '<tr><td class="tdtop">'.$langs->trans("LastMovement").'</td><td>';
889  if ($lastmovementdate) {
890  print dol_print_date($lastmovementdate, 'dayhour').' ';
891  print ' &nbsp; &nbsp; ';
892  print img_picto($langs->trans("StockMovement"), 'movement', 'class="pictofixedwidth"');
893  print '<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?idproduct='.$object->id.'">'.$langs->trans("FullList").'</a>';
894  } else {
895  print img_picto($langs->trans("StockMovement"), 'movement', 'class="pictofixedwidth"');
896  print '<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?idproduct='.$object->id.'">'.$langs->trans("None").'</a>';
897  }
898  print "</td></tr>";
899  }
900 
901  print "</table>";
902 
903  print '</div>';
904  print '</div>';
905 
906  print '<div class="clearboth"></div>';
907  }
908 
909  print dol_get_fiche_end();
910  }
911 
912  // Correct stock
913  if ($action == "correction") {
914  include DOL_DOCUMENT_ROOT.'/product/stock/tpl/stockcorrection.tpl.php';
915  print '<br><br>';
916  }
917 
918  // Transfer of units
919  if ($action == "transfert") {
920  include DOL_DOCUMENT_ROOT.'/product/stock/tpl/stocktransfer.tpl.php';
921  print '<br><br>';
922  }
923 } else {
924  dol_print_error();
925 }
926 
927 
928 // Actions buttons
929 
930 $parameters = array();
931 
932 $reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
933 if (empty($reshook)) {
934  if (empty($action) && $object->id) {
935  print "<div class=\"tabsAction\">\n";
936 
937  if ($user->rights->stock->mouvement->creer) {
938  if (!$variants || !empty($conf->global->VARIANT_ALLOW_STOCK_MOVEMENT_ON_VARIANT_PARENT)) {
939  print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;action=transfert">'.$langs->trans("TransferStock").'</a>';
940  } else {
941  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("ActionAvailableOnVariantProductOnly").'">'.$langs->trans("TransferStock").'</a>';
942  }
943  } else {
944  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans("CorrectStock").'</a>';
945  }
946 
947  if ($user->rights->stock->mouvement->creer) {
948  if (!$variants || !empty($conf->global->VARIANT_ALLOW_STOCK_MOVEMENT_ON_VARIANT_PARENT)) {
949  print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;action=correction">'.$langs->trans("CorrectStock").'</a>';
950  } else {
951  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("ActionAvailableOnVariantProductOnly").'">'.$langs->trans("CorrectStock").'</a>';
952  }
953  } else {
954  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans("CorrectStock").'</a>';
955  }
956 
957  print '</div>';
958  }
959 }
960 
961 
962 if (!$variants) {
963  /*
964  * Stock detail (by warehouse). May go down into batch details.
965  */
966 
967  print '<div class="div-table-responsive">';
968  print '<table class="noborder centpercent">';
969 
970  print '<tr class="liste_titre">';
971  print '<td colspan="4">'.$langs->trans("Warehouse").'</td>';
972  print '<td class="right">'.$langs->trans("NumberOfUnit").'</td>';
973  print '<td class="right">'.$form->textwithpicto($langs->trans("AverageUnitPricePMPShort"), $langs->trans("AverageUnitPricePMPDesc")).'</td>';
974  print '<td class="right">'.$langs->trans("EstimatedStockValueShort").'</td>';
975  print '<td class="right">'.$langs->trans("SellPriceMin").'</td>';
976  print '<td class="right">'.$langs->trans("EstimatedStockValueSellShort").'</td>';
977  print '<td></td>';
978  print '<td></td>';
979  print '</tr>';
980 
981  if ((isModEnabled('productbatch')) && $object->hasbatch()) {
982  $colspan = 3;
983  print '<tr class="liste_titre"><td class="minwidth200">';
984  if (!empty($conf->use_javascript_ajax)) {
985  print '<a id="show_all" href="#" class="hideobject">'.img_picto('', 'folder-open', 'class="paddingright"').$langs->trans("ShowAllLots").'</a>';
986  //print ' &nbsp; ';
987  print '<a id="hide_all" href="#">'.img_picto('', 'folder', 'class="paddingright"').$langs->trans("HideLots").'</a>';
988  //print '&nbsp;'.$form->textwithpicto('', $langs->trans('CollapseBatchDetailHelp'), 1, 'help', '');
989  }
990  print '</td>';
991  print '<td class="right">'.$langs->trans("batch_number").'</td>';
992  if (empty($conf->global->PRODUCT_DISABLE_SELLBY)) {
993  $colspan--;
994  print '<td class="center width100">'.$langs->trans("SellByDate").'</td>';
995  }
996  if (empty($conf->global->PRODUCT_DISABLE_EATBY)) {
997  $colspan--;
998  print '<td class="center width100">'.$langs->trans("EatByDate").'</td>';
999  }
1000  print '<td colspan="'.$colspan.'"></td>';
1001  print '<td></td>';
1002  print '<td></td>';
1003  print '<td></td>';
1004  print '<td></td>';
1005  print '<td></td>';
1006  print '<td></td>';
1007  print '</tr>';
1008  }
1009 
1010  $sql = "SELECT e.rowid, e.ref, e.lieu, e.fk_parent, e.statut as status, ps.reel, ps.rowid as product_stock_id, p.pmp";
1011  $sql .= " FROM ".MAIN_DB_PREFIX."entrepot as e,";
1012  $sql .= " ".MAIN_DB_PREFIX."product_stock as ps";
1013  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = ps.fk_product";
1014  $sql .= " WHERE ps.reel != 0";
1015  $sql .= " AND ps.fk_entrepot = e.rowid";
1016  $sql .= " AND e.entity IN (".getEntity('stock').")";
1017  $sql .= " AND ps.fk_product = ".((int) $object->id);
1018  $sql .= " ORDER BY e.ref";
1019 
1020  $entrepotstatic = new Entrepot($db);
1021  $product_lot_static = new Productlot($db);
1022 
1023  $num = 0;
1024  $total = 0;
1025  $totalvalue = $totalvaluesell = 0;
1026  $totalwithpmp = 0;
1027 
1028  $resql = $db->query($sql);
1029  if ($resql) {
1030  $num = $db->num_rows($resql);
1031  $total = $totalwithpmp;
1032  $i = 0;
1033  $var = false;
1034  while ($i < $num) {
1035  $obj = $db->fetch_object($resql);
1036 
1037  $entrepotstatic->id = $obj->rowid;
1038  $entrepotstatic->ref = $obj->ref;
1039  $entrepotstatic->label = $obj->ref;
1040  $entrepotstatic->lieu = $obj->lieu;
1041  $entrepotstatic->fk_parent = $obj->fk_parent;
1042  $entrepotstatic->statut = $obj->status;
1043  $entrepotstatic->status = $obj->status;
1044 
1045  $stock_real = price2num($obj->reel, 'MS');
1046  print '<tr class="oddeven">';
1047 
1048  // Warehouse
1049  print '<td colspan="4">';
1050  print $entrepotstatic->getNomUrl(1);
1051  if (!empty($conf->use_javascript_ajax) && isModEnabled('productbatch') && $object->hasbatch()) {
1052  print '<a class="collapse_batch marginleftonly" id="ent' . $entrepotstatic->id . '" href="#">';
1053  print (empty($conf->global->STOCK_SHOW_ALL_BATCH_BY_DEFAULT) ? '(+)' : '(-)');
1054  print '</a>';
1055  }
1056  print '</td>';
1057 
1058  print '<td class="right">'.$stock_real.($stock_real < 0 ? ' '.img_warning() : '').'</td>';
1059 
1060  // PMP
1061  print '<td class="right nowraponall">'.(price2num($object->pmp) ? price2num($object->pmp, 'MU') : '').'</td>';
1062 
1063  // Value purchase
1064  if ($usercancreadprice) {
1065  print '<td class="right amount nowraponall">'.(price2num($object->pmp) ? price(price2num($object->pmp * $obj->reel, 'MT')) : '').'</td>';
1066  } else {
1067  print '<td class="right amount nowraponall"></td>';
1068  }
1069 
1070  // Sell price
1071  $minsellprice = null; $maxsellprice = null;
1072  print '<td class="right">';
1073  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
1074  foreach ($object->multiprices as $priceforlevel) {
1075  if (is_numeric($priceforlevel)) {
1076  if (is_null($maxsellprice) || $priceforlevel > $maxsellprice) {
1077  $maxsellprice = $priceforlevel;
1078  }
1079  if (is_null($minsellprice) || $priceforlevel < $minsellprice) {
1080  $minsellprice = $priceforlevel;
1081  }
1082  }
1083  }
1084  print '<span class="valignmiddle">';
1085  if ($usercancreadprice) {
1086  if ($minsellprice != $maxsellprice) {
1087  print price(price2num($minsellprice, 'MU'), 1).' - '.price(price2num($maxsellprice, 'MU'), 1);
1088  } else {
1089  print price(price2num($minsellprice, 'MU'), 1);
1090  }
1091  }
1092  print '</span>';
1093  print $form->textwithpicto('', $langs->trans("Variable"));
1094  } elseif ($usercancreadprice) {
1095  print price(price2num($object->price, 'MU'), 1);
1096  }
1097  print '</td>';
1098 
1099  // Value sell
1100  print '<td class="right amount nowraponall">';
1101  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
1102  print '<span class="valignmiddle">';
1103  if ($usercancreadprice) {
1104  if ($minsellprice != $maxsellprice) {
1105  print price(price2num($minsellprice * $obj->reel, 'MT'), 1).' - '.price(price2num($maxsellprice * $obj->reel, 'MT'), 1);
1106  } else {
1107  print price(price2num($minsellprice * $obj->reel, 'MT'), 1);
1108  }
1109  }
1110  print '</span>';
1111  print $form->textwithpicto('', $langs->trans("Variable"));
1112  } else {
1113  if ($usercancreadprice) {
1114  print price(price2num($object->price * $obj->reel, 'MT'), 1);
1115  }
1116  }
1117  print '</td>';
1118  print '<td></td>';
1119  print '<td></td>';
1120  print '</tr>';
1121  $total += $obj->reel;
1122  if (price2num($object->pmp)) {
1123  $totalwithpmp += $obj->reel;
1124  }
1125  $totalvalue = $totalvalue + ($object->pmp * $obj->reel);
1126  $totalvaluesell = $totalvaluesell + ($object->price * $obj->reel);
1127  // Batch Detail
1128  if ((isModEnabled('productbatch')) && $object->hasbatch()) {
1129  $details = Productbatch::findAll($db, $obj->product_stock_id, 0, $object->id);
1130  if ($details < 0) {
1131  dol_print_error($db);
1132  }
1133  foreach ($details as $pdluo) {
1134  $product_lot_static->id = $pdluo->lotid;
1135  $product_lot_static->batch = $pdluo->batch;
1136  $product_lot_static->eatby = $pdluo->eatby;
1137  $product_lot_static->sellby = $pdluo->sellby;
1138 
1139  if ($action == 'editline' && GETPOST('lineid', 'int') == $pdluo->id) { //Current line edit
1140  print "\n".'<tr>';
1141  print '<td colspan="9">';
1142  print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
1143  print '<input type="hidden" name="token" value="'.newToken().'">';
1144  print '<input type="hidden" name="pdluoid" value="'.$pdluo->id.'"><input type="hidden" name="action" value="updateline"><input type="hidden" name="id" value="'.$id.'"><table class="noborder centpercent"><tr><td width="10%"></td>';
1145  print '<td class="right" width="10%"><input type="text" name="batch_number" value="'.$pdluo->batch.'"></td>';
1146  if (empty($conf->global->PRODUCT_DISABLE_SELLBY)) {
1147  print '<td class="center" width="10%">';
1148  print $form->selectDate($pdluo->sellby, 'sellby', '', '', 1, '', 1, 0);
1149  print '</td>';
1150  }
1151  if (empty($conf->global->PRODUCT_DISABLE_EATBY)) {
1152  print '<td class="center" width="10%">';
1153  print $form->selectDate($pdluo->eatby, 'eatby', '', '', 1, '', 1, 0);
1154  print '</td>';
1155  }
1156  print '<td class="right" colspan="3">'.$pdluo->qty.($pdluo->qty < 0 ? ' '.img_warning() : '').'</td>';
1157  print '<td colspan="4"><input type="submit" class="button button-save" id="savelinebutton marginbottomonly" name="save" value="'.$langs->trans("Save").'">';
1158  print '<input type="submit" class="button button-cancel" id="cancellinebutton" name="Cancel" value="'.$langs->trans("Cancel").'"></td></tr>';
1159  print '</table>';
1160  print '</form>';
1161  print '</td>';
1162  print '<td></td>';
1163  print '<td></td>';
1164  print '</tr>';
1165  } else {
1166  print "\n".'<tr style="display:'.(empty($conf->global->STOCK_SHOW_ALL_BATCH_BY_DEFAULT) ? 'none' : 'visible').';" class="batch_warehouse'.$entrepotstatic->id.'"><td class="left">';
1167  print '</td>';
1168  print '<td class="right nowraponall">';
1169  print $product_lot_static->getNomUrl(1);
1170  print '</td>';
1171  $colspan = 3;
1172  if (empty($conf->global->PRODUCT_DISABLE_SELLBY)) {
1173  $colspan--;
1174  print '<td class="center">'.dol_print_date($pdluo->sellby, 'day').'</td>';
1175  }
1176  if (empty($conf->global->PRODUCT_DISABLE_EATBY)) {
1177  $colspan--;
1178  print '<td class="center">'.dol_print_date($pdluo->eatby, 'day').'</td>';
1179  }
1180  print '<td class="right" colspan="'.$colspan.'">'.$pdluo->qty.($pdluo->qty < 0 ? ' '.img_warning() : '').'</td>';
1181  print '<td colspan="4"></td>';
1182  print '<td class="center">';
1183  if ($entrepotstatic->status != $entrepotstatic::STATUS_CLOSED) {
1184  print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;id_entrepot='.$entrepotstatic->id.'&amp;action=transfert&amp;pdluoid='.$pdluo->id.'">';
1185  print img_picto($langs->trans("TransferStock"), 'add', 'class="hideonsmartphone paddingright" style="color: #a69944"');
1186  print $langs->trans("TransferStock");
1187  print '</a>';
1188  // Disabled, because edition of stock content must use the "Correct stock menu".
1189  // Do not use this, or data will be wrong (bad tracking of movement label, inventory code, ...
1190  //print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$id.'&action=editline&token='.newToken().'&lineid='.$pdluo->id.'#'.$pdluo->id.'">';
1191  //print img_edit().'</a>';
1192  }
1193  print '</td>';
1194  print '<td class="center">';
1195  if ($entrepotstatic->status != $entrepotstatic::STATUS_CLOSED) {
1196  print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;id_entrepot='.$entrepotstatic->id.'&amp;action=correction&amp;pdluoid='.$pdluo->id.'">';
1197  print img_picto($langs->trans("CorrectStock"), 'add', 'class="hideonsmartphone paddingright" style="color: #a69944"');
1198  print $langs->trans("CorrectStock");
1199  print '</a>';
1200  // Disabled, because edition of stock content must use the "Correct stock menu".
1201  // Do not use this, or data will be wrong (bad tracking of movement label, inventory code, ...
1202  //print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$id.'&action=editline&token='.newToken().'&lineid='.$pdluo->id.'#'.$pdluo->id.'">';
1203  //print img_edit().'</a>';
1204  }
1205  print '</td>';
1206  print '</tr>';
1207  }
1208  }
1209  }
1210  $i++;
1211  }
1212  } else {
1213  dol_print_error($db);
1214  }
1215 
1216  // Total line
1217  print '<tr class="liste_total"><td class="right liste_total" colspan="4">'.$langs->trans("Total").':</td>';
1218  print '<td class="liste_total right">'.price2num($total, 'MS').'</td>';
1219  print '<td class="liste_total right">';
1220  if ($usercancreadprice) {
1221  print ($totalwithpmp ? price(price2num($totalvalue / $totalwithpmp, 'MU')) : '&nbsp;'); // This value may have rounding errors
1222  }
1223  print '</td>';
1224  // Value purchase
1225  print '<td class="liste_total right">';
1226  if ($usercancreadprice) {
1227  print $totalvalue ? price(price2num($totalvalue, 'MT'), 1) : '&nbsp;';
1228  }
1229  print '</td>';
1230  print '<td class="liste_total right">';
1231  if ($num) {
1232  if ($total) {
1233  print '<span class="valignmiddle">';
1234  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
1235  print $form->textwithpicto('', $langs->trans("Variable"));
1236  } elseif ($usercancreadprice) {
1237  print price($totalvaluesell / $total, 1);
1238  }
1239  print '</span>';
1240  }
1241  }
1242  print '</td>';
1243  // Value to sell
1244  print '<td class="liste_total right amount">';
1245  if ($num) {
1246  print '<span class="valignmiddle">';
1247  if (empty($conf->global->PRODUIT_MULTIPRICES) && $usercancreadprice) {
1248  print price(price2num($totalvaluesell, 'MT'), 1);
1249  } else {
1250  print $form->textwithpicto('', $langs->trans("Variable"));
1251  }
1252  print '</span>';
1253  }
1254  print '</td>';
1255  print '<td></td>';
1256  print '<td></td>';
1257  print "</tr>";
1258 
1259  print "</table>";
1260  print '</div>';
1261 
1262  if (!empty($conf->global->STOCK_ALLOW_ADD_LIMIT_STOCK_BY_WAREHOUSE)) {
1263  print '<br><br>';
1264  print load_fiche_titre($langs->trans('AddNewProductStockWarehouse'));
1265 
1266  if ($user->hasRight('produit', 'creer')) {
1267  print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
1268  print '<input type="hidden" name="token" value="'.newToken().'">';
1269  print '<input type="hidden" name="action" value="addlimitstockwarehouse">';
1270  print '<input type="hidden" name="id" value="'.$id.'">';
1271  }
1272  print '<table class="noborder centpercent">';
1273  if ($user->hasRight('produit', 'creer')) {
1274  print '<tr class="liste_titre"><td>'.$formproduct->selectWarehouses('', 'fk_entrepot').'</td>';
1275  print '<td class="right"><input name="seuil_stock_alerte" type="text" placeholder="'.$langs->trans("StockLimit").'" /></td>';
1276  print '<td class="right"><input name="desiredstock" type="text" placeholder="'.$langs->trans("DesiredStock").'" /></td>';
1277  print '<td class="right"><input type="submit" value="'.$langs->trans("Save").'" class="button button-save" /></td>';
1278  print '</tr>';
1279  } else {
1280  print '<tr class="liste_titre"><td>'.$langs->trans("Warehouse").'</td>';
1281  print '<td class="right">'.$langs->trans("StockLimit").'</td>';
1282  print '<td class="right">'.$langs->trans("DesiredStock").'</td>';
1283  print '</tr>';
1284  }
1285 
1286  $pse = new ProductStockEntrepot($db);
1287  $lines = $pse->fetchAll($id);
1288 
1289  if (!empty($lines)) {
1290  $var = false;
1291  foreach ($lines as $line) {
1292  $ent = new Entrepot($db);
1293  $ent->fetch($line['fk_entrepot']);
1294  print '<tr class="oddeven"><td>'.$ent->getNomUrl(3).'</td>';
1295  print '<td class="right">'.$line['seuil_stock_alerte'].'</td>';
1296  print '<td class="right">'.$line['desiredstock'].'</td>';
1297  if ($user->hasRight('produit', 'creer')) {
1298  print '<td class="right"><a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&fk_productstockwarehouse='.$line['id'].'&action=delete_productstockwarehouse&token='.newToken().'">'.img_delete().'</a></td>';
1299  }
1300  print '</tr>';
1301  }
1302  }
1303 
1304  print "</table>";
1305 
1306  if ($user->hasRight('produit', 'creer')) {
1307  print '</form>';
1308  }
1309  }
1310 } else {
1311  // List of variants
1312  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1313  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1314  $prodstatic = new Product($db);
1315  $prodcomb = new ProductCombination($db);
1316  $comb2val = new ProductCombination2ValuePair($db);
1317  $productCombinations = $prodcomb->fetchAllByFkProductParent($object->id);
1318 
1319  print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
1320  print '<input type="hidden" name="token" value="'.newToken().'">';
1321  print '<input type="hidden" name="action" value="massaction">';
1322  print '<input type="hidden" name="id" value="'.$id.'">';
1323  print '<input type="hidden" name="backtopage" value="'.$backtopage.'">';
1324 
1325  // load variants
1326  $title = $langs->trans("ProductCombinations");
1327 
1328  print_barre_liste($title, 0, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, '', 0);
1329 
1330  print '<div class="div-table-responsive">';
1331  ?>
1332  <table class="liste">
1333  <tr class="liste_titre">
1334  <td class="liste_titre"><?php echo $langs->trans('Product') ?></td>
1335  <td class="liste_titre"><?php echo $langs->trans('Combination') ?></td>
1336  <td class="liste_titre center"><?php echo $langs->trans('OnSell') ?></td>
1337  <td class="liste_titre center"><?php echo $langs->trans('OnBuy') ?></td>
1338  <td class="liste_titre right"><?php echo $langs->trans('Stock') ?></td>
1339  <td class="liste_titre"></td>
1340  </tr>
1341  <?php
1342 
1343  if (count($productCombinations)) {
1344  $stock_total = 0;
1345  foreach ($productCombinations as $currcomb) {
1346  $prodstatic->fetch($currcomb->fk_product_child);
1347  $prodstatic->load_stock();
1348  $stock_total += $prodstatic->stock_reel;
1349  ?>
1350  <tr class="oddeven">
1351  <td><?php echo $prodstatic->getNomUrl(1) ?></td>
1352  <td>
1353  <?php
1354 
1355  $productCombination2ValuePairs = $comb2val->fetchByFkCombination($currcomb->id);
1356  $iMax = count($productCombination2ValuePairs);
1357 
1358  for ($i = 0; $i < $iMax; $i++) {
1359  echo dol_htmlentities($productCombination2ValuePairs[$i]);
1360 
1361  if ($i !== ($iMax - 1)) {
1362  echo ', ';
1363  }
1364  } ?>
1365  </td>
1366  <td style="text-align: center;"><?php echo $prodstatic->getLibStatut(2, 0) ?></td>
1367  <td style="text-align: center;"><?php echo $prodstatic->getLibStatut(2, 1) ?></td>
1368  <td class="right"><?php echo $prodstatic->stock_reel ?></td>
1369  <td class="right">
1370  <a class="paddingleft paddingright editfielda" href="<?php echo dol_buildpath('/product/stock/product.php?id='.$currcomb->fk_product_child, 2) ?>"><?php echo img_edit() ?></a>
1371  </td>
1372  <?php
1373  ?>
1374  </tr>
1375  <?php
1376  }
1377 
1378  print '<tr class="liste_total">';
1379  print '<td colspan="4" class="left">'.$langs->trans("Total").'</td>';
1380  print '<td class="right">'.$stock_total.'</td>';
1381  print '<td></td>';
1382  print '</tr>';
1383  } else {
1384  print '<tr><td colspan="8"><span class="opacitymedium">'.$langs->trans("None").'</span></td></tr>';
1385  }
1386  ?>
1387  </table>
1388 
1389  <?php
1390  print '</div>';
1391 
1392  print '</form>';
1393 }
1394 
1395 // End of page
1396 llxFooter();
1397 $db->close();
if(!defined('NOREQUIRESOC')) if(!defined('NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined('NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined('NOREQUIREAJAX')) llxHeader()
Empty header.
Definition: wrapper.php:56
llxFooter()
Empty footer.
Definition: wrapper.php:70
Class to manage canvas.
Class to manage warehouses.
const STATUS_CLOSED
Closed status.
const STATUS_VALIDATED
Validated status.
Class to manage standard extra fields.
Class to manage generation of HTML components Only common components must be here.
Class with static methods for building HTML components related to products Only components common to ...
Class to manage building of HTML components.
Class ProductCombination2ValuePair Used to represent the relation between a product combination,...
Class ProductCombination Used to represent a product combination.
Class to manage predefined suppliers products.
Class to manage products or services.
const TYPE_PRODUCT
Regular product.
const TYPE_SERVICE
Service.
Class ProductStockEntrepot.
Manage record for batch number management.
static findAll($dbs, $fk_product_stock, $with_qty=0, $fk_product=0)
Return all batch detail records for a given product and warehouse.
Class with list of lots and properties.
if(isModEnabled('facture') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $sql
Social contributions to pay.
Definition: index.php:746
if($cancel &&! $id) if($action=='add' &&! $cancel) if($action=='delete') if($id) $form
Actions.
Definition: card.php:143
dol_banner_tab($object, $paramid, $morehtml='', $shownav=1, $fieldid='rowid', $fieldref='ref', $morehtmlref='', $moreparam='', $nodbprefix=0, $morehtmlleft='', $morehtmlstatus='', $onlybanner=0, $morehtmlright='')
Show tab footer of a card.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed informations (by default a local PHP server timestamp) Re...
load_fiche_titre($titre, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='')
Load a title with picto.
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
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_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
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.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
newToken()
Return the value of token currently saved into session with name 'newtoken'.
dol_htmlentities($string, $flags=ENT_QUOTES|ENT_SUBSTITUTE, $encoding='UTF-8', $double_encode=false)
Replace htmlentities functions.
dol_htmloutput_events($disabledoutputofmessages=0)
Print formated messages to output (Used to show messages on html output).
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0)
Set event messages in dol_events session object.
print_barre_liste($titre, $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.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
img_edit($titlealt='default', $float=0, $other='')
Show logo editer/modifier fiche.
product_prepare_head($object)
Prepare array with list of tabs.
Definition: product.lib.php:36
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition: repair.php:120
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.