25 require
'../../main.inc.php';
26 include_once DOL_DOCUMENT_ROOT.
'/core/class/html.formcompany.class.php';
27 include_once DOL_DOCUMENT_ROOT.
'/product/class/html.formproduct.class.php';
28 include_once DOL_DOCUMENT_ROOT.
'/product/class/product.class.php';
29 include_once DOL_DOCUMENT_ROOT.
'/product/inventory/class/inventory.class.php';
30 include_once DOL_DOCUMENT_ROOT.
'/product/inventory/lib/inventory.lib.php';
31 include_once DOL_DOCUMENT_ROOT.
'/product/stock/class/mouvementstock.class.php';
32 include_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');
46 $limit =
GETPOST(
'limit',
'int') > 0 ?
GETPOST(
'limit',
'int') : $conf->liste_limit;
48 if (empty($page) || $page == -1) {
51 $offset = $limit * $page;
52 $pageprev = $page - 1;
53 $pagenext = $page + 1;
55 $fk_warehouse =
GETPOST(
'fk_warehouse',
'int');
56 $fk_product =
GETPOST(
'fk_product',
'int');
57 $lineid =
GETPOST(
'lineid',
'int');
58 $batch =
GETPOST(
'batch',
'alphanohtml');
59 $totalExpectedValuation = 0;
60 $totalRealValuation = 0;
61 if (empty($conf->global->MAIN_USE_ADVANCED_PERMS)) {
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');
81 foreach ($object->fields as $key => $val) {
82 if (
GETPOST(
'search_'.$key,
'alpha')) {
83 $search[$key] =
GETPOST(
'search_'.$key,
'alpha');
87 if (empty($action) && empty($id) && empty($ref)) {
92 include DOL_DOCUMENT_ROOT.
'/core/actions_fetchobject.inc.php';
100 $param =
'&id='.$object->id;
101 if ($limit > 0 && $limit != $conf->liste_limit) {
102 $param .=
'&limit='.urlencode($limit);
104 $paramwithsearch = $param;
107 if (empty($conf->global->MAIN_USE_ADVANCED_PERMS)) {
108 $permissiontoadd = $user->rights->stock->creer;
109 $permissiontodelete = $user->rights->stock->supprimer;
111 $permissiontoadd = $user->rights->stock->inventory_advance->write;
112 $permissiontodelete = $user->rights->stock->inventory_advance->write;
128 $parameters = array();
129 $reshook = $hookmanager->executeHooks(
'doActions', $parameters, $object, $action);
134 if (empty($reshook)) {
137 if ($action ==
'cancel_record' && $permissiontoadd) {
138 $object->setCanceled($user);
142 if ($action ==
'update' && !empty($user->rights->stock->mouvement->creer)) {
144 $stockmovment->setOrigin($object->element, $object->id);
146 $cacheOfProducts = array();
150 $sql =
'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
151 $sql .=
' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.pmp_real';
152 $sql .=
' FROM '.MAIN_DB_PREFIX.
'inventorydet as id';
153 $sql .=
' WHERE id.fk_inventory = '.((int) $object->id);
155 $resql = $db->query($sql);
157 $num = $db->num_rows(
$resql);
159 $totalarray = array();
161 $line = $db->fetch_object(
$resql);
163 $qty_stock = $line->qty_stock;
164 $qty_view = $line->qty_view;
168 if (isset($cacheOfProducts[$line->fk_product])) {
169 $product_static = $cacheOfProducts[$line->fk_product];
171 $product_static =
new Product($db);
172 $result = $product_static->fetch($line->fk_product,
'',
'',
'', 1, 1, 1);
175 $option .=
',novirtual';
176 $product_static->load_stock($option);
178 $cacheOfProducts[$product_static->id] = $product_static;
182 $realqtynow = $product_static->stock_warehouse[$line->fk_warehouse]->real;
183 if (
isModEnabled(
'productbatch') && $product_static->hasbatch()) {
184 $realqtynow = $product_static->stock_warehouse[$line->fk_warehouse]->detail_batch[$line->batch]->qty;
188 if (!is_null($qty_view)) {
189 $stock_movement_qty =
price2num($qty_view - $realqtynow,
'MS');
190 if ($stock_movement_qty != 0) {
191 if ($stock_movement_qty < 0) {
199 $inventorycode =
'INV-'.$object->ref;
201 if (!empty($line->pmp_real) && !empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) $price = $line->pmp_real;
203 $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);
204 if ($idstockmove < 0) {
211 $sqlupdate =
"UPDATE ".MAIN_DB_PREFIX.
"inventorydet";
212 $sqlupdate .=
" SET fk_movement = ".((int) $idstockmove);
213 if ($qty_stock != $realqtynow) {
214 $sqlupdate .=
", qty_stock = ".((float) $realqtynow);
216 $sqlupdate .=
" WHERE rowid = ".((int) $line->rowid);
217 $resqlupdate = $db->query($sqlupdate);
218 if (! $resqlupdate) {
225 if (!empty($line->pmp_real) && !empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
226 $sqlpmp =
'UPDATE '.MAIN_DB_PREFIX.
'product SET pmp = '.((
float) $line->pmp_real).
' WHERE rowid = '.((int) $line->fk_product);
227 $resqlpmp = $db->query($sqlpmp);
233 if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
234 $sqlpmp =
'UPDATE '.MAIN_DB_PREFIX.
'product_perentity SET pmp = '.((
float) $line->pmp_real).
' WHERE fk_product = '.((int) $line->fk_product).
' AND entity='.$conf->entity;
235 $resqlpmp = $db->query($sqlpmp);
248 $object->setRecorded($user);
263 if ($action ==
'updateinventorylines' && $permissiontoadd) {
264 $sql =
'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
265 $sql .=
' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated';
266 $sql .=
' FROM '.MAIN_DB_PREFIX.
'inventorydet as id';
267 $sql .=
' WHERE id.fk_inventory = '.((int) $object->id);
268 $sql .= $db->plimit($limit, $offset);
272 $resql = $db->query($sql);
274 $num = $db->num_rows(
$resql);
276 $totalarray = array();
280 $line = $db->fetch_object(
$resql);
281 $lineid = $line->rowid;
286 if (
GETPOST(
"id_".$lineid,
'alpha') !=
'') {
288 $result = $inventoryline->fetch($lineid);
289 if ($qtytoupdate < 0) {
291 setEventMessages($langs->trans(
"FieldCannotBeNegative", $langs->transnoentitiesnoconv(
"RealQty")),
null,
'errors');
294 $inventoryline->qty_stock =
price2num(
GETPOST(
'stock_qty_'.$lineid,
'alpha'),
'MS');
295 $inventoryline->qty_view = $qtytoupdate;
296 $inventoryline->pmp_real =
price2num(
GETPOST(
'realpmp_'.$lineid,
'alpha'),
'MS');
297 $inventoryline->pmp_expected =
price2num(
GETPOST(
'expectedpmp_'.$lineid,
'alpha'),
'MS');
298 $resultupdate = $inventoryline->update($user);
302 $result = $inventoryline->fetch($lineid);
304 $inventoryline->qty_view =
null;
305 $inventoryline->pmp_real =
price2num(
GETPOST(
'realpmp_'.$lineid,
'alpha'),
'MS');
306 $inventoryline->pmp_expected =
price2num(
GETPOST(
'expectedpmp_'.$lineid,
'alpha'),
'MS');
307 $resultupdate = $inventoryline->update($user);
311 if ($result < 0 || $resultupdate < 0) {
321 $sqlupdate =
"UPDATE ".MAIN_DB_PREFIX.
"inventory";
322 $sqlupdate .=
" SET fk_user_modif = ".((int) $user->id);
323 $sqlupdate .=
" WHERE rowid = ".((int) $object->id);
324 $resqlupdate = $db->query($sqlupdate);
325 if (! $resqlupdate) {
338 $backurlforlist = DOL_URL_ROOT.
'/product/inventory/list.php';
339 $backtopage = DOL_URL_ROOT.
'/product/inventory/inventory.php?id='.$object->id.
'&page='.$page.$paramwithsearch;
342 include DOL_DOCUMENT_ROOT.
'/core/actions_addupdatedelete.inc.php';
345 include DOL_DOCUMENT_ROOT.
'/core/actions_dellink.inc.php';
348 include DOL_DOCUMENT_ROOT.
'/core/actions_printing.inc.php';
356 if (
GETPOST(
'addline',
'alpha')) {
358 if ($fk_warehouse <= 0) {
360 setEventMessages($langs->trans(
"ErrorFieldRequired", $langs->transnoentitiesnoconv(
"Warehouse")),
null,
'errors');
362 if ($fk_product <= 0) {
364 setEventMessages($langs->trans(
"ErrorFieldRequired", $langs->transnoentitiesnoconv(
"Product")),
null,
'errors');
368 setEventMessages($langs->trans(
"FieldCannotBeNegative", $langs->transnoentitiesnoconv(
"RealQty")),
null,
'errors');
371 $tmpproduct =
new Product($db);
372 $result = $tmpproduct->fetch($fk_product);
374 if (empty($error) && $tmpproduct->status_batch>0 && empty($batch)) {
376 $langs->load(
"errors");
377 setEventMessages($langs->trans(
"ErrorProductNeedBatchNumber", $tmpproduct->ref),
null,
'errors');
379 if (empty($error) && $tmpproduct->status_batch==2 && !empty($batch) && $qty>1) {
381 $langs->load(
"errors");
382 setEventMessages($langs->trans(
"TooManyQtyForSerialNumber", $tmpproduct->ref, $batch),
null,
'errors');
384 if (empty($error) && empty($tmpproduct->status_batch) && !empty($batch)) {
386 $langs->load(
"errors");
387 setEventMessages($langs->trans(
"ErrorProductDoesNotNeedBatchNumber", $tmpproduct->ref),
null,
'errors');
392 $tmp->fk_inventory = $object->id;
393 $tmp->fk_warehouse = $fk_warehouse;
394 $tmp->fk_product = $fk_product;
395 $tmp->batch = $batch;
397 $tmp->qty_view = $qty;
399 $result = $tmp->create($user);
401 if ($db->lasterrno() ==
'DB_ERROR_RECORD_ALREADY_EXISTS') {
402 $langs->load(
"errors");
403 setEventMessages($langs->trans(
"ErrorRecordAlreadyExists"),
null,
'errors');
409 $_POST[
'batch'] =
'';
410 $_POST[
'qtytoadd'] =
'';
431 if ($object->id > 0) {
432 $res = $object->fetch_optionals();
435 print
dol_get_fiche_head($head,
'inventory', $langs->trans(
"Inventory"), -1,
'stock');
440 if ($action ==
'delete') {
441 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id, $langs->trans(
'DeleteInventory'), $langs->trans(
'ConfirmDeleteOrder'),
'confirm_delete',
'', 0, 1);
444 if ($action ==
'deleteline') {
445 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id.
'&lineid='.$lineid.
'&page='.$page.$paramwithsearch, $langs->trans(
'DeleteLine'), $langs->trans(
'ConfirmDeleteLine'),
'confirm_deleteline',
'', 0, 1);
449 if ($action ==
'clone') {
451 $formquestion = array();
452 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id, $langs->trans(
'ToClone'), $langs->trans(
'ConfirmCloneMyObject', $object->ref),
'confirm_clone', $formquestion,
'yes', 1);
456 if ($action ==
'record') {
457 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id, $langs->trans(
'Close'), $langs->trans(
'ConfirmFinish'),
'update',
'', 0, 1);
462 if ($action ==
'confirm_cancel') {
463 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id, $langs->trans(
'Cancel'), $langs->trans(
'ConfirmCancel'),
'cancel_record',
'', 0, 1);
468 $parameters = array(
'formConfirm' =>
$formconfirm,
'lineid' => $lineid);
469 $reshook = $hookmanager->executeHooks(
'formConfirm', $parameters, $object, $action);
470 if (empty($reshook)) {
472 } elseif ($reshook > 0) {
482 $linkback =
'<a href="'.DOL_URL_ROOT.
'/product/inventory/list.php">'.$langs->trans(
"BackToList").
'</a>';
484 $morehtmlref =
'<div class="refidno">';
524 $morehtmlref .=
'</div>';
527 dol_banner_tab($object,
'ref', $linkback, 1,
'ref',
'ref', $morehtmlref);
530 print
'<div class="fichecenter">';
531 print
'<div class="fichehalfleft">';
532 print
'<div class="underbanner clearboth"></div>';
533 print
'<table class="border centpercent tableforfield">'.
"\n";
536 include DOL_DOCUMENT_ROOT.
'/core/tpl/commonfields_view.tpl.php';
539 include DOL_DOCUMENT_ROOT.
'/core/tpl/extrafields_view.tpl.php';
547 print
'<div class="clearboth"></div>';
551 print
'<form id="formrecord" name="formrecord" method="POST" action="'.$_SERVER[
"PHP_SELF"].
'?page='.$page.
'&id='.$object->id.
'">';
552 print
'<input type="hidden" name="token" value="'.newToken().
'">';
553 print
'<input type="hidden" name="action" value="updateinventorylines">';
554 print
'<input type="hidden" name="id" value="'.$object->id.
'">';
556 print
'<input type="hidden" name="backtopage" value="'.$backtopage.
'">';
561 if ($action !=
'record') {
562 print
'<div class="tabsAction">'.
"\n";
563 $parameters = array();
564 $reshook = $hookmanager->executeHooks(
'addMoreActionsButtons', $parameters, $object, $action);
569 if (empty($reshook)) {
570 if ($object->status == Inventory::STATUS_DRAFT) {
571 if ($permissiontoadd) {
572 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";
574 print
'<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'">'.$langs->trans(
'Validate').
' ('.$langs->trans(
"Start").
')</a>'.
"\n";
579 if ($object->status == $object::STATUS_VALIDATED) {
580 if ($permissiontoadd) {
581 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";
583 print
'<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'">'.$langs->trans(
'MakeMovementsAndClose').
'</a>'.
"\n";
586 if ($permissiontoadd) {
587 print
'<a class="butActionDelete" href="'.$_SERVER[
"PHP_SELF"].
'?id='.$object->id.
'&action=confirm_cancel&token='.
newToken().
'">'.$langs->trans(
"Cancel").
'</a>'.
"\n";
593 if ($object->status != Inventory::STATUS_DRAFT && $object->status != Inventory::STATUS_VALIDATED) {
600 if ($object->status == Inventory::STATUS_VALIDATED) {
602 if (!empty($conf->use_javascript_ajax)) {
603 if ($permissiontoadd) {
606 print
'<a href="'.$_SERVER[
"PHP_SELF"].
'?id='.$object->id.
'&action=updatebyscaning" class="marginrightonly paddingright marginleftonly paddingleft">'.
img_picto(
'',
'barcode',
'class="paddingrightonly"').$langs->trans(
"UpdateByScaning").
'</a>';
610 print
'<a id="fillwithexpected" class="marginrightonly paddingright marginleftonly paddingleft" href="#">'.img_picto(
'',
'autofill',
'class="paddingrightonly"').$langs->trans(
'AutofillWithExpected').
'</a>';
612 print
'$( document ).ready(function() {';
613 print
' $("#fillwithexpected").on("click",function fillWithExpected(){
614 $(".expectedqty").each(function(){
615 var object = $(this)[0];
616 var objecttofill = $("#"+object.id+"_input")[0];
617 objecttofill.value = object.innerText;
618 jQuery(".realqty").trigger("change");
620 console.log("Values filled (after click on fillwithexpected)");
621 disablebuttonmakemovementandclose();
628 print
'<a href="#" id="clearqty" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto(
'',
'eraser',
'class="paddingrightonly"').$langs->trans(
"ClearQtys").
'</a>';
630 print
'<a class="classfortooltip marginrightonly paddingright marginleftonly paddingleft" href="#" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'">'.$langs->trans(
"Save").
'</a>'.
"\n";
640 if ($action ==
'updatebyscaning') {
641 if ($permissiontoadd) {
646 var duplicatedbatchcode = [];
652 function barcodescannerjs(){
653 console.log("We catch inputs in scanner box");
654 jQuery("#scantoolmessage").text();
656 var selectaddorreplace = $("select[name=selectaddorreplace]").val();
657 var barcodemode = $("input[name=barcodemode]:checked").val();
658 var barcodeproductqty = $("input[name=barcodeproductqty]").val();
659 var textarea = $("textarea[name=barcodelist]").val();
660 var textarray = textarea.split(/[\s,;]+/);
662 duplicatedbatchcode = [];
668 textarray = textarray.filter(function(value){
671 if(textarray.some((element) => element != "")){
672 $(".expectedqty").each(function(){
674 console.log("Analyze the line "+id+" in inventory, barcodemode="+barcodemode);
675 warehouse = $("#"+id+"_warehouse").attr(\'data-ref\');
676 //console.log(warehouse);
677 productbarcode = $("#"+id+"_product").attr(\'data-barcode\');
678 //console.log(productbarcode);
679 productbatchcode = $("#"+id+"_batch").attr(\'data-batch\');
680 //console.log(productbatchcode);
682 if (barcodemode != "barcodeforproduct") {
683 tabproduct.forEach(product=>{
684 console.log("product.Batch="+product.Batch+" productbatchcode="+productbatchcode);
685 if(product.Batch != "" && product.Batch == productbatchcode){
686 console.log("duplicate batch code found for batch code "+productbatchcode);
687 duplicatedbatchcode.push(productbatchcode);
691 productinput = $("#"+id+"_input").val();
692 if(productinput == ""){
695 tabproduct.push({\'Id\':id,\'Warehouse\':warehouse,\'Barcode\':productbarcode,\'Batch\':productbatchcode,\'Qty\':productinput,\'fetched\':false});
698 console.log("Loop on each record entered in the textarea");
699 textarray.forEach(function(element,index){
700 console.log("Process record element="+element+" id="+id);
701 var verify_batch = false;
702 var verify_barcode = false;
704 case "barcodeforautodetect":
705 verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode",true);
706 verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial",true);
708 case "barcodeforproduct":
709 verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode");
711 case "barcodeforlotserial":
712 verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial");
715 alert(\''.dol_escape_js($langs->trans(
"ErrorWrongBarcodemode")).
' "\'+barcodemode+\'"\');
716 throw \''.
dol_escape_js($langs->trans(
'ErrorWrongBarcodemode')).
' "\'+barcodemode+\'"\';
719 if (verify_batch == false && verify_barcode == false) { /* If the 2 flags are false, not found error */
720 errortab2.push(element);
721 } else if (verify_batch == true && verify_barcode == true) { /* If the 2 flags are true, error: we don t know which one to take */
722 errortab3.push(element);
723 } else if (verify_batch == true) {
724 console.log("element="+element);
725 console.log(duplicatedbatchcode);
726 if (duplicatedbatchcode.includes(element)) {
727 errortab1.push(element);
732 if (Object.keys(errortab1).length < 1 && Object.keys(errortab2).length < 1 && Object.keys(errortab3).length < 1) {
733 tabproduct.forEach(product => {
735 console.log("We change #"+product.Id+"_input to match input in scanner box");
736 if(product.hasOwnProperty("reelqty")){
737 $.ajax({ url: \''.DOL_URL_ROOT.
'/product/inventory/ajax/searchfrombarcode.php\',
738 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},
741 success: function(response) {
742 response = JSON.parse(response);
743 if(response.status == "success"){
744 console.log(response.message);
745 $("<input type=\'text\' value=\'"+product.Qty+"\' />")
746 .attr("id", "id_"+response.id_line+"_input")
747 .attr("name", "id_"+response.id_line)
748 .appendTo("#formrecord");
750 console.error(response.message);
753 error : function(output) {
754 console.error("Error on line creation function");
758 $("#"+product.Id+"_input").val(product.Qty);
762 jQuery("#scantoolmessage").text("'.
dol_escape_js($langs->transnoentities(
"QtyWasAddedToTheScannedBarcode")).
'\n");
763 /* document.forms["formrecord"].submit(); */
765 let stringerror = "";
766 if (Object.keys(errortab1).length > 0) {
767 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorSameBatchNumber')).
': ";
768 errortab1.forEach(element => {
769 stringerror += (element + ", ")
771 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
773 if (Object.keys(errortab2).length > 0) {
774 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorCantFindCodeInInventory')).
': ";
775 errortab2.forEach(element => {
776 stringerror += (element + ", ")
778 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
780 if (Object.keys(errortab3).length > 0) {
781 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorCodeScannedIsBothProductAndSerial')).
': ";
782 errortab3.forEach(element => {
783 stringerror += (element + ", ")
785 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
787 if (Object.keys(errortab4).length > 0) {
788 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorBarcodeNotFoundForProductWarehouse')).
': ";
789 errortab4.forEach(element => {
790 stringerror += (element + ", ")
792 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
795 jQuery("#scantoolmessage").html(\''.
dol_escape_js($langs->transnoentities(
"ErrorOnElementsInventory")).
'\' + stringerror);
803 function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,mode,autodetect=
false){
804 BarcodeIsInProduct=0;
807 tabproduct.forEach(product => {
808 $.ajax({ url: \
''.DOL_URL_ROOT.
'/product/inventory/ajax/searchfrombarcode.php\',
809 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},
812 success: function(response) {
813 response = JSON.parse(response);
814 if (response.status == "success"){
815 console.log(response.message);
817 newproductrow = response.object;
820 if (mode!="lotserial" && autodetect==false && !errortab4.includes(element)){
821 errortab4.push(element);
822 console.error(response.message);
826 error : function(output) {
827 console.error("Error on barcodeserialforproduct function");
830 console.log("Product "+(index+=1)+": "+element);
831 if(mode == "barcode"){
832 testonproduct = product.Barcode
833 }else if (mode == "lotserial"){
834 testonproduct = product.Batch
836 if(testonproduct == element){
837 if(selectaddorreplace == "add"){
838 productqty = parseInt(product.Qty,10);
839 product.Qty = productqty + parseInt(barcodeproductqty,10);
840 }else if(selectaddorreplace == "replace"){
841 if(product.fetched == false){
842 product.Qty = barcodeproductqty
845 productqty = parseInt(product.Qty,10);
846 product.Qty = productqty + parseInt(barcodeproductqty,10);
849 BarcodeIsInProduct+=1;
852 if(BarcodeIsInProduct==0 && newproductrow!=0){
853 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});
856 if(BarcodeIsInProduct > 0){
864 include DOL_DOCUMENT_ROOT.
'/core/class/html.formother.class.php';
866 print $formother->getHTMLScannerForm(
"barcodescannerjs",
'all');
871 print
'jQuery(document).ready(function() {
872 $("#clearqty").on("click", function() {
873 console.log("Clear all values");
874 disablebuttonmakemovementandclose();
875 jQuery(".realqty").val("");
876 jQuery(".realqty").trigger("change");
877 return false; /* disable submit */
879 $(".undochangesqty").on("click", function undochangesqty() {
880 console.log("Clear value of inventory line");
882 id = id.split("_")[1];
883 tmpvalue = $("#id_"+id+"_input_tmp").val()
884 $("#id_"+id+"_input")[0].value = tmpvalue;
885 disablebuttonmakemovementandclose();
886 return false; /* disable submit */
891 print
'<div class="fichecenter">';
893 print
'<div class="clearboth"></div>';
897 print
'<div class="div-table-responsive-no-min">';
898 print
'<table id="tablelines" class="noborder noshadow centpercent">';
900 print
'<tr class="liste_titre">';
901 print
'<td>'.$langs->trans(
"Warehouse").
'</td>';
902 print
'<td>'.$langs->trans(
"Product").
'</td>';
905 print $langs->trans(
"Batch");
908 print
'<td class="right">'.$langs->trans(
"ExpectedQty").
'</td>';
909 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
910 print
'<td class="right">'.$langs->trans(
'PMPExpected').
'</td>';
911 print
'<td class="right">'.$langs->trans(
'ExpectedValuation').
'</td>';
912 print
'<td class="right">'.$form->textwithpicto($langs->trans(
"RealQty"), $langs->trans(
"InventoryRealQtyHelp")).
'</td>';
913 print
'<td class="right">'.$langs->trans(
'PMPReal').
'</td>';
914 print
'<td class="right">'.$langs->trans(
'RealValuation').
'</td>';
916 print
'<td class="right">';
917 print
$form->textwithpicto($langs->trans(
"RealQty"), $langs->trans(
"InventoryRealQtyHelp"));
920 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
922 print
'<td class="center">';
926 print
'<td class="right">';
933 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
936 print $formproduct->selectWarehouses((
GETPOSTISSET(
'fk_warehouse') ?
GETPOST(
'fk_warehouse',
'int') : $object->fk_warehouse),
'fk_warehouse',
'warehouseopen', 1, 0, 0,
'', 0, 0, array(),
'maxwidth300');
939 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');
943 print
'<input type="text" name="batch" class="maxwidth100" value="'.(GETPOSTISSET(
'batch') ?
GETPOST(
'batch') :
'').
'">';
946 print
'<td class="right"></td>';
947 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
948 print
'<td class="right">';
950 print
'<td class="right">';
952 print
'<td class="right">';
953 print
'<input type="text" name="qtytoadd" class="maxwidth75" value="">';
955 print
'<td class="right">';
957 print
'<td class="right">';
960 print
'<td class="right">';
961 print
'<input type="text" name="qtytoadd" class="maxwidth75" value="">';
965 print
'<td class="center">';
966 print
'<input type="submit" class="button paddingright" name="addline" value="'.$langs->trans(
"Add").
'">';
972 $sql =
'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
973 $sql .=
' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.fk_movement, id.pmp_real, id.pmp_expected';
974 $sql .=
' FROM '.MAIN_DB_PREFIX.
'inventorydet as id';
975 $sql .=
' WHERE id.fk_inventory = '.((int) $object->id);
976 $sql .= $db->order(
'id.rowid',
'ASC');
977 $sql .= $db->plimit($limit, $offset);
979 $cacheOfProducts = array();
980 $cacheOfWarehouses = array();
983 $resql = $db->query($sql);
985 $num = $db->num_rows(
$resql);
987 if (!empty($limit != 0) || $num > $limit || $page) {
988 print_fleche_navigation($page, $_SERVER[
"PHP_SELF"], $paramwithsearch, ($num >= $limit),
'<li class="pagination"><span>' . $langs->trans(
"Page") .
' ' . ($page + 1) .
'</span></li>',
'', $limit);
993 $totalarray = array();
995 $obj = $db->fetch_object(
$resql);
997 if (isset($cacheOfWarehouses[$obj->fk_warehouse])) {
998 $warehouse_static = $cacheOfWarehouses[$obj->fk_warehouse];
1000 $warehouse_static =
new Entrepot($db);
1001 $warehouse_static->fetch($obj->fk_warehouse);
1003 $cacheOfWarehouses[$warehouse_static->id] = $warehouse_static;
1008 if (isset($cacheOfProducts[$obj->fk_product])) {
1009 $product_static = $cacheOfProducts[$obj->fk_product];
1011 $product_static =
new Product($db);
1012 $result = $product_static->fetch($obj->fk_product,
'',
'',
'', 1, 1, 1);
1015 $option .=
',novirtual';
1016 $product_static->load_stock($option);
1018 $cacheOfProducts[$product_static->id] = $product_static;
1021 print
'<tr class="oddeven">';
1022 print
'<td id="id_'.$obj->rowid.
'_warehouse" data-ref="'.
dol_escape_htmltag($warehouse_static->ref).
'">';
1023 print $warehouse_static->getNomUrl(1);
1026 print $product_static->getNomUrl(1).
' - '.$product_static->label;
1030 print
'<td id="id_'.$obj->rowid.
'_batch" data-batch="'.
dol_escape_htmltag($obj->batch).
'">';
1032 $res = $batch_static->fetch(0, $product_static->id, $obj->batch);
1034 print $batch_static->getNomUrl(1);
1042 print
'<td class="right expectedqty" id="id_'.$obj->rowid.
'" title="Stock viewed at last update: '.$obj->qty_stock.
'">';
1043 $valuetoshow = $obj->qty_stock;
1045 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
1046 if (
isModEnabled(
'productbatch') && $product_static->hasbatch()) {
1047 $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->detail_batch[$obj->batch]->qty;
1049 $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->real;
1053 print
'<input type="hidden" name="stock_qty_'.$obj->rowid.
'" value="'.$valuetoshow.
'">';
1057 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
1061 if ($qty_view !=
'') {
1065 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1067 if (!empty($obj->pmp_expected)) $pmp_expected = $obj->pmp_expected;
1068 else $pmp_expected = $product_static->pmp;
1069 $pmp_valuation = $pmp_expected * $valuetoshow;
1070 print
'<td class="right">';
1071 print
price($pmp_expected);
1072 print
'<input type="hidden" name="expectedpmp_'.$obj->rowid.
'" value="'.$pmp_expected.
'"/>';
1074 print
'<td class="right">';
1075 print
price($pmp_valuation);
1078 print
'<td class="right">';
1079 print
'<a id="undochangesqty_'.$obj->rowid.
'" href="#" class="undochangesqty reposition marginrightonly" title="'.
dol_escape_htmltag($langs->trans(
"Clear")).
'">';
1080 print
img_picto(
'',
'eraser',
'class="opacitymedium"');
1082 print
'<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.
'" id="id_'.$obj->rowid.
'_input" value="'.$qty_view.
'">';
1086 print
'<td class="right">';
1089 if (!empty($obj->pmp_real)) $pmp_real = $obj->pmp_real;
1090 else $pmp_real = $product_static->pmp;
1091 $pmp_valuation_real = $pmp_real * $qty_view;
1092 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).
'">';
1094 print
'<td class="right">';
1095 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.
'">';
1098 $totalExpectedValuation += $pmp_valuation;
1099 $totalRealValuation += $pmp_valuation_real;
1101 print
'<td class="right">';
1102 print
'<a id="undochangesqty_'.$obj->rowid.
'" href="#" class="undochangesqty reposition marginrightonly" title="'.
dol_escape_htmltag($langs->trans(
"Clear")).
'">';
1103 print
img_picto(
'',
'eraser',
'class="opacitymedium"');
1105 print
'<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.
'" id="id_'.$obj->rowid.
'_input" value="'.$qty_view.
'">';
1110 print
'<td class="right">';
1111 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>';
1112 $qty_tmp =
price2num(
GETPOST(
"id_".$obj->rowid.
"_input_tmp",
'MS')) >= 0 ?
GETPOST(
"id_".$obj->rowid.
"_input_tmp") : $qty_view;
1113 print
'<input type="hidden" class="maxwidth50 right realqty" name="id_'.$obj->rowid.
'_input_tmp" id="id_'.$obj->rowid.
'_input_tmp" value="'.$qty_tmp.
'">';
1116 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1118 if (!empty($obj->pmp_expected)) $pmp_expected = $obj->pmp_expected;
1119 else $pmp_expected = $product_static->pmp;
1120 $pmp_valuation = $pmp_expected * $valuetoshow;
1121 print
'<td class="right">';
1122 print
price($pmp_expected);
1124 print
'<td class="right">';
1125 print
price($pmp_valuation);
1128 print
'<td class="right nowraponall">';
1129 print $obj->qty_view;
1133 print
'<td class="right">';
1134 if (!empty($obj->pmp_real)) $pmp_real = $obj->pmp_real;
1135 else $pmp_real = $product_static->pmp;
1136 $pmp_valuation_real = $pmp_real * $obj->qty_view;
1137 print
price($pmp_real);
1139 print
'<td class="right">';
1140 print
price($pmp_valuation_real);
1142 print
'<td class="nowraponall right">';
1144 $totalExpectedValuation += $pmp_valuation;
1145 $totalRealValuation += $pmp_valuation_real;
1147 print
'<td class="right nowraponall">';
1148 print $obj->qty_view;
1151 if ($obj->fk_movement > 0) {
1153 $stockmovment->fetch($obj->fk_movement);
1154 print $stockmovment->getNomUrl(1,
'movements');
1165 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1166 print
'<tr class="liste_total">';
1167 print
'<td colspan="4">'.$langs->trans(
"Total").
'</td>';
1168 print
'<td class="right" colspan="2">'.price($totalExpectedValuation).
'</td>';
1169 print
'<td class="right" id="totalRealValuation" colspan="3">'.price($totalRealValuation).
'</td>';
1177 if ($object->status == $object::STATUS_VALIDATED) {
1178 print
'<center><input id="submitrecord" type="submit" class="button button-save" name="save" value="'.$langs->trans(
"Save").
'"></center>';
1186 if ($object->status != $object::STATUS_VALIDATED || !$hasinput) {
1187 print
'<script type="text/javascript">
1188 jQuery(document).ready(function() {
1189 console.log("Call disablebuttonmakemovementandclose because status = '.((int) $object->status).
' or $hasinput = '.((int) $hasinput).
'");
1190 disablebuttonmakemovementandclose();
1196 print
'<script type="text/javascript">
1197 $(document).ready(function() {
1199 $(".paginationnext:last").click(function(e){
1200 var form = $("#formrecord");
1201 var actionURL = "'.$_SERVER[
'PHP_SELF'].
"?page=".($page).$paramwithsearch.
'";
1204 data: form.serialize(),
1206 success: function(result){
1207 window.location.href = "'.$_SERVER[
'PHP_SELF'].
"?page=".($page + 1).$paramwithsearch.
'";
1212 $(".paginationprevious:last").click(function(e){
1213 var form = $("#formrecord");
1214 var actionURL = "'.$_SERVER[
'PHP_SELF'].
"?page=".($page).$paramwithsearch.
'";
1217 data: form.serialize(),
1219 success: function(result){
1220 window.location.href = "'.$_SERVER[
'PHP_SELF'].
"?page=".($page - 1).$paramwithsearch.
'";
1224 $("#idbuttonmakemovementandclose").click(function(e){
1225 var form = $("#formrecord");
1226 var actionURL = "'.$_SERVER[
'PHP_SELF'].
"?page=".($page).$paramwithsearch.
'";
1229 data: form.serialize(),
1231 success: function(result){
1232 window.location.href = "'.$_SERVER[
'PHP_SELF'].
"?page=".($page - 1).$paramwithsearch.
'&action=record";
1239 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1241 <script
type=
"text/javascript">
1242 $(
'.realqty').on(
'change',
function () {
1243 let realqty = $(
this).closest(
'tr').find(
'.realqty').val();
1244 let inputPmp = $(
this).closest(
'tr').find(
'input[class*=realpmp]');
1245 let realpmp = $(inputPmp).val();
1246 if (!isNaN(realqty) && !isNaN(realpmp)) {
1247 let realval = realqty * realpmp;
1248 $(
this).closest(
'tr').find(
'input[name^=realvaluation]').val(realval.toFixed(2));
1250 updateTotalValuation();
1253 $(
'input[class*=realpmp]').on(
'change',
function () {
1254 let inputQtyReal = $(
this).closest(
'tr').find(
'.realqty');
1255 let realqty = $(inputQtyReal).val();
1256 let inputPmp = $(
this).closest(
'tr').find(
'input[class*=realpmp]');
1257 console.log(inputPmp);
1258 let realPmpClassname = $(inputPmp).attr(
'class').match(/[\w-]*realpmp[\w-]*/g)[0];
1259 let realpmp = $(inputPmp).val();
1260 if (!isNaN(realpmp)) {
1261 $(
'.'+realPmpClassname).val(realpmp);
1263 if (!isNaN(realqty)) {
1264 let realval = realqty * realpmp;
1265 $(
this).closest(
'tr').find(
'input[name^=realvaluation]').val(realval.toFixed(2));
1267 $(
'.realqty').trigger(
'change');
1268 updateTotalValuation();
1272 $(
'input[name^=realvaluation]').on(
'change',
function () {
1273 let inputQtyReal = $(
this).closest(
'tr').find(
'.realqty');
1274 let realqty = $(inputQtyReal).val();
1275 let inputPmp = $(
this).closest(
'tr').find(
'input[class*=realpmp]');
1276 let inputRealValuation = $(
this).closest(
'tr').find(
'input[name^=realvaluation]');
1277 let realPmpClassname = $(inputPmp).attr(
'class').match(/[\w-]*realpmp[\w-]*/g)[0];
1278 let realvaluation = $(inputRealValuation).val();
1279 if (!isNaN(realvaluation) && !isNaN(realqty) && realvaluation !==
'' && realqty !==
'' && realqty !== 0) {
1280 let realpmp = realvaluation / realqty
1281 $(
'.'+realPmpClassname).val(realpmp);
1282 $(
'.realqty').trigger(
'change');
1283 updateTotalValuation();
1287 function updateTotalValuation() {
1289 $(
'input[name^=realvaluation]').each(
function( index ) {
1290 let val = $(
this).val();
1291 if(!isNaN(val)) total += parseFloat($(
this).val());
1293 let currencyFractionDigits =
new Intl.NumberFormat(
'fr-FR', {
1296 }).resolvedOptions().maximumFractionDigits;
1297 $(
'#totalRealValuation').html(total.toLocaleString(
'fr-FR', {
1298 maximumFractionDigits: currencyFractionDigits
if(GETPOST('button_removefilter_x', 'alpha')||GETPOST('button_removefilter.x', 'alpha')||GETPOST('button_removefilter', 'alpha')) if(GETPOST('button_search_x', 'alpha')||GETPOST('button_search.x', 'alpha')||GETPOST('button_search', 'alpha')) if($action=="save" &&empty($cancel)) $help_url
View.
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.
if(isModEnabled('facture') &&!empty($user->rights->facture->lire)) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') &&!empty($user->rights->don->lire)) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $resql
Social contributions to pay.
if($cancel &&! $id) if($action=='add' &&! $cancel) if($action=='delete') if($id) $form
Actions.
dol_banner_tab($object, $paramid, $morehtml='', $shownav=1, $fieldid='rowid', $fieldref='ref', $morehtmlref='', $moreparam='', $nodbprefix=0, $morehtmlleft='', $morehtmlstatus='', $onlybanner=0, $morehtmlright='')
Show tab footer of a card.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='')
Show tabs of a record.
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0)
Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_get_fiche_end($notab=0)
Return tab footer of a card.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='')
Set event messages in dol_events session object.
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.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
dol_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'.
print_fleche_navigation($page, $file, $options='', $nextpage=0, $betweenarrows='', $afterarrows='', $limit=-1, $totalnboflines=0, $hideselectlimit=0, $beforearrows='')
Function to show navigation arrows into lists.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
isModEnabled($module)
Is Dolibarr module enabled.
inventoryPrepareHead(&$inventory, $title='Inventory', $get='')
Define head array for tabs of inventory tools setup pages.
$formconfirm
if ($action == 'delbookkeepingyear') {
div float
Buy price without taxes.
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.