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