dolibarr  13.0.2
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  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 3 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program. If not, see <https://www.gnu.org/licenses/>.
25  */
26 
33 require '../../main.inc.php';
34 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
35 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
36 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
37 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
38 require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
39 require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
40 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productstockentrepot.class.php';
41 if (!empty($conf->productbatch->enabled)) {
42  require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
43 }
44 if (!empty($conf->projet->enabled)) {
45  require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php';
46  require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
47 }
48 
49 if (!empty($conf->variants->enabled)) {
50  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
51  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
52  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
53  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
54 }
55 
56 // Load translation files required by the page
57 $langs->loadlangs(array('products', 'suppliers', 'orders', 'bills', 'stocks', 'sendings', 'margins'));
58 if (!empty($conf->productbatch->enabled)) $langs->load("productbatch");
59 
60 $backtopage = GETPOST('backtopage', 'alpha');
61 $action = GETPOST('action', 'aZ09');
62 $cancel = GETPOST('cancel', 'alpha');
63 
64 $id = GETPOST('id', 'int');
65 $ref = GETPOST('ref', 'alpha');
66 $stocklimit = GETPOST('seuil_stock_alerte');
67 $desiredstock = GETPOST('desiredstock');
68 $cancel = GETPOST('cancel', 'alpha');
69 $fieldid = isset($_GET["ref"]) ? 'ref' : 'rowid';
70 $d_eatby = dol_mktime(0, 0, 0, GETPOST('eatbymonth', 'int'), GETPOST('eatbyday', 'int'), GETPOST('eatbyyear', 'int'));
71 $d_sellby = dol_mktime(0, 0, 0, GETPOST('sellbymonth', 'int'), GETPOST('sellbyday', 'int'), GETPOST('sellbyyear', 'int'));
72 $pdluoid = GETPOST('pdluoid', 'int');
73 $batchnumber = GETPOST('batch_number', 'san_alpha');
74 if (!empty($batchnumber)) {
75  $batchnumber = trim($batchnumber);
76 }
77 
78 // Security check
79 if ($user->socid) $socid = $user->socid;
80 $result = restrictedArea($user, 'produit&stock', $id, 'product&product', '', '', $fieldid);
81 
82 
83 $object = new Product($db);
84 $extrafields = new ExtraFields($db);
85 
86 // fetch optionals attributes and labels
87 $extrafields->fetch_name_optionals_label($object->table_element);
88 
89 if ($id > 0 || !empty($ref))
90 {
91  $result = $object->fetch($id, $ref);
92 }
93 
94 if (empty($id) && !empty($object->id)) $id = $object->id;
95 
96 $modulepart = 'product';
97 
98 // Get object canvas (By default, this is not defined, so standard usage of dolibarr)
99 $canvas = !empty($object->canvas) ? $object->canvas : GETPOST("canvas");
100 $objcanvas = null;
101 if (!empty($canvas))
102 {
103  require_once DOL_DOCUMENT_ROOT.'/core/class/canvas.class.php';
104  $objcanvas = new Canvas($db, $action);
105  $objcanvas->getCanvas('stockproduct', 'card', $canvas);
106 }
107 
108 // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
109 $hookmanager->initHooks(array('stockproductcard', 'globalcard'));
110 
111 $error = 0;
112 
113 
114 /*
115  * Actions
116  */
117 
118 if ($cancel) $action = '';
119 
120 $parameters = array('id'=>$id, 'ref'=>$ref, 'objcanvas'=>$objcanvas);
121 $reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
122 if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
123 
124 if ($action == 'addlimitstockwarehouse' && !empty($user->rights->produit->creer))
125 {
126  $seuil_stock_alerte = GETPOST('seuil_stock_alerte');
127  $desiredstock = GETPOST('desiredstock');
128 
129  $maj_ok = true;
130  if ($seuil_stock_alerte == '') {
131  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("StockLimit")), null, 'errors');
132  $maj_ok = false;
133  }
134  if ($desiredstock == '') {
135  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("DesiredStock")), null, 'errors');
136  $maj_ok = false;
137  }
138 
139  if ($maj_ok) {
140  $pse = new ProductStockEntrepot($db);
141  if ($pse->fetch(0, $id, GETPOST('fk_entrepot', 'int')) > 0) {
142  // Update
143  $pse->seuil_stock_alerte = $seuil_stock_alerte;
144  $pse->desiredstock = $desiredstock;
145  if ($pse->update($user) > 0) setEventMessages($langs->trans('ProductStockWarehouseUpdated'), null, 'mesgs');
146  } else {
147  // Create
148  $pse->fk_entrepot = GETPOST('fk_entrepot', 'int');
149  $pse->fk_product = $id;
150  $pse->seuil_stock_alerte = GETPOST('seuil_stock_alerte');
151  $pse->desiredstock = GETPOST('desiredstock');
152  if ($pse->create($user) > 0) setEventMessages($langs->trans('ProductStockWarehouseCreated'), null, 'mesgs');
153  }
154  }
155 
156  header("Location: ".$_SERVER["PHP_SELF"]."?id=".$id);
157  exit;
158 }
159 
160 if ($action == 'delete_productstockwarehouse' && !empty($user->rights->produit->creer))
161 {
162  $pse = new ProductStockEntrepot($db);
163 
164  $pse->fetch(GETPOST('fk_productstockwarehouse', 'int'));
165  if ($pse->delete($user) > 0) setEventMessages($langs->trans('ProductStockWarehouseDeleted'), null, 'mesgs');
166 
167  $action = '';
168 }
169 
170 // Set stock limit
171 if ($action == 'setseuil_stock_alerte' && !empty($user->rights->produit->creer))
172 {
173  $object = new Product($db);
174  $result = $object->fetch($id);
175  $object->seuil_stock_alerte = $stocklimit;
176  $result = $object->update($object->id, $user, 0, 'update');
177  if ($result < 0)
178  setEventMessages($object->error, $object->errors, 'errors');
179  //else
180  // setEventMessages($lans->trans("SavedRecordSuccessfully"), null, 'mesgs');
181  $action = '';
182 }
183 
184 // Set desired stock
185 if ($action == 'setdesiredstock' && !empty($user->rights->produit->creer))
186 {
187  $object = new Product($db);
188  $result = $object->fetch($id);
189  $object->desiredstock = $desiredstock;
190  $result = $object->update($object->id, $user, 0, 'update');
191  if ($result < 0)
192  setEventMessages($object->error, $object->errors, 'errors');
193  $action = '';
194 }
195 
196 
197 // Correct stock
198 if ($action == "correct_stock" && !$cancel)
199 {
200  if (!(GETPOST("id_entrepot", 'int') > 0))
201  {
202  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Warehouse")), null, 'errors');
203  $error++;
204  $action = 'correction';
205  }
206  if (!GETPOST("nbpiece"))
207  {
208  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("NumberOfUnit")), null, 'errors');
209  $error++;
210  $action = 'correction';
211  }
212 
213  if (!empty($conf->productbatch->enabled))
214  {
215  $object = new Product($db);
216  $result = $object->fetch($id);
217 
218  if ($object->hasbatch() && !$batchnumber)
219  {
220  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("batch_number")), null, 'errors');
221  $error++;
222  $action = 'correction';
223  }
224  }
225 
226  if (!$error) {
227  $priceunit = price2num(GETPOST("unitprice"));
228  $nbpiece = price2num(GETPOST("nbpiece", 'alphanohtml'));
229  if (is_numeric($nbpiece) && $nbpiece != 0 && $id) {
230  $origin_element = '';
231  $origin_id = null;
232 
233  if (GETPOST('projectid', 'int')) {
234  $origin_element = 'project';
235  $origin_id = GETPOST('projectid', 'int');
236  }
237 
238  if (empty($object)) {
239  $object = new Product($db);
240  $result = $object->fetch($id);
241  }
242 
243  $disablestockchangeforsubproduct = 0;
244  if (GETPOST('disablesubproductstockchange')) {
245  $disablestockchangeforsubproduct = 1;
246  }
247 
248  if ($object->hasbatch()) {
249  $result = $object->correct_stock_batch(
250  $user,
251  GETPOST("id_entrepot", 'int'),
252  $nbpiece,
253  GETPOST("mouvement", 'int'),
254  GETPOST("label", 'alphanohtml'), // label movement
255  $priceunit,
256  $d_eatby,
257  $d_sellby,
258  $batchnumber,
259  GETPOST('inventorycode', 'alphanohtml'),
260  $origin_element,
261  $origin_id,
262  $disablestockchangeforsubproduct
263  ); // We do not change value of stock for a correction
264  } else {
265  $result = $object->correct_stock(
266  $user,
267  GETPOST("id_entrepot", 'int'),
268  $nbpiece,
269  GETPOST("mouvement", 'int'),
270  GETPOST("label", 'alphanohtml'),
271  $priceunit,
272  GETPOST('inventorycode', 'alphanohtml'),
273  $origin_element,
274  $origin_id,
275  $disablestockchangeforsubproduct
276  ); // We do not change value of stock for a correction
277  }
278 
279  if ($result > 0) {
280  if ($backtopage) {
281  header("Location: ".$backtopage);
282  exit;
283  } else {
284  header("Location: ".$_SERVER["PHP_SELF"]."?id=".$object->id);
285  exit;
286  }
287  } else {
288  setEventMessages($object->error, $object->errors, 'errors');
289  $action = 'correction';
290  }
291  }
292  }
293 }
294 
295 // Transfer stock from a warehouse to another warehouse
296 if ($action == "transfert_stock" && !$cancel)
297 {
298  if (!(GETPOST("id_entrepot", 'int') > 0) || !(GETPOST("id_entrepot_destination", 'int') > 0))
299  {
300  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Warehouse")), null, 'errors');
301  $error++;
302  $action = 'transfert';
303  }
304  if (!GETPOST("nbpiece", 'int'))
305  {
306  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("NumberOfUnit")), null, 'errors');
307  $error++;
308  $action = 'transfert';
309  }
310  if (GETPOST("id_entrepot", 'int') == GETPOST("id_entrepot_destination", 'int'))
311  {
312  setEventMessages($langs->trans("ErrorSrcAndTargetWarehouseMustDiffers"), null, 'errors');
313  $error++;
314  $action = 'transfert';
315  }
316  if (!empty($conf->productbatch->enabled))
317  {
318  $object = new Product($db);
319  $result = $object->fetch($id);
320 
321  if ($object->hasbatch() && !$batchnumber)
322  {
323  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("batch_number")), null, 'errors');
324  $error++;
325  $action = 'transfert';
326  }
327  }
328 
329  if (!$error)
330  {
331  if ($id)
332  {
333  $object = new Product($db);
334  $result = $object->fetch($id);
335 
336  $db->begin();
337 
338  $object->load_stock('novirtual'); // Load array product->stock_warehouse
339 
340  // Define value of products moved
341  $pricesrc = 0;
342  if (isset($object->pmp)) $pricesrc = $object->pmp;
343  $pricedest = $pricesrc;
344 
345  $nbpiece = price2num(GETPOST("nbpiece", 'alphanohtml'));
346 
347  if ($object->hasbatch())
348  {
349  $pdluo = new Productbatch($db);
350 
351  if ($pdluoid > 0)
352  {
353  $result = $pdluo->fetch($pdluoid);
354  if ($result)
355  {
356  $srcwarehouseid = $pdluo->warehouseid;
357  $batch = $pdluo->batch;
358  $eatby = $pdluo->eatby;
359  $sellby = $pdluo->sellby;
360  } else {
361  setEventMessages($pdluo->error, $pdluo->errors, 'errors');
362  $error++;
363  }
364  } else {
365  $srcwarehouseid = GETPOST('id_entrepot', 'int');
366  $batch = $batchnumber;
367  $eatby = $d_eatby;
368  $sellby = $d_sellby;
369  }
370 
371  $nbpiece = price2num(GETPOST("nbpiece", 'alphanohtml'));
372 
373  if (!$error)
374  {
375  // Remove stock
376  $result1 = $object->correct_stock_batch(
377  $user,
378  $srcwarehouseid,
379  $nbpiece,
380  1,
381  GETPOST("label", 'alphanohtml'),
382  $pricesrc,
383  $eatby, $sellby, $batch,
384  GETPOST('inventorycode', 'alphanohtml')
385  );
386  if ($result1 < 0) $error++;
387  }
388  if (!$error)
389  {
390  // Add stock
391  $result2 = $object->correct_stock_batch(
392  $user,
393  GETPOST("id_entrepot_destination", 'int'),
394  $nbpiece,
395  0,
396  GETPOST("label", 'alphanohtml'),
397  $pricedest,
398  $eatby, $sellby, $batch,
399  GETPOST('inventorycode', 'alphanohtml')
400  );
401  if ($result2 < 0) $error++;
402  }
403  } else {
404  if (!$error)
405  {
406  // Remove stock
407  $result1 = $object->correct_stock(
408  $user,
409  GETPOST("id_entrepot", 'int'),
410  $nbpiece,
411  1,
412  GETPOST("label", 'alphanohtml'),
413  $pricesrc,
414  GETPOST('inventorycode', 'alphanohtml')
415  );
416  if ($result1 < 0) $error++;
417  }
418  if (!$error)
419  {
420  // Add stock
421  $result2 = $object->correct_stock(
422  $user,
423  GETPOST("id_entrepot_destination", 'int'),
424  $nbpiece,
425  0,
426  GETPOST("label", 'alphanohtml'),
427  $pricedest,
428  GETPOST('inventorycode', 'alphanohtml')
429  );
430  if ($result2 < 0) $error++;
431  }
432  }
433 
434 
435  if (!$error && $result1 >= 0 && $result2 >= 0)
436  {
437  $db->commit();
438 
439  if ($backtopage)
440  {
441  header("Location: ".$backtopage);
442  exit;
443  } else {
444  header("Location: product.php?id=".$object->id);
445  exit;
446  }
447  } else {
448  setEventMessages($object->error, $object->errors, 'errors');
449  $db->rollback();
450  $action = 'transfert';
451  }
452  }
453  }
454 }
455 
456 // Update batch information
457 if ($action == 'updateline' && GETPOST('save') == $langs->trans("Save"))
458 {
459  $pdluo = new Productbatch($db);
460  $result = $pdluo->fetch(GETPOST('pdluoid', 'int'));
461 
462  if ($result > 0)
463  {
464  if ($pdluo->id)
465  {
466  if ((!GETPOST("sellby")) && (!GETPOST("eatby")) && (!$batchnumber)) {
467  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("atleast1batchfield")), null, 'errors');
468  } else {
469  $d_eatby = dol_mktime(0, 0, 0, $_POST['eatbymonth'], $_POST['eatbyday'], $_POST['eatbyyear']);
470  $d_sellby = dol_mktime(0, 0, 0, $_POST['sellbymonth'], $_POST['sellbyday'], $_POST['sellbyyear']);
471  $pdluo->batch = $batchnumber;
472  $pdluo->eatby = $d_eatby;
473  $pdluo->sellby = $d_sellby;
474  $result = $pdluo->update($user);
475  if ($result < 0)
476  {
477  setEventMessages($pdluo->error, $pdluo->errors, 'errors');
478  }
479  }
480  } else {
481  setEventMessages($langs->trans('BatchInformationNotfound'), null, 'errors');
482  }
483  } else {
484  setEventMessages($pdluo->error, null, 'errors');
485  }
486  header("Location: product.php?id=".$id);
487  exit;
488 }
489 
490 
491 
492 /*
493  * View
494  */
495 
496 $form = new Form($db);
497 $formproduct = new FormProduct($db);
498 if (!empty($conf->projet->enabled)) $formproject = new FormProjets($db);
499 
500 if ($id > 0 || $ref)
501 {
502  $object = new Product($db);
503  $result = $object->fetch($id, $ref);
504 
505  $variants = $object->hasVariants();
506 
507  $object->load_stock();
508 
509  $title = $langs->trans('ProductServiceCard');
510  $helpurl = '';
511  $shortlabel = dol_trunc($object->label, 16);
512  if (GETPOST("type") == '0' || ($object->type == Product::TYPE_PRODUCT))
513  {
514  $title = $langs->trans('Product')." ".$shortlabel." - ".$langs->trans('Stock');
515  $helpurl = 'EN:Module_Products|FR:Module_Produits|ES:M&oacute;dulo_Productos';
516  }
517  if (GETPOST("type") == '1' || ($object->type == Product::TYPE_SERVICE))
518  {
519  $title = $langs->trans('Service')." ".$shortlabel." - ".$langs->trans('Stock');
520  $helpurl = 'EN:Module_Services_En|FR:Module_Services|ES:M&oacute;dulo_Servicios';
521  }
522 
523  llxHeader('', $title, $helpurl);
524 
525  if ($result > 0)
526  {
527  $head = product_prepare_head($object);
528  $titre = $langs->trans("CardProduct".$object->type);
529  $picto = ($object->type == Product::TYPE_SERVICE ? 'service' : 'product');
530 
531  print dol_get_fiche_head($head, 'stock', $titre, -1, $picto);
532 
534 
535  $linkback = '<a href="'.DOL_URL_ROOT.'/product/list.php?restore_lastsearch_values=1">'.$langs->trans("BackToList").'</a>';
536 
537  $shownav = 1;
538  if ($user->socid && !in_array('stock', explode(',', $conf->global->MAIN_MODULES_FOR_EXTERNAL))) $shownav = 0;
539 
540  dol_banner_tab($object, 'ref', $linkback, $shownav, 'ref');
541 
542  if (!$variants) {
543  print '<div class="fichecenter">';
544 
545  print '<div class="fichehalfleft">';
546  print '<div class="underbanner clearboth"></div>';
547 
548  print '<table class="border tableforfield centpercent">';
549 
550  if ($conf->productbatch->enabled) {
551  print '<tr><td class="titlefield">'.$langs->trans("ManageLotSerial").'</td><td>';
552  print $object->getLibStatut(0, 2);
553  print '</td></tr>';
554  }
555 
556  // Cost price. Can be used for margin module for option "calculate margin on explicit cost price
557  print '<tr><td>';
558  $textdesc = $langs->trans("CostPriceDescription");
559  $textdesc .= "<br>".$langs->trans("CostPriceUsage");
560  $text = $form->textwithpicto($langs->trans("CostPrice"), $textdesc, 1, 'help', '');
561  print $form->editfieldkey($text, 'cost_price', $object->cost_price, $object, $usercancreate, 'amount:6');
562  print '</td><td colspan="2">';
563  print $form->editfieldval($text, 'cost_price', $object->cost_price, $object, $usercancreate, 'amount:6');
564  print '</td></tr>';
565 
566  // AWP
567  print '<tr><td class="titlefield">'.$form->textwithpicto($langs->trans("AverageUnitPricePMPShort"), $langs->trans("AverageUnitPricePMPDesc")).'</td>';
568  print '<td>';
569  if ($object->pmp > 0) print price($object->pmp).' '.$langs->trans("HT");
570  print '</td>';
571  print '</tr>';
572 
573  // Minimum Price
574  print '<tr><td>'.$langs->trans("BuyingPriceMin").'</td>';
575  print '<td>';
576  $product_fourn = new ProductFournisseur($db);
577  if ($product_fourn->find_min_price_product_fournisseur($object->id) > 0) {
578  if ($product_fourn->product_fourn_price_id > 0) print $product_fourn->display_price_product_fournisseur();
579  else print $langs->trans("NotDefined");
580  }
581  print '</td></tr>';
582 
583  if (empty($conf->global->PRODUIT_MULTIPRICES)) {
584  // Price
585  print '<tr><td>'.$langs->trans("SellingPrice").'</td><td>';
586  if ($object->price_base_type == 'TTC') {
587  print price($object->price_ttc).' '.$langs->trans($object->price_base_type);
588  } else {
589  print price($object->price).' '.$langs->trans($object->price_base_type);
590  }
591  print '</td></tr>';
592 
593  // Price minimum
594  print '<tr><td>'.$langs->trans("MinPrice").'</td><td>';
595  if ($object->price_base_type == 'TTC') {
596  print price($object->price_min_ttc).' '.$langs->trans($object->price_base_type);
597  } else {
598  print price($object->price_min).' '.$langs->trans($object->price_base_type);
599  }
600  print '</td></tr>';
601  } else {
602  // Price
603  print '<tr><td>'.$langs->trans("SellingPrice").'</td><td>';
604  print $langs->trans("Variable");
605  print '</td></tr>';
606 
607  // Price minimum
608  print '<tr><td>'.$langs->trans("MinPrice").'</td><td>';
609  print $langs->trans("Variable");
610  print '</td></tr>';
611  }
612 
613  // Hook formObject
614  $parameters = array();
615  $reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
616  print $hookmanager->resPrint;
617 
618  print '</table>';
619 
620  print '</div>';
621  print '<div class="fichehalfright"><div class="ficheaddleft"><div class="underbanner clearboth"></div>';
622 
623  print '<table class="border tableforfield centpercent">';
624 
625  // Stock alert threshold
626  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>';
627  print $form->editfieldval("StockLimit", 'seuil_stock_alerte', $object->seuil_stock_alerte, $object, $user->rights->produit->creer, 'string');
628  print '</td></tr>';
629 
630  // Desired stock
631  print '<tr><td>'.$form->editfieldkey($form->textwithpicto($langs->trans("DesiredStock"), $langs->trans("DesiredStockDesc"), 1), 'desiredstock', $object->desiredstock, $object, $user->rights->produit->creer);
632  print '</td><td>';
633  print $form->editfieldval("DesiredStock", 'desiredstock', $object->desiredstock, $object, $user->rights->produit->creer, 'string');
634  print '</td></tr>';
635 
636  // Real stock
637  $text_stock_options = $langs->trans("RealStockDesc").'<br>';
638  $text_stock_options .= $langs->trans("RealStockWillAutomaticallyWhen").'<br>';
639  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE) ? '- '.$langs->trans("DeStockOnShipment").'<br>' : '');
640  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER) ? '- '.$langs->trans("DeStockOnValidateOrder").'<br>' : '');
641  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_BILL) ? '- '.$langs->trans("DeStockOnBill").'<br>' : '');
642  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL) ? '- '.$langs->trans("ReStockOnBill").'<br>' : '');
643  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER) ? '- '.$langs->trans("ReStockOnValidateOrder").'<br>' : '');
644  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER) ? '- '.$langs->trans("ReStockOnDispatchOrder").'<br>' : '');
645  $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_RECEPTION) || !empty($conf->global->STOCK_CALCULATE_ON_RECEPTION_CLOSE) ? '- '.$langs->trans("StockOnReception").'<br>' : '');
646 
647  print '<tr><td>';
648  print $form->textwithpicto($langs->trans("PhysicalStock"), $text_stock_options, 1);
649  print '</td>';
650  print '<td>'.price2num($object->stock_reel, 'MS');
651  if ($object->seuil_stock_alerte != '' && ($object->stock_reel < $object->seuil_stock_alerte)) print ' '.img_warning($langs->trans("StockLowerThanLimit", $object->seuil_stock_alerte));
652 
653  print ' &nbsp; &nbsp;<a href="'.DOL_URL_ROOT.'/product/stock/stockatdate.php?productid='.$object->id.'">'.$langs->trans("StockAtDate").'</a>';
654  print '</td>';
655  print '</tr>';
656 
657  $stocktheo = price2num($object->stock_theorique, 'MS');
658 
659  $found = 0;
660  $helpondiff = '<strong>'.$langs->trans("StockDiffPhysicTeoric").':</strong><br>';
661  // Number of customer orders running
662  if (!empty($conf->commande->enabled)) {
663  if ($found) $helpondiff .= '<br>'; else $found = 1;
664  $helpondiff .= $langs->trans("ProductQtyInCustomersOrdersRunning").': '.$object->stats_commande['qty'];
665  $result = $object->load_stats_commande(0, '0', 1);
666  if ($result < 0) dol_print_error($db, $object->error);
667  $helpondiff .= ' ('.$langs->trans("ProductQtyInDraft").': '.$object->stats_commande['qty'].')';
668  }
669 
670  // Number of product from customer order already sent (partial shipping)
671  if (!empty($conf->expedition->enabled)) {
672  require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
673  $filterShipmentStatus = '';
674  if (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)) {
675  $filterShipmentStatus = Expedition::STATUS_VALIDATED.','.Expedition::STATUS_CLOSED;
676  } elseif (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)) {
677  $filterShipmentStatus = Expedition::STATUS_CLOSED;
678  }
679  if ($found) $helpondiff .= '<br>'; else $found = 1;
680  $result = $object->load_stats_sending(0, '2', 1, $filterShipmentStatus);
681  $helpondiff .= $langs->trans("ProductQtyInShipmentAlreadySent").': '.$object->stats_expedition['qty'];
682  }
683 
684  // Number of supplier order running
685  if (!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) || !empty($conf->supplier_order->enabled) || !empty($conf->supplier_invoice->enabled))
686  {
687  if ($found) $helpondiff .= '<br>'; else $found = 1;
688  $result = $object->load_stats_commande_fournisseur(0, '3,4', 1);
689  $helpondiff .= $langs->trans("ProductQtyInSuppliersOrdersRunning").': '.$object->stats_commande_fournisseur['qty'];
690  $result = $object->load_stats_commande_fournisseur(0, '0,1,2', 1);
691  if ($result < 0) dol_print_error($db, $object->error);
692  $helpondiff .= ' ('.$langs->trans("ProductQtyInDraftOrWaitingApproved").': '.$object->stats_commande_fournisseur['qty'].')';
693  }
694 
695  // Number of product from supplier order already received (partial receipt)
696  if (!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) || !empty($conf->supplier_order->enabled) || !empty($conf->supplier_invoice->enabled)) {
697  if ($found) $helpondiff .= '<br>'; else $found = 1;
698  $helpondiff .= $langs->trans("ProductQtyInSuppliersShipmentAlreadyRecevied").': '.$object->stats_reception['qty'];
699  }
700 
701  // Number of product in production
702  if (!empty($conf->mrp->enabled)) {
703  if ($found) $helpondiff .= '<br>'; else $found = 1;
704  $helpondiff .= $langs->trans("ProductQtyToConsumeByMO").': '.$object->stats_mrptoconsume['qty'].'<br>';
705  $helpondiff .= $langs->trans("ProductQtyToProduceByMO").': '.$object->stats_mrptoproduce['qty'];
706  }
707 
708 
709  // Calculating a theorical value
710  print '<tr><td>';
711  print $form->textwithpicto($langs->trans("VirtualStock"), $langs->trans("VirtualStockDesc"));
712  print '</td>';
713  print "<td>";
714  //print (empty($stocktheo)?0:$stocktheo);
715  print $form->textwithpicto((empty($stocktheo) ? 0 : $stocktheo), $helpondiff);
716  if ($object->seuil_stock_alerte != '' && ($object->stock_theorique < $object->seuil_stock_alerte)) print ' '.img_warning($langs->trans("StockLowerThanLimit", $object->seuil_stock_alerte));
717  print ' &nbsp; &nbsp;<a href="'.DOL_URL_ROOT.'/product/stock/stockatdate.php?mode=future&productid='.$object->id.'">'.$langs->trans("VirtualStockAtDate").'</a>';
718  print '</td>';
719  print '</tr>';
720 
721  // Last movement
722  if (!empty($user->rights->stock->mouvement->lire))
723  {
724  $sql = "SELECT max(m.datem) as datem";
725  $sql .= " FROM ".MAIN_DB_PREFIX."stock_mouvement as m";
726  $sql .= " WHERE m.fk_product = ".((int) $object->id);
727  $resqlbis = $db->query($sql);
728  if ($resqlbis) {
729  $obj = $db->fetch_object($resqlbis);
730  $lastmovementdate = $db->jdate($obj->datem);
731  } else {
732  dol_print_error($db);
733  }
734  print '<tr><td class="tdtop">'.$langs->trans("LastMovement").'</td><td>';
735  if ($lastmovementdate) {
736  print dol_print_date($lastmovementdate, 'dayhour').' ';
737  print ' &nbsp; &nbsp;<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?idproduct='.$object->id.'">'.$langs->trans("FullList").'</a>';
738  } else {
739  print '<a href="'.DOL_URL_ROOT.'/product/stock/movement_list.php?idproduct='.$object->id.'">'.$langs->trans("None").'</a>';
740  }
741  print "</td></tr>";
742  }
743 
744  print "</table>";
745 
746  print '</div>';
747  print '</div>';
748  print '</div>';
749 
750  print '<div style="clear:both"></div>';
751  }
752 
754  }
755 
756  // Correct stock
757  if ($action == "correction")
758  {
759  include DOL_DOCUMENT_ROOT.'/product/stock/tpl/stockcorrection.tpl.php';
760  print '<br><br>';
761  }
762 
763  // Transfer of units
764  if ($action == "transfert")
765  {
766  include DOL_DOCUMENT_ROOT.'/product/stock/tpl/stocktransfer.tpl.php';
767  print '<br><br>';
768  }
769 } else {
770  dol_print_error();
771 }
772 
773 
774 // Actions buttons
775 
776 $parameters = array();
777 
778 $reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
779 if (empty($reshook))
780 {
781  if (empty($action) && $object->id)
782  {
783  print "<div class=\"tabsAction\">\n";
784 
785  if ($user->rights->stock->mouvement->creer)
786  {
787  if (!$variants || !empty($conf->global->VARIANT_ALLOW_STOCK_MOVEMENT_ON_VARIANT_PARENT)) {
788  print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;action=correction">'.$langs->trans("CorrectStock").'</a>';
789  } else {
790  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("ActionAvailableOnVariantProductOnly").'">'.$langs->trans("CorrectStock").'</a>';
791  }
792  } else {
793  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans("CorrectStock").'</a>';
794  }
795 
796  //if (($user->rights->stock->mouvement->creer) && ! $object->hasbatch())
797  if ($user->rights->stock->mouvement->creer)
798  {
799  if (!$variants || !empty($conf->global->VARIANT_ALLOW_STOCK_MOVEMENT_ON_VARIANT_PARENT)) {
800  print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;action=transfert">'.$langs->trans("TransferStock").'</a>';
801  } else {
802  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("ActionAvailableOnVariantProductOnly").'">'.$langs->trans("TransferStock").'</a>';
803  }
804  } else {
805  print '<a class="butActionRefused classfortooltip" href="#" title="'.$langs->trans("NotEnoughPermissions").'">'.$langs->trans("CorrectStock").'</a>';
806  }
807 
808  print '</div>';
809  }
810 }
811 
812 
813 if (!$variants) {
814  /*
815  * Stock detail (by warehouse). May go down into batch details.
816  */
817 
818  print '<div class="div-table-responsive">';
819  print '<table class="noborder centpercent">';
820 
821  print '<tr class="liste_titre">';
822  print '<td colspan="4">'.$langs->trans("Warehouse").'</td>';
823  print '<td class="right">'.$langs->trans("NumberOfUnit").'</td>';
824  print '<td class="right">'.$form->textwithpicto($langs->trans("AverageUnitPricePMPShort"), $langs->trans("AverageUnitPricePMPDesc")).'</td>';
825  print '<td class="right">'.$langs->trans("EstimatedStockValueShort").'</td>';
826  print '<td class="right">'.$langs->trans("SellPriceMin").'</td>';
827  print '<td class="right">'.$langs->trans("EstimatedStockValueSellShort").'</td>';
828  print '</tr>';
829  if ((!empty($conf->productbatch->enabled)) && $object->hasbatch()) {
830  $colspan = 3;
831  print '<tr class="liste_titre"><td width="10%"></td>';
832  print '<td class="right" width="10%">'.$langs->trans("batch_number").'</td>';
833  if (empty($conf->global->PRODUCT_DISABLE_EATBY)) {
834  $colspan--;
835  print '<td class="center" width="10%">'.$langs->trans("EatByDate").'</td>';
836  }
837  if (empty($conf->global->PRODUCT_DISABLE_SELLBY)) {
838  $colspan--;
839  print '<td class="center" width="10%">'.$langs->trans("SellByDate").'</td>';
840  }
841  print '<td colspan="'.$colspan.'"></td>';
842  print '<td></td>';
843  print '<td></td>';
844  print '<td></td>';
845  print '<td></td>';
846  print '</tr>';
847  }
848 
849  $sql = "SELECT e.rowid, e.ref, e.lieu, e.fk_parent, e.statut, ps.reel, ps.rowid as product_stock_id, p.pmp";
850  $sql .= " FROM ".MAIN_DB_PREFIX."entrepot as e,";
851  $sql .= " ".MAIN_DB_PREFIX."product_stock as ps";
852  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = ps.fk_product";
853  $sql .= " WHERE ps.reel != 0";
854  $sql .= " AND ps.fk_entrepot = e.rowid";
855  $sql .= " AND e.entity IN (".getEntity('stock').")";
856  $sql .= " AND ps.fk_product = ".$object->id;
857  $sql .= " ORDER BY e.ref";
858 
859  $entrepotstatic = new Entrepot($db);
860  $product_lot_static = new Productlot($db);
861 
862  $total = 0;
863  $totalvalue = $totalvaluesell = 0;
864 
865  $resql = $db->query($sql);
866  if ($resql) {
867  $num = $db->num_rows($resql);
868  $total = $totalwithpmp;
869  $i = 0;
870  $var = false;
871  while ($i < $num) {
872  $obj = $db->fetch_object($resql);
873 
874  $entrepotstatic->id = $obj->rowid;
875  $entrepotstatic->ref = $obj->ref;
876  $entrepotstatic->label = $obj->ref;
877  $entrepotstatic->lieu = $obj->lieu;
878  $entrepotstatic->fk_parent = $obj->fk_parent;
879  $entrepotstatic->statut = $obj->statut;
880 
881  $stock_real = price2num($obj->reel, 'MS');
882  print '<tr class="oddeven">';
883  print '<td colspan="4">'.$entrepotstatic->getNomUrl(1).'</td>';
884  print '<td class="right">'.$stock_real.($stock_real < 0 ? ' '.img_warning() : '').'</td>';
885  // PMP
886  print '<td class="right">'.(price2num($object->pmp) ? price2num($object->pmp, 'MU') : '').'</td>';
887  // Value purchase
888  print '<td class="right">'.(price2num($object->pmp) ? price(price2num($object->pmp * $obj->reel, 'MT')) : '').'</td>';
889  // Sell price
890  print '<td class="right">';
891  if (empty($conf->global->PRODUIT_MULTIPRICES)) print price(price2num($object->price, 'MU'), 1);
892  else print $langs->trans("Variable");
893  print '</td>';
894  // Value sell
895  print '<td class="right">';
896  if (empty($conf->global->PRODUIT_MULTIPRICES)) print price(price2num($object->price * $obj->reel, 'MT'), 1).'</td>';
897  else print $langs->trans("Variable");
898  print '</tr>';
899  $total += $obj->reel;
900  if (price2num($object->pmp)) $totalwithpmp += $obj->reel;
901  $totalvalue = $totalvalue + ($object->pmp * $obj->reel);
902  $totalvaluesell = $totalvaluesell + ($object->price * $obj->reel);
903  // Batch Detail
904  if ((!empty($conf->productbatch->enabled)) && $object->hasbatch()) {
905  $details = Productbatch::findAll($db, $obj->product_stock_id, 0, $object->id);
906  if ($details < 0) dol_print_error($db);
907  foreach ($details as $pdluo) {
908  $product_lot_static->id = $pdluo->lotid;
909  $product_lot_static->batch = $pdluo->batch;
910  $product_lot_static->eatby = $pdluo->eatby;
911  $product_lot_static->sellby = $pdluo->sellby;
912 
913  if ($action == 'editline' && GETPOST('lineid', 'int') == $pdluo->id) { //Current line edit
914  print "\n".'<tr>';
915  print '<td colspan="9">';
916  print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
917  print '<input type="hidden" name="token" value="'.newToken().'">';
918  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>';
919  print '<td class="right" width="10%"><input type="text" name="batch_number" value="'.$pdluo->batch.'"></td>';
920  if (empty($conf->global->PRODUCT_DISABLE_EATBY)) {
921  print '<td class="center" width="10%">';
922  print $form->selectDate($pdluo->eatby, 'eatby', '', '', 1, '', 1, 0);
923  print '</td>';
924  }
925  if (empty($conf->global->PRODUCT_DISABLE_SELLBY)) {
926  print '<td class="center" width="10%">';
927  print $form->selectDate($pdluo->sellby, 'sellby', '', '', 1, '', 1, 0);
928  print '</td>';
929  }
930  print '<td class="right" colspan="3">'.$pdluo->qty.($pdluo->qty < 0 ? ' '.img_warning() : '').'</td>';
931  print '<td colspan="4"><input type="submit" class="button button-save" id="savelinebutton marginbottomonly" name="save" value="'.$langs->trans("Save").'">';
932  print '<input type="submit" class="button button-cancel" id="cancellinebutton" name="Cancel" value="'.$langs->trans("Cancel").'"></td></tr>';
933  print '</table>';
934  print '</form>';
935  print '</td></tr>';
936  } else {
937  print "\n".'<tr><td class="right">';
938  print img_picto($langs->trans("Tranfer"), 'uparrow', 'class="hideonsmartphone"').' ';
939  print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;id_entrepot='.$entrepotstatic->id.'&amp;action=transfert&amp;pdluoid='.$pdluo->id.'">'.$langs->trans("TransferStock").'</a>';
940  // Disabled, because edition of stock content must use the "Correct stock menu".
941  // Do not use this, or data will be wrong (bad tracking of movement label, inventory code, ...
942  //print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$id.'&amp;action=editline&amp;lineid='.$pdluo->id.'#'.$pdluo->id.'">';
943  //print img_edit().'</a></td>';
944  print '<td class="right">';
945  print $product_lot_static->getNomUrl(1);
946  print '</td>';
947  $colspan = 3;
948  if (empty($conf->global->PRODUCT_DISABLE_EATBY)) {
949  $colspan--;
950  print '<td class="center">'.dol_print_date($pdluo->eatby, 'day').'</td>';
951  }
952  if (empty($conf->global->PRODUCT_DISABLE_SELLBY)) {
953  $colspan--;
954  print '<td class="center">'.dol_print_date($pdluo->sellby, 'day').'</td>';
955  }
956  print '<td class="right" colspan="'.$colspan.'">'.$pdluo->qty.($pdluo->qty < 0 ? ' '.img_warning() : '').'</td>';
957  print '<td colspan="4"></td>';
958  print '</tr>';
959  }
960  }
961  }
962  $i++;
963  }
964  } else dol_print_error($db);
965 
966  // Total line
967  print '<tr class="liste_total"><td class="right liste_total" colspan="4">'.$langs->trans("Total").':</td>';
968  print '<td class="liste_total right">'.price2num($total, 'MS').'</td>';
969  print '<td class="liste_total right">';
970  print ($totalwithpmp ? price(price2num($totalvalue / $totalwithpmp, 'MU')) : '&nbsp;'); // This value may have rounding errors
971  print '</td>';
972  // Value purchase
973  print '<td class="liste_total right">';
974  print $totalvalue ? price(price2num($totalvalue, 'MT'), 1) : '&nbsp;';
975  print '</td>';
976  print '<td class="liste_total right">';
977  if (empty($conf->global->PRODUIT_MULTIPRICES)) print ($total ? price($totalvaluesell / $total, 1) : '&nbsp;');
978  else print $langs->trans("Variable");
979  print '</td>';
980  // Value to sell
981  print '<td class="liste_total right">';
982  if (empty($conf->global->PRODUIT_MULTIPRICES)) print price(price2num($totalvaluesell, 'MT'), 1);
983  else print $langs->trans("Variable");
984  print '</td>';
985  print "</tr>";
986 
987  print "</table>";
988  print '</div>';
989 
990  if (!empty($conf->global->STOCK_ALLOW_ADD_LIMIT_STOCK_BY_WAREHOUSE)) {
991  print '<br><br>';
992  print load_fiche_titre($langs->trans('AddNewProductStockWarehouse'));
993 
994  if (!empty($user->rights->produit->creer)) {
995  print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
996  print '<input type="hidden" name="token" value="'.newToken().'">';
997  print '<input type="hidden" name="action" value="addlimitstockwarehouse">';
998  print '<input type="hidden" name="id" value="'.$id.'">';
999  }
1000  print '<table class="noborder centpercent">';
1001  if (!empty($user->rights->produit->creer)) {
1002  print '<tr class="liste_titre"><td width="40%">'.$formproduct->selectWarehouses('', 'fk_entrepot').'</td>';
1003  print '<td class="right"><input name="seuil_stock_alerte" type="text" placeholder="'.$langs->trans("StockLimit").'" /></td>';
1004  print '<td class="right"><input name="desiredstock" type="text" placeholder="'.$langs->trans("DesiredStock").'" /></td>';
1005  print '<td class="right"><input type="submit" value="'.$langs->trans("Save").'" class="button button-save" /></td>';
1006  print '</tr>';
1007  } else {
1008  print '<tr class="liste_titre"><td width="40%">'.$langs->trans("Warehouse").'</td>';
1009  print '<td class="right">'.$langs->trans("StockLimit").'</td>';
1010  print '<td class="right">'.$langs->trans("DesiredStock").'</td>';
1011  print '</tr>';
1012  }
1013 
1014  $pse = new ProductStockEntrepot($db);
1015  $lines = $pse->fetchAll($id);
1016 
1017  if (!empty($lines)) {
1018  $var = false;
1019  foreach ($lines as $line) {
1020  $ent = new Entrepot($db);
1021  $ent->fetch($line['fk_entrepot']);
1022  print '<tr class="oddeven"><td width="40%">'.$ent->getNomUrl(3).'</td>';
1023  print '<td class="right">'.$line['seuil_stock_alerte'].'</td>';
1024  print '<td class="right">'.$line['desiredstock'].'</td>';
1025  if (!empty($user->rights->produit->creer)) {
1026  print '<td class="right"><a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&fk_productstockwarehouse='.$line['id'].'&action=delete_productstockwarehouse&token='.newToken().'">'.img_delete().'</a></td>';
1027  }
1028  print '</tr>';
1029  }
1030  }
1031 
1032  print "</table>";
1033 
1034  if (!empty($user->rights->produit->creer)) {
1035  print '</form>';
1036  }
1037  }
1038 } else {
1039  // List of variants
1040  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1041  include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1042  $prodstatic = new Product($db);
1043  $prodcomb = new ProductCombination($db);
1044  $comb2val = new ProductCombination2ValuePair($db);
1045  $productCombinations = $prodcomb->fetchAllByFkProductParent($object->id);
1046 
1047  print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
1048  print '<input type="hidden" name="token" value="'.newToken().'">';
1049  print '<input type="hidden" name="action" value="massaction">';
1050  print '<input type="hidden" name="id" value="'.$id.'">';
1051  print '<input type="hidden" name="backtopage" value="'.$backtopage.'">';
1052 
1053  // load variants
1054  $title = $langs->trans("ProductCombinations");
1055 
1056  print_barre_liste($title, 0, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, '', 0);
1057 
1058  print '<div class="div-table-responsive">';
1059  ?>
1060  <table class="liste">
1061  <tr class="liste_titre">
1062  <td class="liste_titre"><?php echo $langs->trans('Product') ?></td>
1063  <td class="liste_titre"><?php echo $langs->trans('Combination') ?></td>
1064  <td class="liste_titre center"><?php echo $langs->trans('OnSell') ?></td>
1065  <td class="liste_titre center"><?php echo $langs->trans('OnBuy') ?></td>
1066  <td class="liste_titre right"><?php echo $langs->trans('Stock') ?></td>
1067  <td class="liste_titre"></td>
1068  </tr>
1069  <?php
1070 
1071  if (count($productCombinations))
1072  {
1073  $stock_total = 0;
1074  foreach ($productCombinations as $currcomb)
1075  {
1076  $prodstatic->fetch($currcomb->fk_product_child);
1077  $prodstatic->load_stock();
1078  $stock_total += $prodstatic->stock_reel;
1079  ?>
1080  <tr class="oddeven">
1081  <td><?php echo $prodstatic->getNomUrl(1) ?></td>
1082  <td>
1083  <?php
1084 
1085  $productCombination2ValuePairs = $comb2val->fetchByFkCombination($currcomb->id);
1086  $iMax = count($productCombination2ValuePairs);
1087 
1088  for ($i = 0; $i < $iMax; $i++) {
1089  echo dol_htmlentities($productCombination2ValuePairs[$i]);
1090 
1091  if ($i !== ($iMax - 1)) {
1092  echo ', ';
1093  }
1094  } ?>
1095  </td>
1096  <td style="text-align: center;"><?php echo $prodstatic->getLibStatut(2, 0) ?></td>
1097  <td style="text-align: center;"><?php echo $prodstatic->getLibStatut(2, 1) ?></td>
1098  <td class="right"><?php echo $prodstatic->stock_reel ?></td>
1099  <td class="right">
1100  <a class="paddingleft paddingright" href="<?php echo dol_buildpath('/product/stock/product.php?id='.$currcomb->fk_product_child, 2) ?>"><?php echo img_edit() ?></a>
1101  </td>
1102  <?php
1103  ?>
1104  </tr>
1105  <?php
1106  }
1107 
1108  print '<tr class="liste_total">';
1109  print '<td colspan="4" class="left">'.$langs->trans("Total").'</td>';
1110  print '<td class="right">'.$stock_total.'</td>';
1111  print '<td></td>';
1112  print '</tr>';
1113  } else {
1114  print '<tr><td colspan="8"><span class="opacitymedium">'.$langs->trans("None").'</span></td></tr>';
1115  }
1116  ?>
1117  </table>
1118 
1119  <?php
1120  print '</div>';
1121 
1122  print '</form>';
1123 }
1124 
1125 // End of page
1126 llxFooter();
1127 $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.
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...
Class to manage canvas.
Class to manage products or services.
dol_htmlentities($string, $flags=null, $encoding= 'UTF-8', $double_encode=false)
Replace htmlentities functions.
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 ...
llxHeader()
Empty header.
Definition: wrapper.php:45
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)
restrictedArea($user, $features, $objectid=0, $tableandshare= '', $feature2= '', $dbt_keyfield= 'fk_soc', $dbt_select= 'rowid', $isdraft=0)
Check permissions of a user to show a page and an object.
Class ProductCombination Used to represent a product combination.
const STATUS_VALIDATED
Validated status.
print $_SERVER["PHP_SELF"]
Edit parameters.
static findAll($db, $fk_product_stock, $with_qty=0, $fk_product=0)
Return all batch detail records for a given product and warehouse.
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.
print
Draft customers invoices.
Definition: index.php:89
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...
if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if((!empty($conf->fournisseur->enabled)&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)||!empty($conf->supplier_invoice->enabled))&&$user->rights->fournisseur->facture->lire) if(!empty($conf->don->enabled)&&$user->rights->don->lire) if(!empty($conf->tax->enabled)&&$user->rights->tax->charges->lire) if(!empty($conf->facture->enabled)&&!empty($conf->commande->enabled)&&$user->rights->commande->lire &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) if(!empty($conf->facture->enabled)&&$user->rights->facture->lire) if((!empty($conf->fournisseur->enabled)&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)||!empty($conf->supplier_invoice->enabled))&&$user->rights->fournisseur->facture->lire) $resql
Social contributions to pay.
Definition: index.php:1232
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.
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.
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:59
img_delete($titlealt= 'default', $other= 'class="pictodelete"', $morecss= '')
Show delete logo.
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.