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