dolibarr 18.0.6
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(',', $conf->global->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 (empty($conf->global->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 (empty($conf->global->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->rights->produit->creer).'</td><td>';
737 print $form->editfieldval("StockLimit", 'seuil_stock_alerte', $object->seuil_stock_alerte, $object, $user->rights->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->rights->produit->creer);
742 print '</td><td>';
743 print $form->editfieldval("DesiredStock", 'desiredstock', $object->desiredstock, $object, $user->rights->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 .= (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE) ? '- '.$langs->trans("DeStockOnShipment").'<br>' : '');
750 $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER) ? '- '.$langs->trans("DeStockOnValidateOrder").'<br>' : '');
751 $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_BILL) ? '- '.$langs->trans("DeStockOnBill").'<br>' : '');
752 $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL) ? '- '.$langs->trans("ReStockOnBill").'<br>' : '');
753 $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER) ? '- '.$langs->trans("ReStockOnValidateOrder").'<br>' : '');
754 $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER) ? '- '.$langs->trans("ReStockOnDispatchOrder").'<br>' : '');
755 $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_RECEPTION) || !empty($conf->global->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 (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)) {
803 } elseif (!empty($conf->global->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 (!empty($user->rights->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->rights->stock->mouvement->creer) {
938 if (!$variants || !empty($conf->global->VARIANT_ALLOW_STOCK_MOVEMENT_ON_VARIANT_PARENT)) {
939 print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;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->rights->stock->mouvement->creer) {
948 if (!$variants || !empty($conf->global->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) {
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 (empty($conf->global->PRODUCT_DISABLE_SELLBY)) {
993 $colspan--;
994 print '<td class="center width100">'.$langs->trans("SellByDate").'</td>';
995 }
996 if (empty($conf->global->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 (empty($conf->global->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; $maxsellprice = null;
1072 print '<td class="right">';
1073 if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
1074 foreach ($object->multiprices as $priceforlevel) {
1075 if (is_numeric($priceforlevel)) {
1076 if (is_null($maxsellprice) || $priceforlevel > $maxsellprice) {
1077 $maxsellprice = $priceforlevel;
1078 }
1079 if (is_null($minsellprice) || $priceforlevel < $minsellprice) {
1080 $minsellprice = $priceforlevel;
1081 }
1082 }
1083 }
1084 print '<span class="valignmiddle">';
1085 if ($usercancreadprice) {
1086 if ($minsellprice != $maxsellprice) {
1087 print price(price2num($minsellprice, 'MU'), 1).' - '.price(price2num($maxsellprice, 'MU'), 1);
1088 } else {
1089 print price(price2num($minsellprice, 'MU'), 1);
1090 }
1091 }
1092 print '</span>';
1093 print $form->textwithpicto('', $langs->trans("Variable"));
1094 } elseif ($usercancreadprice) {
1095 print price(price2num($object->price, 'MU'), 1);
1096 }
1097 print '</td>';
1098
1099 // Value sell
1100 print '<td class="right amount nowraponall">';
1101 if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
1102 print '<span class="valignmiddle">';
1103 if ($usercancreadprice) {
1104 if ($minsellprice != $maxsellprice) {
1105 print price(price2num($minsellprice * $obj->reel, 'MT'), 1).' - '.price(price2num($maxsellprice * $obj->reel, 'MT'), 1);
1106 } else {
1107 print price(price2num($minsellprice * $obj->reel, 'MT'), 1);
1108 }
1109 }
1110 print '</span>';
1111 print $form->textwithpicto('', $langs->trans("Variable"));
1112 } else {
1113 if ($usercancreadprice) {
1114 print price(price2num($object->price * $obj->reel, 'MT'), 1);
1115 }
1116 }
1117 print '</td>';
1118 print '<td></td>';
1119 print '<td></td>';
1120 print '</tr>';
1121 $total += $obj->reel;
1122 if (price2num($object->pmp)) {
1123 $totalwithpmp += $obj->reel;
1124 }
1125 $totalvalue = $totalvalue + ($object->pmp * $obj->reel);
1126 $totalvaluesell = $totalvaluesell + ($object->price * $obj->reel);
1127 // Batch Detail
1128 if ((isModEnabled('productbatch')) && $object->hasbatch()) {
1129 $details = Productbatch::findAll($db, $obj->product_stock_id, 0, $object->id);
1130 if ($details < 0) {
1131 dol_print_error($db);
1132 }
1133 foreach ($details as $pdluo) {
1134 $product_lot_static->id = $pdluo->lotid;
1135 $product_lot_static->batch = $pdluo->batch;
1136 $product_lot_static->eatby = $pdluo->eatby;
1137 $product_lot_static->sellby = $pdluo->sellby;
1138
1139 if ($action == 'editline' && GETPOST('lineid', 'int') == $pdluo->id) { //Current line edit
1140 print "\n".'<tr>';
1141 print '<td colspan="9">';
1142 print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
1143 print '<input type="hidden" name="token" value="'.newToken().'">';
1144 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>';
1145 print '<td class="right" width="10%"><input type="text" name="batch_number" value="'.$pdluo->batch.'"></td>';
1146 if (empty($conf->global->PRODUCT_DISABLE_SELLBY)) {
1147 print '<td class="center" width="10%">';
1148 print $form->selectDate($pdluo->sellby, 'sellby', '', '', 1, '', 1, 0);
1149 print '</td>';
1150 }
1151 if (empty($conf->global->PRODUCT_DISABLE_EATBY)) {
1152 print '<td class="center" width="10%">';
1153 print $form->selectDate($pdluo->eatby, 'eatby', '', '', 1, '', 1, 0);
1154 print '</td>';
1155 }
1156 print '<td class="right" colspan="3">'.$pdluo->qty.($pdluo->qty < 0 ? ' '.img_warning() : '').'</td>';
1157 print '<td colspan="4"><input type="submit" class="button button-save" id="savelinebutton marginbottomonly" name="save" value="'.$langs->trans("Save").'">';
1158 print '<input type="submit" class="button button-cancel" id="cancellinebutton" name="Cancel" value="'.$langs->trans("Cancel").'"></td></tr>';
1159 print '</table>';
1160 print '</form>';
1161 print '</td>';
1162 print '<td></td>';
1163 print '<td></td>';
1164 print '</tr>';
1165 } else {
1166 print "\n".'<tr style="display:'.(empty($conf->global->STOCK_SHOW_ALL_BATCH_BY_DEFAULT) ? 'none' : 'visible').';" class="batch_warehouse'.$entrepotstatic->id.'"><td class="left">';
1167 print '</td>';
1168 print '<td class="right nowraponall">';
1169 print $product_lot_static->getNomUrl(1);
1170 print '</td>';
1171 $colspan = 3;
1172 if (empty($conf->global->PRODUCT_DISABLE_SELLBY)) {
1173 $colspan--;
1174 print '<td class="center">'.dol_print_date($pdluo->sellby, 'day').'</td>';
1175 }
1176 if (empty($conf->global->PRODUCT_DISABLE_EATBY)) {
1177 $colspan--;
1178 print '<td class="center">'.dol_print_date($pdluo->eatby, 'day').'</td>';
1179 }
1180 print '<td class="right" colspan="'.$colspan.'">'.$pdluo->qty.($pdluo->qty < 0 ? ' '.img_warning() : '').'</td>';
1181 print '<td colspan="4"></td>';
1182 print '<td class="center">';
1183 if ($entrepotstatic->status != $entrepotstatic::STATUS_CLOSED) {
1184 print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;id_entrepot='.$entrepotstatic->id.'&amp;action=transfert&amp;pdluoid='.$pdluo->id.'">';
1185 print img_picto($langs->trans("TransferStock"), 'add', 'class="hideonsmartphone paddingright" style="color: #a69944"');
1186 print $langs->trans("TransferStock");
1187 print '</a>';
1188 // Disabled, because edition of stock content must use the "Correct stock menu".
1189 // Do not use this, or data will be wrong (bad tracking of movement label, inventory code, ...
1190 //print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$id.'&action=editline&token='.newToken().'&lineid='.$pdluo->id.'#'.$pdluo->id.'">';
1191 //print img_edit().'</a>';
1192 }
1193 print '</td>';
1194 print '<td class="center">';
1195 if ($entrepotstatic->status != $entrepotstatic::STATUS_CLOSED) {
1196 print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;id_entrepot='.$entrepotstatic->id.'&amp;action=correction&amp;pdluoid='.$pdluo->id.'">';
1197 print img_picto($langs->trans("CorrectStock"), 'add', 'class="hideonsmartphone paddingright" style="color: #a69944"');
1198 print $langs->trans("CorrectStock");
1199 print '</a>';
1200 // Disabled, because edition of stock content must use the "Correct stock menu".
1201 // Do not use this, or data will be wrong (bad tracking of movement label, inventory code, ...
1202 //print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$id.'&action=editline&token='.newToken().'&lineid='.$pdluo->id.'#'.$pdluo->id.'">';
1203 //print img_edit().'</a>';
1204 }
1205 print '</td>';
1206 print '</tr>';
1207 }
1208 }
1209 }
1210 $i++;
1211 }
1212 } else {
1213 dol_print_error($db);
1214 }
1215
1216 // Total line
1217 print '<tr class="liste_total"><td class="right liste_total" colspan="4">'.$langs->trans("Total").':</td>';
1218 print '<td class="liste_total right">'.price2num($total, 'MS').'</td>';
1219 print '<td class="liste_total right">';
1220 if ($usercancreadprice) {
1221 print ($totalwithpmp ? price(price2num($totalvalue / $totalwithpmp, 'MU')) : '&nbsp;'); // This value may have rounding errors
1222 }
1223 print '</td>';
1224 // Value purchase
1225 print '<td class="liste_total right">';
1226 if ($usercancreadprice) {
1227 print $totalvalue ? price(price2num($totalvalue, 'MT'), 1) : '&nbsp;';
1228 }
1229 print '</td>';
1230 print '<td class="liste_total right">';
1231 if ($num) {
1232 if ($total) {
1233 print '<span class="valignmiddle">';
1234 if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
1235 print $form->textwithpicto('', $langs->trans("Variable"));
1236 } elseif ($usercancreadprice) {
1237 print price($totalvaluesell / $total, 1);
1238 }
1239 print '</span>';
1240 }
1241 }
1242 print '</td>';
1243 // Value to sell
1244 print '<td class="liste_total right amount">';
1245 if ($num) {
1246 print '<span class="valignmiddle">';
1247 if (empty($conf->global->PRODUIT_MULTIPRICES) && $usercancreadprice) {
1248 print price(price2num($totalvaluesell, 'MT'), 1);
1249 } else {
1250 print $form->textwithpicto('', $langs->trans("Variable"));
1251 }
1252 print '</span>';
1253 }
1254 print '</td>';
1255 print '<td></td>';
1256 print '<td></td>';
1257 print "</tr>";
1258
1259 print "</table>";
1260 print '</div>';
1261
1262 if (!empty($conf->global->STOCK_ALLOW_ADD_LIMIT_STOCK_BY_WAREHOUSE)) {
1263 print '<br><br>';
1264 print load_fiche_titre($langs->trans('AddNewProductStockWarehouse'));
1265
1266 if ($user->hasRight('produit', 'creer')) {
1267 print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
1268 print '<input type="hidden" name="token" value="'.newToken().'">';
1269 print '<input type="hidden" name="action" value="addlimitstockwarehouse">';
1270 print '<input type="hidden" name="id" value="'.$id.'">';
1271 }
1272 print '<table class="noborder centpercent">';
1273 if ($user->hasRight('produit', 'creer')) {
1274 print '<tr class="liste_titre"><td>'.$formproduct->selectWarehouses('', 'fk_entrepot').'</td>';
1275 print '<td class="right"><input name="seuil_stock_alerte" type="text" placeholder="'.$langs->trans("StockLimit").'" /></td>';
1276 print '<td class="right"><input name="desiredstock" type="text" placeholder="'.$langs->trans("DesiredStock").'" /></td>';
1277 print '<td class="right"><input type="submit" value="'.$langs->trans("Save").'" class="button button-save" /></td>';
1278 print '</tr>';
1279 } else {
1280 print '<tr class="liste_titre"><td>'.$langs->trans("Warehouse").'</td>';
1281 print '<td class="right">'.$langs->trans("StockLimit").'</td>';
1282 print '<td class="right">'.$langs->trans("DesiredStock").'</td>';
1283 print '</tr>';
1284 }
1285
1286 $pse = new ProductStockEntrepot($db);
1287 $lines = $pse->fetchAll($id);
1288
1289 if (!empty($lines)) {
1290 $var = false;
1291 foreach ($lines as $line) {
1292 $ent = new Entrepot($db);
1293 $ent->fetch($line['fk_entrepot']);
1294 print '<tr class="oddeven"><td>'.$ent->getNomUrl(3).'</td>';
1295 print '<td class="right">'.$line['seuil_stock_alerte'].'</td>';
1296 print '<td class="right">'.$line['desiredstock'].'</td>';
1297 if ($user->hasRight('produit', 'creer')) {
1298 print '<td class="right"><a href="'.$_SERVER['PHP_SELF'].'?id='.$id.'&fk_productstockwarehouse='.$line['id'].'&action=delete_productstockwarehouse&token='.newToken().'">'.img_delete().'</a></td>';
1299 }
1300 print '</tr>';
1301 }
1302 }
1303
1304 print "</table>";
1305
1306 if ($user->hasRight('produit', 'creer')) {
1307 print '</form>';
1308 }
1309 }
1310} else {
1311 // List of variants
1312 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
1313 include_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
1314 $prodstatic = new Product($db);
1315 $prodcomb = new ProductCombination($db);
1316 $comb2val = new ProductCombination2ValuePair($db);
1317 $productCombinations = $prodcomb->fetchAllByFkProductParent($object->id);
1318
1319 print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
1320 print '<input type="hidden" name="token" value="'.newToken().'">';
1321 print '<input type="hidden" name="action" value="massaction">';
1322 print '<input type="hidden" name="id" value="'.$id.'">';
1323 print '<input type="hidden" name="backtopage" value="'.$backtopage.'">';
1324
1325 // load variants
1326 $title = $langs->trans("ProductCombinations");
1327
1328 print_barre_liste($title, 0, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, '', 0);
1329
1330 print '<div class="div-table-responsive">';
1331 ?>
1332 <table class="liste">
1333 <tr class="liste_titre">
1334 <td class="liste_titre"><?php echo $langs->trans('Product') ?></td>
1335 <td class="liste_titre"><?php echo $langs->trans('Combination') ?></td>
1336 <td class="liste_titre center"><?php echo $langs->trans('OnSell') ?></td>
1337 <td class="liste_titre center"><?php echo $langs->trans('OnBuy') ?></td>
1338 <td class="liste_titre right"><?php echo $langs->trans('Stock') ?></td>
1339 <td class="liste_titre"></td>
1340 </tr>
1341 <?php
1342
1343 if (count($productCombinations)) {
1344 $stock_total = 0;
1345 foreach ($productCombinations as $currcomb) {
1346 $prodstatic->fetch($currcomb->fk_product_child);
1347 $prodstatic->load_stock();
1348 $stock_total += $prodstatic->stock_reel;
1349 ?>
1350 <tr class="oddeven">
1351 <td><?php echo $prodstatic->getNomUrl(1) ?></td>
1352 <td>
1353 <?php
1354
1355 $productCombination2ValuePairs = $comb2val->fetchByFkCombination($currcomb->id);
1356 $iMax = count($productCombination2ValuePairs);
1357
1358 for ($i = 0; $i < $iMax; $i++) {
1359 echo dol_htmlentities($productCombination2ValuePairs[$i]);
1360
1361 if ($i !== ($iMax - 1)) {
1362 echo ', ';
1363 }
1364 } ?>
1365 </td>
1366 <td style="text-align: center;"><?php echo $prodstatic->getLibStatut(2, 0) ?></td>
1367 <td style="text-align: center;"><?php echo $prodstatic->getLibStatut(2, 1) ?></td>
1368 <td class="right"><?php echo $prodstatic->stock_reel ?></td>
1369 <td class="right">
1370 <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>
1371 </td>
1372 <?php
1373 ?>
1374 </tr>
1375 <?php
1376 }
1377
1378 print '<tr class="liste_total">';
1379 print '<td colspan="4" class="left">'.$langs->trans("Total").'</td>';
1380 print '<td class="right">'.$stock_total.'</td>';
1381 print '<td></td>';
1382 print '</tr>';
1383 } else {
1384 print '<tr><td colspan="8"><span class="opacitymedium">'.$langs->trans("None").'</span></td></tr>';
1385 }
1386 ?>
1387 </table>
1388
1389 <?php
1390 print '</div>';
1391
1392 print '</form>';
1393}
1394
1395// End of page
1396llxFooter();
1397$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:56
llxFooter()
Empty footer.
Definition wrapper.php:70
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_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:120
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.