24 require
'../../main.inc.php';
25 include_once DOL_DOCUMENT_ROOT.
'/core/class/html.formcompany.class.php';
26 include_once DOL_DOCUMENT_ROOT.
'/product/class/html.formproduct.class.php';
27 include_once DOL_DOCUMENT_ROOT.
'/product/class/product.class.php';
28 include_once DOL_DOCUMENT_ROOT.
'/product/inventory/class/inventory.class.php';
29 include_once DOL_DOCUMENT_ROOT.
'/product/inventory/lib/inventory.lib.php';
30 include_once DOL_DOCUMENT_ROOT.
'/product/stock/class/mouvementstock.class.php';
31 include_once DOL_DOCUMENT_ROOT.
'/product/stock/class/productlot.class.php';
34 $langs->loadLangs(array(
"stocks",
"other",
"productbatch"));
39 $action =
GETPOST(
'action',
'aZ09');
40 $confirm =
GETPOST(
'confirm',
'alpha');
41 $cancel =
GETPOST(
'cancel',
'aZ09');
42 $contextpage =
GETPOST(
'contextpage',
'aZ') ?
GETPOST(
'contextpage',
'aZ') :
'inventorycard';
43 $backtopage =
GETPOST(
'backtopage',
'alpha');
45 $fk_warehouse =
GETPOST(
'fk_warehouse',
'int');
46 $fk_product =
GETPOST(
'fk_product',
'int');
47 $lineid =
GETPOST(
'lineid',
'int');
48 $batch =
GETPOST(
'batch',
'alphanohtml');
49 $totalExpectedValuation = 0;
50 $totalRealValuation = 0;
51 if (empty($conf->global->MAIN_USE_ADVANCED_PERMS)) {
54 $result =
restrictedArea($user,
'stock', $id,
'',
'inventory_advance');
60 $diroutputmassaction = $conf->stock->dir_output.
'/temp/massgeneration/'.$user->id;
61 $hookmanager->initHooks(array(
'inventorycard'));
64 $extrafields->fetch_name_optionals_label($object->table_element);
66 $search_array_options = $extrafields->getOptionalsFromPost($object->table_element,
'',
'search_');
69 $search_all =
GETPOST(
"search_all",
'alpha');
71 foreach ($object->fields as $key => $val) {
72 if (
GETPOST(
'search_'.$key,
'alpha')) {
73 $search[$key] =
GETPOST(
'search_'.$key,
'alpha');
77 if (empty($action) && empty($id) && empty($ref)) {
82 include DOL_DOCUMENT_ROOT.
'/core/actions_fetchobject.inc.php';
89 if (empty($conf->global->MAIN_USE_ADVANCED_PERMS)) {
90 $permissiontoadd = $user->rights->stock->creer;
91 $permissiontodelete = $user->rights->stock->supprimer;
93 $permissiontoadd = $user->rights->stock->inventory_advance->write;
94 $permissiontodelete = $user->rights->stock->inventory_advance->write;
109 $parameters = array();
110 $reshook = $hookmanager->executeHooks(
'doActions', $parameters, $object, $action);
115 if (empty($reshook)) {
118 if ($action ==
'cancel_record' && $permissiontoadd) {
119 $object->setCanceled($user);
123 if ($action ==
'update' && !empty($user->rights->stock->mouvement->creer) && $object->status == $object::STATUS_VALIDATED) {
125 $stockmovment->setOrigin($object->element, $object->id);
127 $cacheOfProducts = array();
131 $sql =
'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
132 $sql .=
' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.pmp_real';
133 $sql .=
' FROM '.MAIN_DB_PREFIX.
'inventorydet as id';
134 $sql .=
' WHERE id.fk_inventory = '.((int) $object->id);
136 $resql = $db->query($sql);
138 $num = $db->num_rows(
$resql);
140 $totalarray = array();
142 $line = $db->fetch_object(
$resql);
144 $qty_stock = $line->qty_stock;
145 $qty_view = $line->qty_view;
149 if (isset($cacheOfProducts[$line->fk_product])) {
150 $product_static = $cacheOfProducts[$line->fk_product];
152 $product_static =
new Product($db);
153 $result = $product_static->fetch($line->fk_product,
'',
'',
'', 1, 1, 1);
156 $option .=
',novirtual';
157 $product_static->load_stock($option);
159 $cacheOfProducts[$product_static->id] = $product_static;
163 $realqtynow = $product_static->stock_warehouse[$line->fk_warehouse]->real;
164 if ($conf->productbatch->enabled && $product_static->hasbatch()) {
165 $realqtynow = $product_static->stock_warehouse[$line->fk_warehouse]->detail_batch[$line->batch]->qty;
169 if (!is_null($qty_view)) {
170 $stock_movement_qty =
price2num($qty_view - $realqtynow,
'MS');
171 if ($stock_movement_qty != 0) {
172 if ($stock_movement_qty < 0) {
180 $inventorycode =
'INV-'.$object->ref;
182 if (!empty($line->pmp_real) && !empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) $price = $line->pmp_real;
184 $idstockmove = $stockmovment->_create($user, $line->fk_product, $line->fk_warehouse, $stock_movement_qty, $movement_type, $price, $langs->trans(
'LabelOfInventoryMovemement', $object->ref), $inventorycode, $datemovement,
'',
'', $line->batch);
185 if ($idstockmove < 0) {
192 $sqlupdate =
"UPDATE ".MAIN_DB_PREFIX.
"inventorydet";
193 $sqlupdate .=
" SET fk_movement = ".((int) $idstockmove);
194 if ($qty_stock != $realqtynow) {
195 $sqlupdate .=
", qty_stock = ".((float) $realqtynow);
197 $sqlupdate .=
" WHERE rowid = ".((int) $line->rowid);
198 $resqlupdate = $db->query($sqlupdate);
199 if (! $resqlupdate) {
206 if (!empty($line->pmp_real) && !empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
207 $sqlpmp =
'UPDATE '.MAIN_DB_PREFIX.
'product SET pmp = '.((
float) $line->pmp_real).
' WHERE rowid = '.((int) $line->fk_product);
208 $resqlpmp = $db->query($sqlpmp);
214 if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
215 $sqlpmp =
'UPDATE '.MAIN_DB_PREFIX.
'product_perentity SET pmp = '.((
float) $line->pmp_real).
' WHERE fk_product = '.((int) $line->fk_product).
' AND entity='.$conf->entity;
216 $resqlpmp = $db->query($sqlpmp);
229 $object->setRecorded($user);
244 if ($action ==
'updateinventorylines' && $permissiontoadd) {
245 $sql =
'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
246 $sql .=
' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated';
247 $sql .=
' FROM '.MAIN_DB_PREFIX.
'inventorydet as id';
248 $sql .=
' WHERE id.fk_inventory = '.((int) $object->id);
252 $resql = $db->query($sql);
254 $num = $db->num_rows(
$resql);
256 $totalarray = array();
260 $line = $db->fetch_object(
$resql);
261 $lineid = $line->rowid;
266 if (
GETPOST(
"id_".$lineid,
'alpha') !=
'') {
268 $result = $inventoryline->fetch($lineid);
269 if ($qtytoupdate < 0) {
271 setEventMessages($langs->trans(
"FieldCannotBeNegative", $langs->transnoentitiesnoconv(
"RealQty")),
null,
'errors');
274 $inventoryline->qty_stock =
price2num(
GETPOST(
'stock_qty_'.$lineid,
'alpha'),
'MS');
275 $inventoryline->qty_view = $qtytoupdate;
276 $inventoryline->pmp_real =
price2num(
GETPOST(
'realpmp_'.$lineid,
'alpha'),
'MS');
277 $inventoryline->pmp_expected =
price2num(
GETPOST(
'expectedpmp_'.$lineid,
'alpha'),
'MS');
278 $resultupdate = $inventoryline->update($user);
282 $result = $inventoryline->fetch($lineid);
284 $inventoryline->qty_view =
null;
285 $inventoryline->pmp_real =
price2num(
GETPOST(
'realpmp_'.$lineid,
'alpha'),
'MS');
286 $inventoryline->pmp_expected =
price2num(
GETPOST(
'expectedpmp_'.$lineid,
'alpha'),
'MS');
287 $resultupdate = $inventoryline->update($user);
291 if ($result < 0 || $resultupdate < 0) {
301 $sqlupdate =
"UPDATE ".MAIN_DB_PREFIX.
"inventory";
302 $sqlupdate .=
" SET fk_user_modif = ".((int) $user->id);
303 $sqlupdate .=
" WHERE rowid = ".((int) $object->id);
304 $resqlupdate = $db->query($sqlupdate);
305 if (! $resqlupdate) {
319 $backurlforlist = DOL_URL_ROOT.
'/product/inventory/list.php';
320 $backtopage = DOL_URL_ROOT.
'/product/inventory/inventory.php?id='.$object->id;
323 include DOL_DOCUMENT_ROOT.
'/core/actions_addupdatedelete.inc.php';
326 include DOL_DOCUMENT_ROOT.
'/core/actions_dellink.inc.php';
329 include DOL_DOCUMENT_ROOT.
'/core/actions_printing.inc.php';
337 if (
GETPOST(
'addline',
'alpha')) {
339 if ($fk_warehouse <= 0) {
341 setEventMessages($langs->trans(
"ErrorFieldRequired", $langs->transnoentitiesnoconv(
"Warehouse")),
null,
'errors');
343 if ($fk_product <= 0) {
345 setEventMessages($langs->trans(
"ErrorFieldRequired", $langs->transnoentitiesnoconv(
"Product")),
null,
'errors');
349 setEventMessages($langs->trans(
"FieldCannotBeNegative", $langs->transnoentitiesnoconv(
"RealQty")),
null,
'errors');
351 if (!$error && !empty($conf->productbatch->enabled)) {
352 $tmpproduct =
new Product($db);
353 $result = $tmpproduct->fetch($fk_product);
355 if (empty($error) && $tmpproduct->status_batch>0 && empty($batch)) {
357 $langs->load(
"errors");
358 setEventMessages($langs->trans(
"ErrorProductNeedBatchNumber", $tmpproduct->ref),
null,
'errors');
360 if (empty($error) && $tmpproduct->status_batch==2 && !empty($batch) && $qty>1) {
362 $langs->load(
"errors");
363 setEventMessages($langs->trans(
"TooManyQtyForSerialNumber", $tmpproduct->ref, $batch),
null,
'errors');
365 if (empty($error) && empty($tmpproduct->status_batch) && !empty($batch)) {
367 $langs->load(
"errors");
368 setEventMessages($langs->trans(
"ErrorProductDoesNotNeedBatchNumber", $tmpproduct->ref),
null,
'errors');
373 $tmp->fk_inventory = $object->id;
374 $tmp->fk_warehouse = $fk_warehouse;
375 $tmp->fk_product = $fk_product;
376 $tmp->batch = $batch;
378 $tmp->qty_view = $qty;
380 $result = $tmp->create($user);
382 if ($db->lasterrno() ==
'DB_ERROR_RECORD_ALREADY_EXISTS') {
383 $langs->load(
"errors");
384 setEventMessages($langs->trans(
"ErrorRecordAlreadyExists"),
null,
'errors');
390 $_POST[
'batch'] =
'';
391 $_POST[
'qtytoadd'] =
'';
413 print
'<script type="text/javascript">
414 function disablebuttonmakemovementandclose() {
415 console.log("Disable button idbuttonmakemovementandclose until we save");
416 jQuery("#idbuttonmakemovementandclose").attr(\'disabled\',\'disabled\');
417 jQuery("#idbuttonmakemovementandclose").attr(\'onclick\', \'return false;\');
418 jQuery("#idbuttonmakemovementandclose").attr(\'title\',\''.dol_escape_js($langs->trans(
"SaveQtyFirst")).
'\');
419 jQuery(
"#idbuttonmakemovementandclose").attr(\
'class\',\'butActionRefused classfortooltip\');
422 jQuery(document).ready(function() {
423 jQuery(".realqty").keyup(function() {
424 console.log("keyup on realqty");
425 disablebuttonmakemovementandclose();
427 jQuery(".realqty").change(function() {
428 console.log("change on realqty");
429 disablebuttonmakemovementandclose();
436 if ($object->id > 0) {
437 $res = $object->fetch_optionals();
439 $head = inventoryPrepareHead($object);
440 print dol_get_fiche_head($head,
'inventory', $langs->trans(
"Inventory"), -1,
'stock');
445 if ($action ==
'delete') {
446 $formconfirm = $form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id, $langs->trans(
'DeleteInventory'), $langs->trans(
'ConfirmDeleteOrder'),
'confirm_delete',
'', 0, 1);
449 if ($action ==
'deleteline') {
450 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id.
'&lineid='.$lineid, $langs->trans(
'DeleteLine'), $langs->trans(
'ConfirmDeleteLine'),
'confirm_deleteline',
'', 0, 1);
454 if ($action ==
'clone') {
456 $formquestion = array();
457 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id, $langs->trans(
'ToClone'), $langs->trans(
'ConfirmCloneMyObject', $object->ref),
'confirm_clone', $formquestion,
'yes', 1);
461 if ($action ==
'record') {
462 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id, $langs->trans(
'Close'), $langs->trans(
'ConfirmFinish'),
'update',
'', 0, 1);
467 if ($action ==
'confirm_cancel') {
468 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id, $langs->trans(
'Cancel'), $langs->trans(
'ConfirmCancel'),
'cancel_record',
'', 0, 1);
473 $parameters = array(
'formConfirm' =>
$formconfirm,
'lineid' => $lineid);
474 $reshook = $hookmanager->executeHooks(
'formConfirm', $parameters, $object, $action);
475 if (empty($reshook)) {
477 } elseif ($reshook > 0) {
487 $linkback =
'<a href="'.DOL_URL_ROOT.
'/product/inventory/list.php">'.$langs->trans(
"BackToList").
'</a>';
489 $morehtmlref =
'<div class="refidno">';
529 $morehtmlref .=
'</div>';
532 dol_banner_tab($object,
'ref', $linkback, 1,
'ref',
'ref', $morehtmlref);
535 print
'<div class="fichecenter">';
536 print
'<div class="fichehalfleft">';
537 print
'<div class="underbanner clearboth"></div>';
538 print
'<table class="border centpercent tableforfield">'.
"\n";
541 include DOL_DOCUMENT_ROOT.
'/core/tpl/commonfields_view.tpl.php';
544 include DOL_DOCUMENT_ROOT.
'/core/tpl/extrafields_view.tpl.php';
552 print
'<div class="clearboth"></div>';
557 print
'<form id="formrecord" name="formrecord" method="POST" action="'.$_SERVER[
"PHP_SELF"].
'">';
558 print
'<input type="hidden" name="token" value="'.newToken().
'">';
559 print
'<input type="hidden" name="action" value="updateinventorylines">';
560 print
'<input type="hidden" name="id" value="'.$object->id.
'">';
562 print
'<input type="hidden" name="backtopage" value="'.$backtopage.
'">';
567 if ($action !=
'record') {
568 print
'<div class="tabsAction">'.
"\n";
569 $parameters = array();
570 $reshook = $hookmanager->executeHooks(
'addMoreActionsButtons', $parameters, $object, $action);
575 if (empty($reshook)) {
576 if ($object->status == Inventory::STATUS_DRAFT) {
577 if ($permissiontoadd) {
578 print
'<a class="butAction" href="'.$_SERVER[
"PHP_SELF"].
'?id='.$object->id.
'&action=confirm_validate&confirm=yes&token='.
newToken().
'">'.$langs->trans(
"Validate").
' ('.$langs->trans(
"Start").
')</a>'.
"\n";
580 print
'<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'">'.$langs->trans(
'Validate').
' ('.$langs->trans(
"Start").
')</a>'.
"\n";
585 if ($object->status == $object::STATUS_VALIDATED) {
586 if ($permissiontoadd) {
587 print
'<a class="butAction classfortooltip" id="idbuttonmakemovementandclose" href="'.$_SERVER[
"PHP_SELF"].
'?id='.$object->id.
'&action=record&token='.
newToken().
'" title="'.
dol_escape_htmltag($langs->trans(
"MakeMovementsAndClose")).
'">'.$langs->trans(
"MakeMovementsAndClose").
'</a>'.
"\n";
589 print
'<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'">'.$langs->trans(
'MakeMovementsAndClose').
'</a>'.
"\n";
592 if ($permissiontoadd) {
593 print
'<a class="butActionDelete" href="'.$_SERVER[
"PHP_SELF"].
'?id='.$object->id.
'&action=confirm_cancel&token='.
newToken().
'">'.$langs->trans(
"Cancel").
'</a>'.
"\n";
599 if ($object->status != Inventory::STATUS_DRAFT && $object->status != Inventory::STATUS_VALIDATED) {
606 if ($object->status == Inventory::STATUS_VALIDATED) {
608 if (!empty($conf->use_javascript_ajax)) {
609 if ($permissiontoadd) {
611 if (!empty($conf->barcode->enabled) || !empty($conf->productbatch->enabled)) {
612 print
'<a href="'.$_SERVER[
"PHP_SELF"].
'?id='.$object->id.
'&action=updatebyscaning&token='.
currentToken().
'" class="marginrightonly paddingright marginleftonly paddingleft">'.
img_picto(
'',
'barcode',
'class="paddingrightonly"').$langs->trans(
"UpdateByScaning").
'</a>';
616 print
'<a id="fillwithexpected" class="marginrightonly paddingright marginleftonly paddingleft" href="#">'.img_picto(
'',
'autofill',
'class="paddingrightonly"').$langs->trans(
'AutofillWithExpected').
'</a>';
618 print
'$( document ).ready(function() {';
619 print
' $("#fillwithexpected").on("click",function fillWithExpected(){
620 $(".expectedqty").each(function(){
621 var object = $(this)[0];
622 var objecttofill = $("#"+object.id+"_input")[0];
623 objecttofill.value = object.innerText;
624 jQuery(".realqty").trigger("change");
626 console.log("Values filled (after click on fillwithexpected)");
627 disablebuttonmakemovementandclose();
634 print
'<a href="#" id="clearqty" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto(
'',
'eraser',
'class="paddingrightonly"').$langs->trans(
"ClearQtys").
'</a>';
636 print
'<a class="classfortooltip marginrightonly paddingright marginleftonly paddingleft" href="#" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'">'.$langs->trans(
"Save").
'</a>'.
"\n";
646 if ($action ==
'updatebyscaning') {
647 if ($permissiontoadd) {
652 var duplicatedbatchcode = [];
658 function barcodescannerjs(){
659 console.log("We catch inputs in scanner box");
660 jQuery("#scantoolmessage").text();
662 var selectaddorreplace = $("select[name=selectaddorreplace]").val();
663 var barcodemode = $("input[name=barcodemode]:checked").val();
664 var barcodeproductqty = $("input[name=barcodeproductqty]").val();
665 var textarea = $("textarea[name=barcodelist]").val();
666 var textarray = textarea.split(/[\s,;]+/);
668 duplicatedbatchcode = [];
674 textarray = textarray.filter(function(value){
677 if(textarray.some((element) => element != "")){
678 $(".expectedqty").each(function(){
680 console.log("Analyze the line "+id+" in inventory, barcodemode="+barcodemode);
681 warehouse = $("#"+id+"_warehouse").attr(\'data-ref\');
682 //console.log(warehouse);
683 productbarcode = $("#"+id+"_product").attr(\'data-barcode\');
684 //console.log(productbarcode);
685 productbatchcode = $("#"+id+"_batch").attr(\'data-batch\');
686 //console.log(productbatchcode);
688 if (barcodemode != "barcodeforproduct") {
689 tabproduct.forEach(product=>{
690 console.log("product.Batch="+product.Batch+" productbatchcode="+productbatchcode);
691 if(product.Batch != "" && product.Batch == productbatchcode){
692 console.log("duplicate batch code found for batch code "+productbatchcode);
693 duplicatedbatchcode.push(productbatchcode);
697 productinput = $("#"+id+"_input").val();
698 if(productinput == ""){
701 tabproduct.push({\'Id\':id,\'Warehouse\':warehouse,\'Barcode\':productbarcode,\'Batch\':productbatchcode,\'Qty\':productinput,\'fetched\':false});
704 console.log("Loop on each record entered in the textarea");
705 textarray.forEach(function(element,index){
706 console.log("Process record element="+element+" id="+id);
707 var verify_batch = false;
708 var verify_barcode = false;
710 case "barcodeforautodetect":
711 verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode",true);
712 verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial",true);
714 case "barcodeforproduct":
715 verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode");
717 case "barcodeforlotserial":
718 verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial");
721 alert(\''.dol_escape_js($langs->trans(
"ErrorWrongBarcodemode")).
' "\'+barcodemode+\'"\');
722 throw \''.
dol_escape_js($langs->trans(
'ErrorWrongBarcodemode')).
' "\'+barcodemode+\'"\';
725 if (verify_batch == false && verify_barcode == false) { /* If the 2 flags are false, not found error */
726 errortab2.push(element);
727 } else if (verify_batch == true && verify_barcode == true) { /* If the 2 flags are true, error: we don t know which one to take */
728 errortab3.push(element);
729 } else if (verify_batch == true) {
730 console.log("element="+element);
731 console.log(duplicatedbatchcode);
732 if (duplicatedbatchcode.includes(element)) {
733 errortab1.push(element);
738 if (Object.keys(errortab1).length < 1 && Object.keys(errortab2).length < 1 && Object.keys(errortab3).length < 1) {
739 tabproduct.forEach(product => {
741 console.log("We change #"+product.Id+"_input to match input in scanner box");
742 if(product.hasOwnProperty("reelqty")){
743 $.ajax({ url: \''.DOL_URL_ROOT.
'/product/inventory/ajax/searchfrombarcode.php\',
744 data: { "token":"'.
newToken().
'", "action":"addnewlineproduct", "fk_entrepot":product.Warehouse, "batch":product.Batch, "fk_inventory":'.
dol_escape_js($object->id).
', "fk_product":product.fk_product, "reelqty":product.reelqty},
747 success: function(response) {
748 response = JSON.parse(response);
749 if(response.status == "success"){
750 console.log(response.message);
751 $("<input type=\'text\' value=\'"+product.Qty+"\' />")
752 .attr("id", "id_"+response.id_line+"_input")
753 .attr("name", "id_"+response.id_line)
754 .appendTo("#formrecord");
756 console.error(response.message);
759 error : function(output) {
760 console.error("Error on line creation function");
764 $("#"+product.Id+"_input").val(product.Qty);
768 jQuery("#scantoolmessage").text("'.
dol_escape_js($langs->transnoentities(
"QtyWasAddedToTheScannedBarcode")).
'\n");
769 /* document.forms["formrecord"].submit(); */
771 let stringerror = "";
772 if (Object.keys(errortab1).length > 0) {
773 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorSameBatchNumber')).
': ";
774 errortab1.forEach(element => {
775 stringerror += (element + ", ")
777 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
779 if (Object.keys(errortab2).length > 0) {
780 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorCantFindCodeInInventory')).
': ";
781 errortab2.forEach(element => {
782 stringerror += (element + ", ")
784 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
786 if (Object.keys(errortab3).length > 0) {
787 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorCodeScannedIsBothProductAndSerial')).
': ";
788 errortab3.forEach(element => {
789 stringerror += (element + ", ")
791 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
793 if (Object.keys(errortab4).length > 0) {
794 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorBarcodeNotFoundForProductWarehouse')).
': ";
795 errortab4.forEach(element => {
796 stringerror += (element + ", ")
798 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
801 jQuery("#scantoolmessage").html(\''.
dol_escape_js($langs->transnoentities(
"ErrorOnElementsInventory")).
'\' + stringerror);
809 function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,mode,autodetect=
false){
810 BarcodeIsInProduct=0;
813 tabproduct.forEach(product => {
814 $.ajax({ url: \
''.DOL_URL_ROOT.
'/product/inventory/ajax/searchfrombarcode.php\',
815 data: { "token":"'.
newToken().
'", "action":"existbarcode", '.(!empty($object->fk_warehouse)?
'"fk_entrepot":'.$object->fk_warehouse.
', ':
'').(!empty($object->fk_product)?
'"fk_product":'.$object->fk_product.
', ':
'').
'"barcode":element, "product":product, "mode":mode},
818 success: function(response) {
819 response = JSON.parse(response);
820 if (response.status == "success"){
821 console.log(response.message);
823 newproductrow = response.object;
826 if (mode!="lotserial" && autodetect==false && !errortab4.includes(element)){
827 errortab4.push(element);
828 console.error(response.message);
832 error : function(output) {
833 console.error("Error on barcodeserialforproduct function");
836 console.log("Product "+(index+=1)+": "+element);
837 if(mode == "barcode"){
838 testonproduct = product.Barcode
839 }else if (mode == "lotserial"){
840 testonproduct = product.Batch
842 if(testonproduct == element){
843 if(selectaddorreplace == "add"){
844 productqty = parseInt(product.Qty,10);
845 product.Qty = productqty + parseInt(barcodeproductqty,10);
846 }else if(selectaddorreplace == "replace"){
847 if(product.fetched == false){
848 product.Qty = barcodeproductqty
851 productqty = parseInt(product.Qty,10);
852 product.Qty = productqty + parseInt(barcodeproductqty,10);
855 BarcodeIsInProduct+=1;
858 if(BarcodeIsInProduct==0 && newproductrow!=0){
859 tabproduct.push({\'Id\':tabproduct.length-1,\'Warehouse\':newproductrow.fk_warehouse,\'Barcode\':mode=="barcode"?element:null,\'Batch\':mode=="lotserial"?element:null,\'Qty\':barcodeproductqty,\'fetched\':true,\'reelqty\':newproductrow.reelqty,\'fk_product\':newproductrow.fk_product,\'mode\':mode});
862 if(BarcodeIsInProduct > 0){
870 include DOL_DOCUMENT_ROOT.
'/core/class/html.formother.class.php';
872 print $formother->getHTMLScannerForm(
"barcodescannerjs",
'all');
877 print
'jQuery(document).ready(function() {
878 $("#clearqty").on("click", function() {
879 console.log("Clear all values");
880 disablebuttonmakemovementandclose();
881 jQuery(".realqty").val("");
882 jQuery(".realqty").trigger("change");
883 return false; /* disable submit */
885 $(".undochangesqty").on("click", function undochangesqty() {
886 console.log("Clear value of inventory line");
888 id = id.split("_")[1];
889 tmpvalue = $("#id_"+id+"_input_tmp").val()
890 $("#id_"+id+"_input")[0].value = tmpvalue;
891 disablebuttonmakemovementandclose();
892 return false; /* disable submit */
897 print
'<div class="fichecenter">';
899 print
'<div class="clearboth"></div>';
903 print
'<div class="div-table-responsive-no-min">';
904 print
'<table id="tablelines" class="noborder noshadow centpercent">';
906 print
'<tr class="liste_titre">';
907 print
'<td>'.$langs->trans(
"Warehouse").
'</td>';
908 print
'<td>'.$langs->trans(
"Product").
'</td>';
909 if (!empty($conf->productbatch->enabled)) {
911 print $langs->trans(
"Batch");
914 print
'<td class="right">'.$langs->trans(
"ExpectedQty").
'</td>';
915 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
916 print
'<td class="right">'.$langs->trans(
'PMPExpected').
'</td>';
917 print
'<td class="right">'.$langs->trans(
'ExpectedValuation').
'</td>';
918 print
'<td class="right">'.$form->textwithpicto($langs->trans(
"RealQty"), $langs->trans(
"InventoryRealQtyHelp")).
'</td>';
919 print
'<td class="right">'.$langs->trans(
'PMPReal').
'</td>';
920 print
'<td class="right">'.$langs->trans(
'RealValuation').
'</td>';
922 print
'<td class="right">';
923 print
$form->textwithpicto($langs->trans(
"RealQty"), $langs->trans(
"InventoryRealQtyHelp"));
926 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
928 print
'<td class="center">';
932 print
'<td class="right">';
939 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
942 print $formproduct->selectWarehouses((
GETPOSTISSET(
'fk_warehouse') ?
GETPOST(
'fk_warehouse',
'int') : $object->fk_warehouse),
'fk_warehouse',
'warehouseopen', 1, 0, 0,
'', 0, 0, array(),
'maxwidth300');
945 print
$form->select_produits((
GETPOSTISSET(
'fk_product') ?
GETPOST(
'fk_product',
'int') : $object->fk_product),
'fk_product',
'', 0, 0, -1, 2,
'', 0,
null, 0,
'1', 0,
'maxwidth300');
947 if (!empty($conf->productbatch->enabled)) {
949 print
'<input type="text" name="batch" class="maxwidth100" value="'.(GETPOSTISSET(
'batch') ?
GETPOST(
'batch') :
'').
'">';
952 print
'<td class="right"></td>';
953 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
954 print
'<td class="right">';
956 print
'<td class="right">';
958 print
'<td class="right">';
959 print
'<input type="text" name="qtytoadd" class="maxwidth75" value="">';
961 print
'<td class="right">';
963 print
'<td class="right">';
966 print
'<td class="right">';
967 print
'<input type="text" name="qtytoadd" class="maxwidth75" value="">';
971 print
'<td class="center">';
972 print
'<input type="submit" class="button paddingright" name="addline" value="'.$langs->trans(
"Add").
'">';
978 $sql =
'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
979 $sql .=
' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.fk_movement, id.pmp_real, id.pmp_expected';
980 $sql .=
' FROM '.MAIN_DB_PREFIX.
'inventorydet as id';
981 $sql .=
' WHERE id.fk_inventory = '.((int) $object->id);
982 $sql .=
' ORDER BY id.rowid';
984 $cacheOfProducts = array();
985 $cacheOfWarehouses = array();
988 $resql = $db->query($sql);
990 $num = $db->num_rows(
$resql);
994 $totalarray = array();
996 $obj = $db->fetch_object(
$resql);
998 if (isset($cacheOfWarehouses[$obj->fk_warehouse])) {
999 $warehouse_static = $cacheOfWarehouses[$obj->fk_warehouse];
1001 $warehouse_static =
new Entrepot($db);
1002 $warehouse_static->fetch($obj->fk_warehouse);
1004 $cacheOfWarehouses[$warehouse_static->id] = $warehouse_static;
1009 if (isset($cacheOfProducts[$obj->fk_product])) {
1010 $product_static = $cacheOfProducts[$obj->fk_product];
1012 $product_static =
new Product($db);
1013 $result = $product_static->fetch($obj->fk_product,
'',
'',
'', 1, 1, 1);
1016 $option .=
',novirtual';
1017 $product_static->load_stock($option);
1019 $cacheOfProducts[$product_static->id] = $product_static;
1022 print
'<tr class="oddeven">';
1023 print
'<td id="id_'.$obj->rowid.
'_warehouse" data-ref="'.
dol_escape_htmltag($warehouse_static->ref).
'">';
1024 print $warehouse_static->getNomUrl(1);
1027 print $product_static->getNomUrl(1).
' - '.$product_static->label;
1030 if (!empty($conf->productbatch->enabled)) {
1031 print
'<td id="id_'.$obj->rowid.
'_batch" data-batch="'.
dol_escape_htmltag($obj->batch).
'">';
1033 $res = $batch_static->fetch(0, $product_static->id, $obj->batch);
1035 print $batch_static->getNomUrl(1);
1043 print
'<td class="right expectedqty" id="id_'.$obj->rowid.
'" title="Stock viewed at last update: '.$obj->qty_stock.
'">';
1044 $valuetoshow = $obj->qty_stock;
1046 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
1047 if (!empty($conf->productbatch->enabled) && $product_static->hasbatch()) {
1048 $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->detail_batch[$obj->batch]->qty;
1050 $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->real;
1054 print
'<input type="hidden" name="stock_qty_'.$obj->rowid.
'" value="'.$valuetoshow.
'">';
1058 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
1062 if ($qty_view !=
'') {
1066 if (! empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1068 if (! empty($obj->pmp_expected)) $pmp_expected = $obj->pmp_expected;
1069 else $pmp_expected = $product_static->pmp;
1070 $pmp_valuation = $pmp_expected * $valuetoshow;
1071 print
'<td class="right">';
1072 print
price($pmp_expected);
1073 print
'<input type="hidden" name="expectedpmp_'.$obj->rowid.
'" value="'.$pmp_expected.
'"/>';
1075 print
'<td class="right">';
1076 print
price($pmp_valuation);
1079 print
'<td class="right">';
1080 print
'<a id="undochangesqty_'.$obj->rowid.
'" href="#" class="undochangesqty reposition marginrightonly" title="'.
dol_escape_htmltag($langs->trans(
"Clear")).
'">';
1081 print
img_picto(
'',
'eraser',
'class="opacitymedium"');
1083 print
'<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.
'" id="id_'.$obj->rowid.
'_input" value="'.$qty_view.
'">';
1087 print
'<td class="right">';
1090 if (! empty($obj->pmp_real)) $pmp_real = $obj->pmp_real;
1091 else $pmp_real = $product_static->pmp;
1092 $pmp_valuation_real = $pmp_real * $qty_view;
1093 print
'<input type="text" class="maxwidth75 right realpmp'.$obj->fk_product.
'" name="realpmp_'.$obj->rowid.
'" id="id_'.$obj->rowid.
'_input_pmp" value="'.
price2num($pmp_real).
'">';
1095 print
'<td class="right">';
1096 print
'<input type="text" class="maxwidth75 right realvaluation'.$obj->fk_product.
'" name="realvaluation_'.$obj->rowid.
'" id="id_'.$obj->rowid.
'_input_real_valuation" value="'.$pmp_valuation_real.
'">';
1099 $totalExpectedValuation += $pmp_valuation;
1100 $totalRealValuation += $pmp_valuation_real;
1102 print
'<td class="right">';
1103 print
'<a id="undochangesqty_'.$obj->rowid.
'" href="#" class="undochangesqty reposition marginrightonly" title="'.
dol_escape_htmltag($langs->trans(
"Clear")).
'">';
1104 print
img_picto(
'',
'eraser',
'class="opacitymedium"');
1106 print
'<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.
'" id="id_'.$obj->rowid.
'_input" value="'.$qty_view.
'">';
1111 print
'<td class="right">';
1112 print
'<a class="reposition" href="'.DOL_URL_ROOT.
'/product/inventory/inventory.php?id='.$object->id.
'&lineid='.$obj->rowid.
'&action=deleteline&token='.
newToken().
'">'.
img_delete().
'</a>';
1113 $qty_tmp =
price2num(
GETPOST(
"id_".$obj->rowid.
"_input_tmp",
'MS')) >= 0 ?
GETPOST(
"id_".$obj->rowid.
"_input_tmp") : $qty_view;
1114 print
'<input type="hidden" class="maxwidth50 right realqty" name="id_'.$obj->rowid.
'_input_tmp" id="id_'.$obj->rowid.
'_input_tmp" value="'.$qty_tmp.
'">';
1117 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1119 if (! empty($obj->pmp_expected)) $pmp_expected = $obj->pmp_expected;
1120 else $pmp_expected = $product_static->pmp;
1121 $pmp_valuation = $pmp_expected * $valuetoshow;
1122 print
'<td class="right">';
1123 print
price($pmp_expected);
1125 print
'<td class="right">';
1126 print
price($pmp_valuation);
1129 print
'<td class="right nowraponall">';
1130 print $obj->qty_view;
1134 print
'<td class="right">';
1135 if (! empty($obj->pmp_real)) $pmp_real = $obj->pmp_real;
1136 else $pmp_real = $product_static->pmp;
1137 $pmp_valuation_real = $pmp_real * $obj->qty_view;
1138 print
price($pmp_real);
1140 print
'<td class="right">';
1141 print
price($pmp_valuation_real);
1143 print
'<td class="nowraponall right">';
1145 $totalExpectedValuation += $pmp_valuation;
1146 $totalRealValuation += $pmp_valuation_real;
1148 print
'<td class="right nowraponall">';
1149 print $obj->qty_view;
1152 if ($obj->fk_movement > 0) {
1154 $stockmovment->fetch($obj->fk_movement);
1155 print $stockmovment->getNomUrl(1,
'movements');
1166 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1167 print
'<tr class="liste_total">';
1168 print
'<td colspan="4">'.$langs->trans(
"Total").
'</td>';
1169 print
'<td class="right" colspan="2">'.price($totalExpectedValuation).
'</td>';
1170 print
'<td class="right" id="totalRealValuation" colspan="3">'.price($totalRealValuation).
'</td>';
1178 if ($object->status == $object::STATUS_VALIDATED) {
1179 print
'<center><input id="submitrecord" type="submit" class="button button-save" name="save" value="'.$langs->trans(
"Save").
'"></center>';
1187 if ($object->status != $object::STATUS_VALIDATED || !$hasinput) {
1188 print
'<script type="text/javascript">
1189 jQuery(document).ready(function() {
1190 console.log("Call disablebuttonmakemovementandclose because status = '.((int) $object->status).
' or $hasinput = '.((int) $hasinput).
'");
1191 disablebuttonmakemovementandclose();
1198 if (! empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1200 <script
type=
"text/javascript">
1201 $(
'.realqty').on(
'change',
function () {
1202 let realqty = $(
this).closest(
'tr').find(
'.realqty').val();
1203 let inputPmp = $(
this).closest(
'tr').find(
'input[class*=realpmp]');
1204 let realpmp = $(inputPmp).val();
1205 if (!isNaN(realqty) && !isNaN(realpmp)) {
1206 let realval = realqty * realpmp;
1207 $(
this).closest(
'tr').find(
'input[name^=realvaluation]').val(realval.toFixed(2));
1209 updateTotalValuation();
1212 $(
'input[class*=realpmp]').on(
'change',
function () {
1213 let inputQtyReal = $(
this).closest(
'tr').find(
'.realqty');
1214 let realqty = $(inputQtyReal).val();
1215 let inputPmp = $(
this).closest(
'tr').find(
'input[class*=realpmp]');
1216 console.log(inputPmp);
1217 let realPmpClassname = $(inputPmp).attr(
'class').match(/[\w-]*realpmp[\w-]*/g)[0];
1218 let realpmp = $(inputPmp).val();
1219 if (!isNaN(realpmp)) {
1220 $(
'.'+realPmpClassname).val(realpmp);
1222 if (!isNaN(realqty)) {
1223 let realval = realqty * realpmp;
1224 $(
this).closest(
'tr').find(
'input[name^=realvaluation]').val(realval.toFixed(2));
1226 $(
'.realqty').trigger(
'change');
1227 updateTotalValuation();
1231 $(
'input[name^=realvaluation]').on(
'change',
function () {
1232 let inputQtyReal = $(
this).closest(
'tr').find(
'.realqty');
1233 let realqty = $(inputQtyReal).val();
1234 let inputPmp = $(
this).closest(
'tr').find(
'input[class*=realpmp]');
1235 let inputRealValuation = $(
this).closest(
'tr').find(
'input[name^=realvaluation]');
1236 let realPmpClassname = $(inputPmp).attr(
'class').match(/[\w-]*realpmp[\w-]*/g)[0];
1237 let realvaluation = $(inputRealValuation).val();
1238 if (!isNaN(realvaluation) && !isNaN(realqty) && realvaluation !==
'' && realqty !==
'' && realqty !== 0) {
1239 let realpmp = realvaluation / realqty
1240 $(
'.'+realPmpClassname).val(realpmp);
1241 $(
'.realqty').trigger(
'change');
1242 updateTotalValuation();
1246 function updateTotalValuation() {
1248 $(
'input[name^=realvaluation]').each(
function( index ) {
1249 let val = $(
this).val();
1250 if(!isNaN(val)) total += parseFloat($(
this).val());
1252 let currencyFractionDigits =
new Intl.NumberFormat(
'fr-FR', {
1255 }).resolvedOptions().maximumFractionDigits;
1256 $(
'#totalRealValuation').html(total.toLocaleString(
'fr-FR', {
1257 maximumFractionDigits: currencyFractionDigits