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