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