25require
'../../main.inc.php';
26include_once DOL_DOCUMENT_ROOT.
'/core/class/html.formcompany.class.php';
27include_once DOL_DOCUMENT_ROOT.
'/product/class/html.formproduct.class.php';
28include_once DOL_DOCUMENT_ROOT.
'/product/class/product.class.php';
29include_once DOL_DOCUMENT_ROOT.
'/product/inventory/class/inventory.class.php';
30include_once DOL_DOCUMENT_ROOT.
'/product/inventory/lib/inventory.lib.php';
31include_once DOL_DOCUMENT_ROOT.
'/product/stock/class/mouvementstock.class.php';
32include_once DOL_DOCUMENT_ROOT.
'/product/stock/class/productlot.class.php';
35$langs->loadLangs(array(
"stocks",
"other",
"productbatch"));
40$action =
GETPOST(
'action',
'aZ09');
41$confirm =
GETPOST(
'confirm',
'alpha');
42$cancel =
GETPOST(
'cancel',
'aZ09');
43$contextpage =
GETPOST(
'contextpage',
'aZ') ?
GETPOST(
'contextpage',
'aZ') :
'inventorycard';
44$backtopage =
GETPOST(
'backtopage',
'alpha');
45$listoffset =
GETPOST(
'listoffset',
'alpha');
48if (empty($page) || $page == -1) {
51$offset = $limit * $page;
58$batch =
GETPOST(
'batch',
'alphanohtml');
59$totalExpectedValuation = 0;
60$totalRealValuation = 0;
64 $result =
restrictedArea($user,
'stock', $id,
'',
'inventory_advance');
70$diroutputmassaction = $conf->stock->dir_output.
'/temp/massgeneration/'.$user->id;
71$hookmanager->initHooks(array(
'inventorycard'));
74$extrafields->fetch_name_optionals_label(
$object->table_element);
76$search_array_options = $extrafields->getOptionalsFromPost(
$object->table_element,
'',
'search_');
79$search_all =
GETPOST(
"search_all",
'alpha');
81foreach (
$object->fields as $key => $val) {
82 if (
GETPOST(
'search_'.$key,
'alpha')) {
83 $search[$key] =
GETPOST(
'search_'.$key,
'alpha');
87if (empty($action) && empty($id) && empty($ref)) {
92include DOL_DOCUMENT_ROOT.
'/core/actions_fetchobject.inc.php';
100$paramwithsearch =
'';
101if ($limit > 0 && $limit != $conf->liste_limit) {
102 $paramwithsearch .=
'&limit='.((int) $limit);
107 $permissiontoadd = $user->hasRight(
'stock',
'creer');
108 $permissiontodelete = $user->hasRight(
'stock',
'supprimer');
109 $permissiontoupdatestock = $user->hasRight(
'stock',
'mouvement',
'creer');
111 $permissiontoadd = $user->hasRight(
'stock',
'inventory_advance',
'write');
112 $permissiontodelete = $user->hasRight(
'stock',
'inventory_advance',
'write');
113 $permissiontoupdatestock = $user->hasRight(
'stock',
'inventory_advance',
'write');
129$parameters = array();
130$reshook = $hookmanager->executeHooks(
'doActions', $parameters, $object, $action);
135if (empty($reshook)) {
138 if ($action ==
'cancel_record' && $permissiontoupdatestock) {
143 if ($action ==
'update' && $permissiontoupdatestock &&
$object->status == $object::STATUS_VALIDATED) {
147 $cacheOfProducts = array();
151 $sql =
'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
152 $sql .=
' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.pmp_real';
153 $sql .=
' FROM '.MAIN_DB_PREFIX.
'inventorydet as id';
154 $sql .=
' WHERE id.fk_inventory = '.((int)
$object->id);
155 $sql .=
' ORDER BY id.rowid';
157 $resql = $db->query($sql);
159 $num = $db->num_rows($resql);
161 $totalarray = array();
165 $line = $db->fetch_object($resql);
167 $qty_stock = $line->qty_stock;
168 $qty_view = $line->qty_view;
172 if (isset($cacheOfProducts[$line->fk_product])) {
173 $product_static = $cacheOfProducts[$line->fk_product];
175 $product_static =
new Product($db);
176 $result = $product_static->fetch($line->fk_product,
'',
'',
'', 1, 1, 1);
179 $option .=
',novirtual';
180 $product_static->load_stock($option);
182 $cacheOfProducts[$product_static->id] = $product_static;
186 $realqtynow = $product_static->stock_warehouse[$line->fk_warehouse]->real;
187 if (isModEnabled(
'productbatch') && $product_static->hasbatch()) {
188 $realqtynow = $product_static->stock_warehouse[$line->fk_warehouse]->detail_batch[$line->batch]->qty;
191 if (!is_null($qty_view)) {
192 $stock_movement_qty =
price2num($qty_view - $realqtynow,
'MS');
195 if ($stock_movement_qty != 0) {
196 if ($stock_movement_qty < 0) {
204 $inventorycode =
'INV-'.$object->ref;
207 $price = $line->pmp_real;
210 $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);
211 if ($idstockmove < 0) {
218 $sqlupdate =
"UPDATE ".MAIN_DB_PREFIX.
"inventorydet";
219 $sqlupdate .=
" SET fk_movement = ".((int) $idstockmove);
220 if ($qty_stock != $realqtynow) {
221 $sqlupdate .=
", qty_stock = ".((float) $realqtynow);
223 $sqlupdate .=
" WHERE rowid = ".((int) $line->rowid);
224 $resqlupdate = $db->query($sqlupdate);
225 if (! $resqlupdate) {
233 $sqlpmp =
'UPDATE '.MAIN_DB_PREFIX.
'product SET pmp = '.((float) $line->pmp_real).
' WHERE rowid = '.((int) $line->fk_product);
234 $resqlpmp = $db->query($sqlpmp);
241 $sqlpmp =
'UPDATE '.MAIN_DB_PREFIX.
'product_perentity SET pmp = '.((float) $line->pmp_real).
' WHERE fk_product = '.((int) $line->fk_product).
' AND entity='.$conf->entity;
242 $resqlpmp = $db->query($sqlpmp);
270 if ($action ==
'updateinventorylines' && $permissiontoupdatestock) {
271 $sql =
'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
272 $sql .=
' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated';
273 $sql .=
' FROM '.MAIN_DB_PREFIX.
'inventorydet as id';
274 $sql .=
' WHERE id.fk_inventory = '.((int)
$object->id);
275 $sql .= $db->order(
'id.rowid',
'ASC');
276 $sql .= $db->plimit($limit, $offset);
280 $resql = $db->query($sql);
282 $num = $db->num_rows($resql);
284 $totalarray = array();
288 $line = $db->fetch_object($resql);
289 $lineid = $line->rowid;
294 if (
GETPOST(
"id_".$lineid,
'alpha') !=
'') {
296 $result = $inventoryline->fetch($lineid);
297 if ($qtytoupdate < 0) {
299 setEventMessages($langs->trans(
"FieldCannotBeNegative", $langs->transnoentitiesnoconv(
"RealQty")),
null,
'errors');
302 $inventoryline->qty_stock = (float)
price2num(
GETPOST(
'stock_qty_'.$lineid,
'alpha'),
'MS');
303 $inventoryline->qty_view = $qtytoupdate;
304 $inventoryline->pmp_real =
price2num(
GETPOST(
'realpmp_'.$lineid,
'alpha'),
'MS');
305 $inventoryline->pmp_expected =
price2num(
GETPOST(
'expectedpmp_'.$lineid,
'alpha'),
'MS');
306 $resultupdate = $inventoryline->update($user);
308 } elseif (GETPOSTISSET(
'id_' . $lineid)) {
310 $result = $inventoryline->fetch($lineid);
312 $inventoryline->qty_view =
null;
313 $inventoryline->pmp_real =
price2num(
GETPOST(
'realpmp_'.$lineid,
'alpha'),
'MS');
314 $inventoryline->pmp_expected =
price2num(
GETPOST(
'expectedpmp_'.$lineid,
'alpha'),
'MS');
315 $resultupdate = $inventoryline->update($user);
319 if ($result < 0 || $resultupdate < 0) {
329 $sqlupdate =
"UPDATE ".MAIN_DB_PREFIX.
"inventory";
330 $sqlupdate .=
" SET fk_user_modif = ".((int) $user->id);
331 $sqlupdate .=
" WHERE rowid = ".((int)
$object->id);
332 $resqlupdate = $db->query($sqlupdate);
333 if (! $resqlupdate) {
346 $backurlforlist = DOL_URL_ROOT.
'/product/inventory/list.php';
347 $backtopage = DOL_URL_ROOT.
'/product/inventory/inventory.php?id='.
$object->id.
'&page='.$page.$paramwithsearch;
350 include DOL_DOCUMENT_ROOT.
'/core/actions_addupdatedelete.inc.php';
353 include DOL_DOCUMENT_ROOT.
'/core/actions_dellink.inc.php';
356 include DOL_DOCUMENT_ROOT.
'/core/actions_printing.inc.php';
364 if (
GETPOST(
'addline',
'alpha')) {
366 if ($fk_warehouse <= 0) {
368 setEventMessages($langs->trans(
"ErrorFieldRequired", $langs->transnoentitiesnoconv(
"Warehouse")),
null,
'errors');
370 if ($fk_product <= 0) {
372 setEventMessages($langs->trans(
"ErrorFieldRequired", $langs->transnoentitiesnoconv(
"Product")),
null,
'errors');
376 setEventMessages($langs->trans(
"FieldCannotBeNegative", $langs->transnoentitiesnoconv(
"RealQty")),
null,
'errors');
378 if (!$error && isModEnabled(
'productbatch')) {
379 $tmpproduct =
new Product($db);
380 $result = $tmpproduct->fetch($fk_product);
382 if (empty($error) && $tmpproduct->status_batch > 0 && empty($batch)) {
384 $langs->load(
"errors");
385 setEventMessages($langs->trans(
"ErrorProductNeedBatchNumber", $tmpproduct->ref),
null,
'errors');
387 if (empty($error) && $tmpproduct->status_batch == 2 && !empty($batch) && $qty > 1) {
389 $langs->load(
"errors");
390 setEventMessages($langs->trans(
"TooManyQtyForSerialNumber", $tmpproduct->ref, $batch),
null,
'errors');
392 if (empty($error) && empty($tmpproduct->status_batch) && !empty($batch)) {
394 $langs->load(
"errors");
395 setEventMessages($langs->trans(
"ErrorProductDoesNotNeedBatchNumber", $tmpproduct->ref),
null,
'errors');
400 $tmp->fk_inventory =
$object->id;
401 $tmp->fk_warehouse = $fk_warehouse;
402 $tmp->fk_product = $fk_product;
403 $tmp->batch = $batch;
405 $tmp->qty_view = $qty;
407 $result = $tmp->create($user);
409 if ($db->lasterrno() ==
'DB_ERROR_RECORD_ALREADY_EXISTS') {
410 $langs->load(
"errors");
411 setEventMessages($langs->trans(
"ErrorRecordAlreadyExists"),
null,
'errors');
417 $_POST[
'batch'] =
'';
418 $_POST[
'qtytoadd'] =
'';
430$form =
new Form($db);
435llxHeader(
'', $langs->trans(
'Inventory'), $help_url,
'', 0, 0,
'',
'',
'',
'mod-product page-inventory_inventory');
444$res =
$object->fetch_optionals();
452if ($action ==
'delete') {
453 $formconfirm = $form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.
$object->id, $langs->trans(
'DeleteInventory'), $langs->trans(
'ConfirmDeleteOrder'),
'confirm_delete',
'', 0, 1);
456if ($action ==
'deleteline') {
457 $formconfirm = $form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.
$object->id.
'&lineid='.$lineid.
'&page='.$page.$paramwithsearch, $langs->trans(
'DeleteLine'), $langs->trans(
'ConfirmDeleteLine'),
'confirm_deleteline',
'', 0, 1);
461if ($action ==
'clone') {
463 $formquestion = array();
464 $formconfirm = $form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.
$object->id, $langs->trans(
'ToClone'), $langs->trans(
'ConfirmCloneMyObject',
$object->ref),
'confirm_clone', $formquestion,
'yes', 1);
468if ($action ==
'record') {
469 $formconfirm = $form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.
$object->id.
'&page='.$page.$paramwithsearch, $langs->trans(
'Close'), $langs->trans(
'ConfirmFinish'),
'update',
'', 0, 1);
474if ($action ==
'confirm_cancel') {
475 $formconfirm = $form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.
$object->id, $langs->trans(
'Cancel'), $langs->trans(
'ConfirmCancel'),
'cancel_record',
'', 0, 1);
479if ($action ==
'validate') {
480 $form =
new Form($db);
483 $formquestion = array(
484 array(
'type' =>
'checkbox',
'name' =>
'include_sub_warehouse',
'label' => $langs->trans(
"IncludeSubWarehouse"),
'value' => 1,
'size' =>
'10'),
486 $formconfirm = $form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.
$object->id, $langs->trans(
'ValidateInventory'), $langs->trans(
'IncludeSubWarehouseExplanation'),
'confirm_validate', $formquestion,
'', 1);
491$parameters = array(
'formConfirm' => $formconfirm,
'lineid' => $lineid);
492$reshook = $hookmanager->executeHooks(
'formConfirm', $parameters, $object, $action);
493if (empty($reshook)) {
494 $formconfirm .= $hookmanager->resPrint;
495} elseif ($reshook > 0) {
496 $formconfirm = $hookmanager->resPrint;
505$linkback =
'<a href="'.DOL_URL_ROOT.
'/product/inventory/list.php">'.$langs->trans(
"BackToList").
'</a>';
507$morehtmlref =
'<div class="refidno">';
547$morehtmlref .=
'</div>';
550dol_banner_tab($object,
'ref', $linkback, 1,
'ref',
'ref', $morehtmlref);
553print
'<div class="fichecenter">';
554print
'<div class="fichehalfleft">';
555print
'<div class="underbanner clearboth"></div>';
556print
'<table class="border centpercent tableforfield">'.
"\n";
559include DOL_DOCUMENT_ROOT.
'/core/tpl/commonfields_view.tpl.php';
562include DOL_DOCUMENT_ROOT.
'/core/tpl/extrafields_view.tpl.php';
570print
'<div class="clearboth"></div>';
574print
'<form id="formrecord" name="formrecord" method="POST" action="'.$_SERVER[
"PHP_SELF"].
'?page='.$page.
'&id='.
$object->id.
'">';
575print
'<input type="hidden" name="token" value="'.newToken().
'">';
576print
'<input type="hidden" name="action" value="updateinventorylines">';
577print
'<input type="hidden" name="id" value="'.$object->id.
'">';
579 print
'<input type="hidden" name="backtopage" value="'.$backtopage.
'">';
584if ($action !=
'record') {
585 print
'<div class="tabsAction">'.
"\n";
586 $parameters = array();
587 $reshook = $hookmanager->executeHooks(
'addMoreActionsButtons', $parameters, $object, $action);
592 if (empty($reshook)) {
593 if (
$object->status == Inventory::STATUS_DRAFT) {
594 if ($permissiontoupdatestock) {
596 print
'<a class="butAction" href="'.$_SERVER[
'PHP_SELF'].
'?id='.
$object->id.
'&action=validate&token='.
newToken().
'">'.$langs->trans(
"Validate").
' ('.$langs->trans(
"Start").
')</a>';
598 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>';
601 print
'<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'">'.$langs->trans(
'Validate').
' ('.$langs->trans(
"Start").
')</a>'.
"\n";
606 if (
$object->status == $object::STATUS_VALIDATED) {
607 if ($permissiontoupdatestock) {
608 print
'<a class="butAction classfortooltip" id="idbuttonmakemovementandclose" href="'.$_SERVER[
"PHP_SELF"].
'?id='.
$object->id.
'&action=record&page='.$page.$paramwithsearch.
'&token='.
newToken().
'" title="'.
dol_escape_htmltag($langs->trans(
"MakeMovementsAndClose")).
'">'.$langs->trans(
"MakeMovementsAndClose").
'</a>'.
"\n";
610 print
'<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'">'.$langs->trans(
'MakeMovementsAndClose').
'</a>'.
"\n";
613 if ($permissiontoupdatestock) {
614 print
'<a class="butActionDelete" href="'.$_SERVER[
"PHP_SELF"].
'?id='.
$object->id.
'&action=confirm_cancel&page='.$page.$paramwithsearch.
'&token='.
newToken().
'">'.$langs->trans(
"Cancel").
'</a>'.
"\n";
620 if (
$object->status != Inventory::STATUS_DRAFT &&
$object->status != Inventory::STATUS_VALIDATED) {
627if (
$object->status == Inventory::STATUS_VALIDATED) {
629 if (!empty($conf->use_javascript_ajax)) {
630 if ($permissiontoupdatestock) {
632 if (isModEnabled(
'barcode') || isModEnabled(
'productbatch')) {
633 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>';
637 print
'<a id="fillwithexpected" class="marginrightonly paddingright marginleftonly paddingleft" href="#">'.img_picto(
'',
'autofill',
'class="paddingrightonly"').$langs->trans(
'AutofillWithExpected').
'</a>';
639 print
'$( document ).ready(function() {';
640 print
' $("#fillwithexpected").on("click",function fillWithExpected(){
641 $(".expectedqty").each(function(){
642 var object = $(this)[0];
643 var objecttofill = $("#"+object.id+"_input")[0];
644 objecttofill.value = object.innerText;
645 jQuery(".realqty").trigger("change");
647 console.log("Values filled (after click on fillwithexpected)");
648 /* disablebuttonmakemovementandclose(); */
655 print
'<a href="#" id="clearqty" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto(
'',
'eraser',
'class="paddingrightonly"').$langs->trans(
"ClearQtys").
'</a>';
657 print
'<a class="classfortooltip marginrightonly paddingright marginleftonly paddingleft" href="#" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'">'.$langs->trans(
"Save").
'</a>'.
"\n";
667if ($action ==
'updatebyscaning') {
668 if ($permissiontoupdatestock) {
673 var duplicatedbatchcode = [];
679 function barcodescannerjs(){
680 console.log("We catch inputs in scanner box");
681 jQuery("#scantoolmessage").text();
683 var selectaddorreplace = $("select[name=selectaddorreplace]").val();
684 var barcodemode = $("input[name=barcodemode]:checked").val();
685 var barcodeproductqty = $("input[name=barcodeproductqty]").val();
686 var textarea = $("textarea[name=barcodelist]").val();
687 var textarray = textarea.split(/[\s,;]+/);
689 duplicatedbatchcode = [];
695 textarray = textarray.filter(function(value){
698 if(textarray.some((element) => element != "")){
699 $(".expectedqty").each(function(){
701 console.log("Analyze the line "+id+" in inventory, barcodemode="+barcodemode);
702 warehouse = $("#"+id+"_warehouse").attr(\'data-ref\');
703 //console.log(warehouse);
704 productbarcode = $("#"+id+"_product").attr(\'data-barcode\');
705 //console.log(productbarcode);
706 productbatchcode = $("#"+id+"_batch").attr(\'data-batch\');
707 //console.log(productbatchcode);
709 if (barcodemode != "barcodeforproduct") {
710 tabproduct.forEach(product=>{
711 console.log("product.Batch="+product.Batch+" productbatchcode="+productbatchcode);
712 if(product.Batch != "" && product.Batch == productbatchcode){
713 console.log("duplicate batch code found for batch code "+productbatchcode);
714 duplicatedbatchcode.push(productbatchcode);
718 productinput = $("#"+id+"_input").val();
719 if(productinput == ""){
722 tabproduct.push({\'Id\':id,\'Warehouse\':warehouse,\'Barcode\':productbarcode,\'Batch\':productbatchcode,\'Qty\':productinput,\'fetched\':false});
725 console.log("Loop on each record entered in the textarea");
726 textarray.forEach(function(element,index){
727 console.log("Process record element="+element+" id="+id);
728 var verify_batch = false;
729 var verify_barcode = false;
731 case "barcodeforautodetect":
732 verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode",true);
733 verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial",true);
735 case "barcodeforproduct":
736 verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode");
738 case "barcodeforlotserial":
739 verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial");
742 alert(\''.dol_escape_js($langs->trans(
"ErrorWrongBarcodemode")).
' "\'+barcodemode+\'"\');
743 throw \''.
dol_escape_js($langs->trans(
'ErrorWrongBarcodemode')).
' "\'+barcodemode+\'"\';
746 if (verify_batch == false && verify_barcode == false) { /* If the 2 flags are false, not found error */
747 errortab2.push(element);
748 } else if (verify_batch == true && verify_barcode == true) { /* If the 2 flags are true, error: we don t know which one to take */
749 errortab3.push(element);
750 } else if (verify_batch == true) {
751 console.log("element="+element);
752 console.log(duplicatedbatchcode);
753 if (duplicatedbatchcode.includes(element)) {
754 errortab1.push(element);
759 if (Object.keys(errortab1).length < 1 && Object.keys(errortab2).length < 1 && Object.keys(errortab3).length < 1) {
760 tabproduct.forEach(product => {
762 console.log("We change #"+product.Id+"_input to match input in scanner box");
763 if(product.hasOwnProperty("reelqty")){
764 $.ajax({ url: \''.DOL_URL_ROOT.
'/product/inventory/ajax/searchfrombarcode.php\',
765 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},
768 success: function(response) {
769 response = JSON.parse(response);
770 if(response.status == "success"){
771 console.log(response.message);
772 $("<input type=\'text\' value=\'"+product.Qty+"\' />")
773 .attr("id", "id_"+response.id_line+"_input")
774 .attr("name", "id_"+response.id_line)
775 .appendTo("#formrecord");
777 console.error(response.message);
780 error : function(output) {
781 console.error("Error on line creation function");
785 $("#"+product.Id+"_input").val(product.Qty);
789 jQuery("#scantoolmessage").text("'.
dol_escape_js($langs->transnoentities(
"QtyWasAddedToTheScannedBarcode")).
'\n");
790 /* document.forms["formrecord"].submit(); */
792 let stringerror = "";
793 if (Object.keys(errortab1).length > 0) {
794 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorSameBatchNumber')).
': ";
795 errortab1.forEach(element => {
796 stringerror += (element + ", ")
798 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
800 if (Object.keys(errortab2).length > 0) {
801 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorCantFindCodeInInventory')).
': ";
802 errortab2.forEach(element => {
803 stringerror += (element + ", ")
805 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
807 if (Object.keys(errortab3).length > 0) {
808 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorCodeScannedIsBothProductAndSerial')).
': ";
809 errortab3.forEach(element => {
810 stringerror += (element + ", ")
812 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
814 if (Object.keys(errortab4).length > 0) {
815 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorBarcodeNotFoundForProductWarehouse')).
': ";
816 errortab4.forEach(element => {
817 stringerror += (element + ", ")
819 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
822 jQuery("#scantoolmessage").html(\''.
dol_escape_js($langs->transnoentities(
"ErrorOnElementsInventory")).
'\' + stringerror);
830 function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,mode,autodetect=
false){
831 BarcodeIsInProduct=0;
834 tabproduct.forEach(product => {
835 $.ajax({ url: \
''.DOL_URL_ROOT.
'/product/inventory/ajax/searchfrombarcode.php\',
836 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},
839 success: function(response) {
840 response = JSON.parse(response);
841 if (response.status == "success"){
842 console.log(response.message);
844 newproductrow = response.object;
847 if (mode!="lotserial" && autodetect==false && !errortab4.includes(element)){
848 errortab4.push(element);
849 console.error(response.message);
853 error : function(output) {
854 console.error("Error on barcodeserialforproduct function");
857 console.log("Product "+(index+=1)+": "+element);
858 if(mode == "barcode"){
859 testonproduct = product.Barcode
860 }else if (mode == "lotserial"){
861 testonproduct = product.Batch
863 if(testonproduct == element){
864 if(selectaddorreplace == "add"){
865 productqty = parseInt(product.Qty,10);
866 product.Qty = productqty + parseInt(barcodeproductqty,10);
867 }else if(selectaddorreplace == "replace"){
868 if(product.fetched == false){
869 product.Qty = barcodeproductqty
872 productqty = parseInt(product.Qty,10);
873 product.Qty = productqty + parseInt(barcodeproductqty,10);
876 BarcodeIsInProduct+=1;
879 if(BarcodeIsInProduct==0 && newproductrow!=0){
880 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});
883 if(BarcodeIsInProduct > 0){
891 include DOL_DOCUMENT_ROOT.
'/core/class/html.formother.class.php';
893 print $formother->getHTMLScannerForm(
"barcodescannerjs",
'all');
898print
'jQuery(document).ready(function() {
899 $("#clearqty").on("click", function() {
900 console.log("Clear all values");
901 /* disablebuttonmakemovementandclose(); */
902 jQuery(".realqty").val("");
903 jQuery(".realqty").trigger("change");
904 return false; /* disable submit */
906 $(".undochangesqty").on("click", function undochangesqty() {
907 console.log("Clear value of inventory line");
909 id = id.split("_")[1];
910 tmpvalue = $("#id_"+id+"_input_tmp").val()
911 $("#id_"+id+"_input")[0].value = tmpvalue;
912 /* disablebuttonmakemovementandclose(); */
913 return false; /* disable submit */
918print
'<div class="fichecenter">';
920print
'<div class="clearboth"></div>';
924print
'<div class="div-table-responsive-no-min">';
925print
'<table id="tablelines" class="noborder noshadow centpercent">';
927print
'<tr class="liste_titre">';
928print
'<td>'.$langs->trans(
"Warehouse").
'</td>';
929print
'<td>'.$langs->trans(
"Product").
'</td>';
930if (isModEnabled(
'productbatch')) {
932 print $langs->trans(
"Batch");
935if (
$object->status == $object::STATUS_DRAFT ||
$object->status == $object::STATUS_VALIDATED) {
937 print
'<td class="right">'.$form->textwithpicto($langs->trans(
"ExpectedQty"), $langs->trans(
"QtyCurrentlyKnownInStock")).
'</td>';
940 print
'<td class="right">'.$form->textwithpicto($langs->trans(
"ExpectedQty"), $langs->trans(
"QtyInStockWhenInventoryWasValidated")).
'</td>';
943 print
'<td class="right">'.$langs->trans(
'PMPExpected').
'</td>';
944 print
'<td class="right">'.$langs->trans(
'ExpectedValuation').
'</td>';
945 print
'<td class="right">'.$form->textwithpicto($langs->trans(
"RealQty"), $langs->trans(
"InventoryRealQtyHelp")).
'</td>';
946 print
'<td class="right">'.$langs->trans(
'PMPReal').
'</td>';
947 print
'<td class="right">'.$langs->trans(
'RealValuation').
'</td>';
949 print
'<td class="right">';
950 print $form->textwithpicto($langs->trans(
"RealQty"), $langs->trans(
"InventoryRealQtyHelp"));
953if (
$object->status == $object::STATUS_DRAFT ||
$object->status == $object::STATUS_VALIDATED) {
955 print
'<td class="center">';
959 print
'<td class="right">';
966if (
$object->status == $object::STATUS_DRAFT ||
$object->status == $object::STATUS_VALIDATED) {
969 print $formproduct->selectWarehouses((GETPOSTISSET(
'fk_warehouse') ?
GETPOSTINT(
'fk_warehouse') :
$object->fk_warehouse),
'fk_warehouse',
'warehouseopen', 1, 0, 0,
'', 0, 0, array(),
'maxwidth300');
977 print $form->select_produits((GETPOSTISSET(
'fk_product') ?
GETPOSTINT(
'fk_product') :
$object->fk_product),
'fk_product', $filtertype, 0, 0, -1, 2,
'', 0, null, 0,
'1', 0,
'maxwidth300');
979 if (isModEnabled(
'productbatch')) {
981 print
'<input type="text" name="batch" class="maxwidth100" value="'.(GETPOSTISSET(
'batch') ?
GETPOST(
'batch') :
'').
'">';
984 print
'<td class="right"></td>';
986 print
'<td class="right">';
988 print
'<td class="right">';
990 print
'<td class="right">';
991 print
'<input type="text" name="qtytoadd" class="maxwidth75" value="">';
993 print
'<td class="right">';
995 print
'<td class="right">';
998 print
'<td class="right">';
999 print
'<input type="text" name="qtytoadd" class="maxwidth75" value="">';
1003 print
'<td class="center">';
1004 if ($permissiontoupdatestock) {
1005 print
'<input type="submit" class="button paddingright" name="addline" value="'.$langs->trans(
"Add").
'">';
1007 print
'<input type="submit" class="button paddingright" disabled="disabled" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'" name="addline" value="'.$langs->trans(
"Add").
'">';
1014$sql =
'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
1015$sql .=
' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.fk_movement, id.pmp_real, id.pmp_expected';
1016$sql .=
' FROM '.MAIN_DB_PREFIX.
'inventorydet as id';
1017$sql .=
' WHERE id.fk_inventory = '.((int)
$object->id);
1018$sql .= $db->order(
'id.rowid',
'ASC');
1019$sql .= $db->plimit($limit, $offset);
1021$cacheOfProducts = array();
1022$cacheOfWarehouses = array();
1025$resql = $db->query($sql);
1027 $num = $db->num_rows($resql);
1029 if (!empty($limit != 0) || $num > $limit || $page) {
1030 print_fleche_navigation($page, $_SERVER[
"PHP_SELF"],
'&id='.
$object->id.$paramwithsearch, ($num >= $limit),
'<li class="pagination"><span>' . $langs->trans(
"Page") .
' ' . ($page + 1) .
'</span></li>',
'', $limit);
1035 $totalarray = array();
1037 $obj = $db->fetch_object($resql);
1039 if (isset($cacheOfWarehouses[$obj->fk_warehouse])) {
1040 $warehouse_static = $cacheOfWarehouses[$obj->fk_warehouse];
1042 $warehouse_static =
new Entrepot($db);
1043 $warehouse_static->fetch($obj->fk_warehouse);
1045 $cacheOfWarehouses[$warehouse_static->id] = $warehouse_static;
1050 if (isset($cacheOfProducts[$obj->fk_product])) {
1051 $product_static = $cacheOfProducts[$obj->fk_product];
1053 $product_static =
new Product($db);
1054 $result = $product_static->fetch($obj->fk_product,
'',
'',
'', 1, 1, 1);
1057 $option .=
',novirtual';
1058 $product_static->load_stock($option);
1060 $cacheOfProducts[$product_static->id] = $product_static;
1063 print
'<tr class="oddeven">';
1064 print
'<td id="id_'.$obj->rowid.
'_warehouse" data-ref="'.
dol_escape_htmltag($warehouse_static->ref).
'">';
1065 print $warehouse_static->getNomUrl(1);
1068 print $product_static->getNomUrl(1).
' - '.$product_static->label;
1071 if (isModEnabled(
'productbatch')) {
1072 print
'<td id="id_'.$obj->rowid.
'_batch" data-batch="'.
dol_escape_htmltag($obj->batch).
'">';
1075 $res = $batch_static->fetch(0, $product_static->id, $obj->batch);
1077 print $batch_static->getNomUrl(1);
1086 print
'<td class="right expectedqty" id="id_'.$obj->rowid.
'" title="Stock viewed at last update: '.$obj->qty_stock.
'">';
1087 $valuetoshow = $obj->qty_stock;
1089 if (
$object->status == $object::STATUS_DRAFT ||
$object->status == $object::STATUS_VALIDATED) {
1090 if (isModEnabled(
'productbatch') && $product_static->hasbatch()) {
1091 $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->detail_batch[$obj->batch]->qty ?? 0;
1093 $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->real ?? 0;
1097 print
'<input type="hidden" name="stock_qty_'.$obj->rowid.
'" value="'.$valuetoshow.
'">';
1101 if (
$object->status == $object::STATUS_DRAFT ||
$object->status == $object::STATUS_VALIDATED) {
1105 if ($qty_view !=
'') {
1111 if (!empty($obj->pmp_expected)) {
1112 $pmp_expected = $obj->pmp_expected;
1114 $pmp_expected = $product_static->pmp;
1116 $pmp_valuation = $pmp_expected * $valuetoshow;
1117 print
'<td class="right">';
1118 print
price($pmp_expected);
1119 print
'<input type="hidden" name="expectedpmp_'.$obj->rowid.
'" value="'.$pmp_expected.
'"/>';
1121 print
'<td class="right">';
1122 print
price($pmp_valuation);
1125 print
'<td class="right">';
1126 print
'<a id="undochangesqty_'.$obj->rowid.
'" href="#" class="undochangesqty reposition marginrightonly" title="'.
dol_escape_htmltag($langs->trans(
"Clear")).
'">';
1127 print
img_picto(
'',
'eraser',
'class="opacitymedium"');
1129 print
'<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.
'" id="id_'.$obj->rowid.
'_input" value="'.$qty_view.
'">';
1133 print
'<td class="right">';
1134 if (!empty($obj->pmp_real) || (
string) $obj->pmp_real ===
'0') {
1135 $pmp_real = $obj->pmp_real;
1137 $pmp_real = $product_static->pmp;
1139 $pmp_valuation_real = $pmp_real * $qty_view;
1140 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).
'">';
1142 print
'<td class="right">';
1143 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.
'">';
1146 $totalExpectedValuation += $pmp_valuation;
1147 $totalRealValuation += $pmp_valuation_real;
1149 print
'<td class="right">';
1150 print
'<a id="undochangesqty_'.$obj->rowid.
'" href="#" class="undochangesqty reposition marginrightonly" title="'.
dol_escape_htmltag($langs->trans(
"Clear")).
'">';
1151 print
img_picto(
'',
'eraser',
'class="opacitymedium"');
1153 print
'<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.
'" id="id_'.$obj->rowid.
'_input" value="'.$qty_view.
'">';
1158 print
'<td class="right">';
1159 if ($permissiontoupdatestock) {
1160 print
'<a class="reposition" href="'.DOL_URL_ROOT.
'/product/inventory/inventory.php?id='.
$object->id.
'&lineid='.$obj->rowid.
'&action=deleteline&page='.$page.$paramwithsearch.
'&token='.
newToken().
'">'.
img_delete().
'</a>';
1162 $qty_tmp =
price2num(
GETPOST(
"id_".$obj->rowid.
"_input_tmp",
'MS')) >= 0 ?
GETPOST(
"id_".$obj->rowid.
"_input_tmp") : $qty_view;
1163 print
'<input type="hidden" class="maxwidth50 right realqty" name="id_'.$obj->rowid.
'_input_tmp" id="id_'.$obj->rowid.
'_input_tmp" value="'.$qty_tmp.
'">';
1168 if (!empty($obj->pmp_expected)) {
1169 $pmp_expected = $obj->pmp_expected;
1171 $pmp_expected = $product_static->pmp;
1173 $pmp_valuation = $pmp_expected * $valuetoshow;
1174 print
'<td class="right">';
1175 print
price($pmp_expected);
1177 print
'<td class="right">';
1178 print
price($pmp_valuation);
1181 print
'<td class="right nowraponall">';
1182 print $obj->qty_view;
1186 print
'<td class="right">';
1187 if (!empty($obj->pmp_real)) {
1188 $pmp_real = $obj->pmp_real;
1190 $pmp_real = $product_static->pmp;
1192 $pmp_valuation_real = $pmp_real * $obj->qty_view;
1193 print
price($pmp_real);
1195 print
'<td class="right">';
1196 print
price($pmp_valuation_real);
1198 print
'<td class="nowraponall right">';
1200 $totalExpectedValuation += $pmp_valuation;
1201 $totalRealValuation += $pmp_valuation_real;
1203 print
'<td class="right nowraponall">';
1204 print $obj->qty_view;
1208 if ($obj->fk_movement > 0) {
1210 $stockmovment->fetch($obj->fk_movement);
1211 print $stockmovment->getNomUrl(1,
'movements');
1223 print
'<tr class="liste_total">';
1224 print
'<td colspan="4">'.$langs->trans(
"Total").
'</td>';
1225 print
'<td class="right" colspan="2">'.price($totalExpectedValuation).
'</td>';
1226 print
'<td class="right" id="totalRealValuation" colspan="3">'.price($totalRealValuation).
'</td>';
1234if (
$object->status == $object::STATUS_VALIDATED) {
1235 print
'<center><input id="submitrecord" type="submit" class="button button-save" name="save" value="'.$langs->trans(
"Save").
'"></center>';
1255print
'<script type="text/javascript">
1256 $(document).ready(function() {
1258 $(".paginationnext:last").click(function(e){
1259 var form = $("#formrecord");
1260 var actionURL = "'.$_SERVER[
'PHP_SELF'].
'?id='.
$object->id.
'&page='.($page).$paramwithsearch.
'";
1263 data: form.serialize(),
1265 success: function(result){
1266 window.location.href = "'.$_SERVER[
'PHP_SELF'].
'?id='.
$object->id.
'&page='.($page + 1).$paramwithsearch.
'";
1271 $(".paginationprevious:last").click(function(e){
1272 var form = $("#formrecord");
1273 var actionURL = "'.$_SERVER[
'PHP_SELF'].
'?id='.
$object->id.
'&page='.($page).$paramwithsearch.
'";
1276 data: form.serialize(),
1278 success: function(result){
1279 window.location.href = "'.$_SERVER[
'PHP_SELF'].
'?id='.
$object->id.
'&page='.($page - 1).$paramwithsearch.
'";
1283 $("#idbuttonmakemovementandclose").click(function(e){
1284 var form = $("#formrecord");
1285 var actionURL = "'.$_SERVER[
'PHP_SELF'].
'?id='.
$object->id.
'&page='.($page).$paramwithsearch.
'";
1288 data: form.serialize(),
1290 success: function(result){
1291 window.location.href = "'.$_SERVER[
'PHP_SELF'].
'?id='.
$object->id.
'&page='.($page - 1).$paramwithsearch.
'&action=record";
1300<script
type=
"text/javascript">
1301$(
'.realqty').on(
'change',
function () {
1302 let realqty = $(
this).closest(
'tr').find(
'.realqty').val();
1303 let inputPmp = $(
this).closest(
'tr').find(
'input[class*=realpmp]');
1304 let realpmp = $(inputPmp).val();
1305 if (!isNaN(realqty) && !isNaN(realpmp)) {
1306 let realval = realqty * realpmp;
1307 $(
this).closest(
'tr').find(
'input[name^=realvaluation]').val(realval.toFixed(2));
1309 updateTotalValuation();
1312$(
'input[class*=realpmp]').on(
'change',
function () {
1313 let inputQtyReal = $(
this).closest(
'tr').find(
'.realqty');
1314 let realqty = $(inputQtyReal).val();
1315 let inputPmp = $(
this).closest(
'tr').find(
'input[class*=realpmp]');
1316 console.log(inputPmp);
1317 let realPmpClassname = $(inputPmp).attr(
'class').match(/[\w-]*realpmp[\w-]*/g)[0];
1318 let realpmp = $(inputPmp).val();
1319 if (!isNaN(realpmp)) {
1320 $(
'.'+realPmpClassname).val(realpmp);
1322 if (!isNaN(realqty)) {
1323 let realval = realqty * realpmp;
1324 $(
this).closest(
'tr').find(
'input[name^=realvaluation]').val(realval.toFixed(2));
1326 $(
'.realqty').trigger(
'change');
1327 updateTotalValuation();
1331$(
'input[name^=realvaluation]').on(
'change',
function () {
1332 let inputQtyReal = $(
this).closest(
'tr').find(
'.realqty');
1333 let realqty = $(inputQtyReal).val();
1334 let inputPmp = $(
this).closest(
'tr').find(
'input[class*=realpmp]');
1335 let inputRealValuation = $(
this).closest(
'tr').find(
'input[name^=realvaluation]');
1336 let realPmpClassname = $(inputPmp).attr(
'class').match(/[\w-]*realpmp[\w-]*/g)[0];
1337 let realvaluation = $(inputRealValuation).val();
1338 if (!isNaN(realvaluation) && !isNaN(realqty) && realvaluation !==
'' && realqty !==
'' && realqty !== 0) {
1339 let realpmp = realvaluation / realqty
1340 $(
'.'+realPmpClassname).val(realpmp);
1341 $(
'.realqty').trigger(
'change');
1342 updateTotalValuation();
1346function updateTotalValuation() {
1348 $(
'input[name^=realvaluation]').each(
function( index ) {
1349 let val = $(
this).val();
1350 if(!isNaN(val)) total += parseFloat($(
this).val());
1352 let currencyFractionDigits =
new Intl.NumberFormat(
'fr-FR', {
1355 }).resolvedOptions().maximumFractionDigits;
1356 $(
'#totalRealValuation').html(total.toLocaleString(
'fr-FR', {
1357 maximumFractionDigits: currencyFractionDigits
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader()
Empty header.
Class to manage warehouses.
Class to manage stock movements.
Class to manage products or services.
Class with list of lots and properties.
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.
print_fleche_navigation($page, $file, $options='', $nextpage=0, $betweenarrows='', $afterarrows='', $limit=-1, $totalnboflines=0, $hideselectlimit=0, $beforearrows='', $hidenavigation=0)
Function to show navigation arrows into lists.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
currentToken()
Return the value of token currently saved into session with name 'token'.
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_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into javascript code.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
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...
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
inventoryPrepareHead(&$inventory, $title='Inventory', $get='')
Define head array for tabs of inventory tools setup pages.
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
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.