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