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