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