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