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='.((int) $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) && $object->status == $object::STATUS_VALIDATED) {
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'] =
'';
430 if ($object->id <= 0) {
436 $res = $object->fetch_optionals();
439 print
dol_get_fiche_head($head,
'inventory', $langs->trans(
"Inventory"), -1,
'stock');
444 if ($action ==
'delete') {
445 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id, $langs->trans(
'DeleteInventory'), $langs->trans(
'ConfirmDeleteOrder'),
'confirm_delete',
'', 0, 1);
448 if ($action ==
'deleteline') {
449 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id.
'&lineid='.$lineid.
'&page='.$page.$paramwithsearch, $langs->trans(
'DeleteLine'), $langs->trans(
'ConfirmDeleteLine'),
'confirm_deleteline',
'', 0, 1);
453 if ($action ==
'clone') {
455 $formquestion = array();
456 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id, $langs->trans(
'ToClone'), $langs->trans(
'ConfirmCloneMyObject', $object->ref),
'confirm_clone', $formquestion,
'yes', 1);
460 if ($action ==
'record') {
461 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id, $langs->trans(
'Close'), $langs->trans(
'ConfirmFinish'),
'update',
'', 0, 1);
466 if ($action ==
'confirm_cancel') {
467 $formconfirm =
$form->formconfirm($_SERVER[
"PHP_SELF"].
'?id='.$object->id, $langs->trans(
'Cancel'), $langs->trans(
'ConfirmCancel'),
'cancel_record',
'', 0, 1);
472 $parameters = array(
'formConfirm' =>
$formconfirm,
'lineid' => $lineid);
473 $reshook = $hookmanager->executeHooks(
'formConfirm', $parameters, $object, $action);
474 if (empty($reshook)) {
476 } elseif ($reshook > 0) {
486 $linkback =
'<a href="'.DOL_URL_ROOT.
'/product/inventory/list.php">'.$langs->trans(
"BackToList").
'</a>';
488 $morehtmlref =
'<div class="refidno">';
528 $morehtmlref .=
'</div>';
531 dol_banner_tab($object,
'ref', $linkback, 1,
'ref',
'ref', $morehtmlref);
534 print
'<div class="fichecenter">';
535 print
'<div class="fichehalfleft">';
536 print
'<div class="underbanner clearboth"></div>';
537 print
'<table class="border centpercent tableforfield">'.
"\n";
540 include DOL_DOCUMENT_ROOT.
'/core/tpl/commonfields_view.tpl.php';
543 include DOL_DOCUMENT_ROOT.
'/core/tpl/extrafields_view.tpl.php';
551 print
'<div class="clearboth"></div>';
555 print
'<form id="formrecord" name="formrecord" method="POST" action="'.$_SERVER[
"PHP_SELF"].
'?page='.$page.
'&id='.$object->id.
'">';
556 print
'<input type="hidden" name="token" value="'.newToken().
'">';
557 print
'<input type="hidden" name="action" value="updateinventorylines">';
558 print
'<input type="hidden" name="id" value="'.$object->id.
'">';
560 print
'<input type="hidden" name="backtopage" value="'.$backtopage.
'">';
565 if ($action !=
'record') {
566 print
'<div class="tabsAction">'.
"\n";
567 $parameters = array();
568 $reshook = $hookmanager->executeHooks(
'addMoreActionsButtons', $parameters, $object, $action);
573 if (empty($reshook)) {
574 if ($object->status == Inventory::STATUS_DRAFT) {
575 if ($permissiontoadd) {
576 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";
578 print
'<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'">'.$langs->trans(
'Validate').
' ('.$langs->trans(
"Start").
')</a>'.
"\n";
583 if ($object->status == $object::STATUS_VALIDATED) {
584 if ($permissiontoadd) {
585 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";
587 print
'<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'">'.$langs->trans(
'MakeMovementsAndClose').
'</a>'.
"\n";
590 if ($permissiontoadd) {
591 print
'<a class="butActionDelete" href="'.$_SERVER[
"PHP_SELF"].
'?id='.$object->id.
'&action=confirm_cancel&token='.
newToken().
'">'.$langs->trans(
"Cancel").
'</a>'.
"\n";
597 if ($object->status != Inventory::STATUS_DRAFT && $object->status != Inventory::STATUS_VALIDATED) {
604 if ($object->status == Inventory::STATUS_VALIDATED) {
606 if (!empty($conf->use_javascript_ajax)) {
607 if ($permissiontoadd) {
610 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>';
614 print
'<a id="fillwithexpected" class="marginrightonly paddingright marginleftonly paddingleft" href="#">'.img_picto(
'',
'autofill',
'class="paddingrightonly"').$langs->trans(
'AutofillWithExpected').
'</a>';
616 print
'$( document ).ready(function() {';
617 print
' $("#fillwithexpected").on("click",function fillWithExpected(){
618 $(".expectedqty").each(function(){
619 var object = $(this)[0];
620 var objecttofill = $("#"+object.id+"_input")[0];
621 objecttofill.value = object.innerText;
622 jQuery(".realqty").trigger("change");
624 console.log("Values filled (after click on fillwithexpected)");
625 /* disablebuttonmakemovementandclose(); */
632 print
'<a href="#" id="clearqty" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto(
'',
'eraser',
'class="paddingrightonly"').$langs->trans(
"ClearQtys").
'</a>';
634 print
'<a class="classfortooltip marginrightonly paddingright marginleftonly paddingleft" href="#" title="'.dol_escape_htmltag($langs->trans(
"NotEnoughPermissions")).
'">'.$langs->trans(
"Save").
'</a>'.
"\n";
644 if ($action ==
'updatebyscaning') {
645 if ($permissiontoadd) {
650 var duplicatedbatchcode = [];
656 function barcodescannerjs(){
657 console.log("We catch inputs in scanner box");
658 jQuery("#scantoolmessage").text();
660 var selectaddorreplace = $("select[name=selectaddorreplace]").val();
661 var barcodemode = $("input[name=barcodemode]:checked").val();
662 var barcodeproductqty = $("input[name=barcodeproductqty]").val();
663 var textarea = $("textarea[name=barcodelist]").val();
664 var textarray = textarea.split(/[\s,;]+/);
666 duplicatedbatchcode = [];
672 textarray = textarray.filter(function(value){
675 if(textarray.some((element) => element != "")){
676 $(".expectedqty").each(function(){
678 console.log("Analyze the line "+id+" in inventory, barcodemode="+barcodemode);
679 warehouse = $("#"+id+"_warehouse").attr(\'data-ref\');
680 //console.log(warehouse);
681 productbarcode = $("#"+id+"_product").attr(\'data-barcode\');
682 //console.log(productbarcode);
683 productbatchcode = $("#"+id+"_batch").attr(\'data-batch\');
684 //console.log(productbatchcode);
686 if (barcodemode != "barcodeforproduct") {
687 tabproduct.forEach(product=>{
688 console.log("product.Batch="+product.Batch+" productbatchcode="+productbatchcode);
689 if(product.Batch != "" && product.Batch == productbatchcode){
690 console.log("duplicate batch code found for batch code "+productbatchcode);
691 duplicatedbatchcode.push(productbatchcode);
695 productinput = $("#"+id+"_input").val();
696 if(productinput == ""){
699 tabproduct.push({\'Id\':id,\'Warehouse\':warehouse,\'Barcode\':productbarcode,\'Batch\':productbatchcode,\'Qty\':productinput,\'fetched\':false});
702 console.log("Loop on each record entered in the textarea");
703 textarray.forEach(function(element,index){
704 console.log("Process record element="+element+" id="+id);
705 var verify_batch = false;
706 var verify_barcode = false;
708 case "barcodeforautodetect":
709 verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode",true);
710 verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial",true);
712 case "barcodeforproduct":
713 verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode");
715 case "barcodeforlotserial":
716 verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial");
719 alert(\''.dol_escape_js($langs->trans(
"ErrorWrongBarcodemode")).
' "\'+barcodemode+\'"\');
720 throw \''.
dol_escape_js($langs->trans(
'ErrorWrongBarcodemode')).
' "\'+barcodemode+\'"\';
723 if (verify_batch == false && verify_barcode == false) { /* If the 2 flags are false, not found error */
724 errortab2.push(element);
725 } else if (verify_batch == true && verify_barcode == true) { /* If the 2 flags are true, error: we don t know which one to take */
726 errortab3.push(element);
727 } else if (verify_batch == true) {
728 console.log("element="+element);
729 console.log(duplicatedbatchcode);
730 if (duplicatedbatchcode.includes(element)) {
731 errortab1.push(element);
736 if (Object.keys(errortab1).length < 1 && Object.keys(errortab2).length < 1 && Object.keys(errortab3).length < 1) {
737 tabproduct.forEach(product => {
739 console.log("We change #"+product.Id+"_input to match input in scanner box");
740 if(product.hasOwnProperty("reelqty")){
741 $.ajax({ url: \''.DOL_URL_ROOT.
'/product/inventory/ajax/searchfrombarcode.php\',
742 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},
745 success: function(response) {
746 response = JSON.parse(response);
747 if(response.status == "success"){
748 console.log(response.message);
749 $("<input type=\'text\' value=\'"+product.Qty+"\' />")
750 .attr("id", "id_"+response.id_line+"_input")
751 .attr("name", "id_"+response.id_line)
752 .appendTo("#formrecord");
754 console.error(response.message);
757 error : function(output) {
758 console.error("Error on line creation function");
762 $("#"+product.Id+"_input").val(product.Qty);
766 jQuery("#scantoolmessage").text("'.
dol_escape_js($langs->transnoentities(
"QtyWasAddedToTheScannedBarcode")).
'\n");
767 /* document.forms["formrecord"].submit(); */
769 let stringerror = "";
770 if (Object.keys(errortab1).length > 0) {
771 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorSameBatchNumber')).
': ";
772 errortab1.forEach(element => {
773 stringerror += (element + ", ")
775 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
777 if (Object.keys(errortab2).length > 0) {
778 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorCantFindCodeInInventory')).
': ";
779 errortab2.forEach(element => {
780 stringerror += (element + ", ")
782 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
784 if (Object.keys(errortab3).length > 0) {
785 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorCodeScannedIsBothProductAndSerial')).
': ";
786 errortab3.forEach(element => {
787 stringerror += (element + ", ")
789 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
791 if (Object.keys(errortab4).length > 0) {
792 stringerror += "<br>'.
dol_escape_js($langs->transnoentities(
'ErrorBarcodeNotFoundForProductWarehouse')).
': ";
793 errortab4.forEach(element => {
794 stringerror += (element + ", ")
796 stringerror = stringerror.slice(0, -2); /* Remove last ", " */
799 jQuery("#scantoolmessage").html(\''.
dol_escape_js($langs->transnoentities(
"ErrorOnElementsInventory")).
'\' + stringerror);
807 function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,mode,autodetect=
false){
808 BarcodeIsInProduct=0;
811 tabproduct.forEach(product => {
812 $.ajax({ url: \
''.DOL_URL_ROOT.
'/product/inventory/ajax/searchfrombarcode.php\',
813 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},
816 success: function(response) {
817 response = JSON.parse(response);
818 if (response.status == "success"){
819 console.log(response.message);
821 newproductrow = response.object;
824 if (mode!="lotserial" && autodetect==false && !errortab4.includes(element)){
825 errortab4.push(element);
826 console.error(response.message);
830 error : function(output) {
831 console.error("Error on barcodeserialforproduct function");
834 console.log("Product "+(index+=1)+": "+element);
835 if(mode == "barcode"){
836 testonproduct = product.Barcode
837 }else if (mode == "lotserial"){
838 testonproduct = product.Batch
840 if(testonproduct == element){
841 if(selectaddorreplace == "add"){
842 productqty = parseInt(product.Qty,10);
843 product.Qty = productqty + parseInt(barcodeproductqty,10);
844 }else if(selectaddorreplace == "replace"){
845 if(product.fetched == false){
846 product.Qty = barcodeproductqty
849 productqty = parseInt(product.Qty,10);
850 product.Qty = productqty + parseInt(barcodeproductqty,10);
853 BarcodeIsInProduct+=1;
856 if(BarcodeIsInProduct==0 && newproductrow!=0){
857 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});
860 if(BarcodeIsInProduct > 0){
868 include DOL_DOCUMENT_ROOT.
'/core/class/html.formother.class.php';
870 print $formother->getHTMLScannerForm(
"barcodescannerjs",
'all');
875 print
'jQuery(document).ready(function() {
876 $("#clearqty").on("click", function() {
877 console.log("Clear all values");
878 /* disablebuttonmakemovementandclose(); */
879 jQuery(".realqty").val("");
880 jQuery(".realqty").trigger("change");
881 return false; /* disable submit */
883 $(".undochangesqty").on("click", function undochangesqty() {
884 console.log("Clear value of inventory line");
886 id = id.split("_")[1];
887 tmpvalue = $("#id_"+id+"_input_tmp").val()
888 $("#id_"+id+"_input")[0].value = tmpvalue;
889 /* disablebuttonmakemovementandclose(); */
890 return false; /* disable submit */
895 print
'<div class="fichecenter">';
897 print
'<div class="clearboth"></div>';
901 print
'<div class="div-table-responsive-no-min">';
902 print
'<table id="tablelines" class="noborder noshadow centpercent">';
904 print
'<tr class="liste_titre">';
905 print
'<td>'.$langs->trans(
"Warehouse").
'</td>';
906 print
'<td>'.$langs->trans(
"Product").
'</td>';
909 print $langs->trans(
"Batch");
912 print
'<td class="right">'.$langs->trans(
"ExpectedQty").
'</td>';
913 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
914 print
'<td class="right">'.$langs->trans(
'PMPExpected').
'</td>';
915 print
'<td class="right">'.$langs->trans(
'ExpectedValuation').
'</td>';
916 print
'<td class="right">'.$form->textwithpicto($langs->trans(
"RealQty"), $langs->trans(
"InventoryRealQtyHelp")).
'</td>';
917 print
'<td class="right">'.$langs->trans(
'PMPReal').
'</td>';
918 print
'<td class="right">'.$langs->trans(
'RealValuation').
'</td>';
920 print
'<td class="right">';
921 print
$form->textwithpicto($langs->trans(
"RealQty"), $langs->trans(
"InventoryRealQtyHelp"));
924 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
926 print
'<td class="center">';
930 print
'<td class="right">';
937 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
940 print $formproduct->selectWarehouses((
GETPOSTISSET(
'fk_warehouse') ?
GETPOST(
'fk_warehouse',
'int') : $object->fk_warehouse),
'fk_warehouse',
'warehouseopen', 1, 0, 0,
'', 0, 0, array(),
'maxwidth300');
943 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 print
'<input type="text" name="batch" class="maxwidth100" value="'.(GETPOSTISSET(
'batch') ?
GETPOST(
'batch') :
'').
'">';
950 print
'<td class="right"></td>';
951 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
952 print
'<td class="right">';
954 print
'<td class="right">';
956 print
'<td class="right">';
957 print
'<input type="text" name="qtytoadd" class="maxwidth75" value="">';
959 print
'<td class="right">';
961 print
'<td class="right">';
964 print
'<td class="right">';
965 print
'<input type="text" name="qtytoadd" class="maxwidth75" value="">';
969 print
'<td class="center">';
970 print
'<input type="submit" class="button paddingright" name="addline" value="'.$langs->trans(
"Add").
'">';
976 $sql =
'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
977 $sql .=
' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.fk_movement, id.pmp_real, id.pmp_expected';
978 $sql .=
' FROM '.MAIN_DB_PREFIX.
'inventorydet as id';
979 $sql .=
' WHERE id.fk_inventory = '.((int) $object->id);
980 $sql .= $db->order(
'id.rowid',
'ASC');
981 $sql .= $db->plimit($limit, $offset);
983 $cacheOfProducts = array();
984 $cacheOfWarehouses = array();
987 $resql = $db->query(
$sql);
989 $num = $db->num_rows($resql);
991 if (!empty($limit != 0) || $num > $limit || $page) {
992 print_fleche_navigation($page, $_SERVER[
"PHP_SELF"], $paramwithsearch, ($num >= $limit),
'<li class="pagination"><span>' . $langs->trans(
"Page") .
' ' . ($page + 1) .
'</span></li>',
'', $limit);
997 $totalarray = array();
999 $obj = $db->fetch_object($resql);
1001 if (isset($cacheOfWarehouses[$obj->fk_warehouse])) {
1002 $warehouse_static = $cacheOfWarehouses[$obj->fk_warehouse];
1004 $warehouse_static =
new Entrepot($db);
1005 $warehouse_static->fetch($obj->fk_warehouse);
1007 $cacheOfWarehouses[$warehouse_static->id] = $warehouse_static;
1012 if (isset($cacheOfProducts[$obj->fk_product])) {
1013 $product_static = $cacheOfProducts[$obj->fk_product];
1015 $product_static =
new Product($db);
1016 $result = $product_static->fetch($obj->fk_product,
'',
'',
'', 1, 1, 1);
1019 $option .=
',novirtual';
1020 $product_static->load_stock($option);
1022 $cacheOfProducts[$product_static->id] = $product_static;
1025 print
'<tr class="oddeven">';
1026 print
'<td id="id_'.$obj->rowid.
'_warehouse" data-ref="'.
dol_escape_htmltag($warehouse_static->ref).
'">';
1027 print $warehouse_static->getNomUrl(1);
1030 print $product_static->getNomUrl(1).
' - '.$product_static->label;
1034 print
'<td id="id_'.$obj->rowid.
'_batch" data-batch="'.
dol_escape_htmltag($obj->batch).
'">';
1036 $res = $batch_static->fetch(0, $product_static->id, $obj->batch);
1038 print $batch_static->getNomUrl(1);
1046 print
'<td class="right expectedqty" id="id_'.$obj->rowid.
'" title="Stock viewed at last update: '.$obj->qty_stock.
'">';
1047 $valuetoshow = $obj->qty_stock;
1049 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
1050 if (
isModEnabled(
'productbatch') && $product_static->hasbatch()) {
1051 $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->detail_batch[$obj->batch]->qty;
1053 $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->real;
1057 print
'<input type="hidden" name="stock_qty_'.$obj->rowid.
'" value="'.$valuetoshow.
'">';
1061 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
1065 if ($qty_view !=
'') {
1069 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1071 if (!empty($obj->pmp_expected)) $pmp_expected = $obj->pmp_expected;
1072 else $pmp_expected = $product_static->pmp;
1073 $pmp_valuation = $pmp_expected * $valuetoshow;
1074 print
'<td class="right">';
1075 print
price($pmp_expected);
1076 print
'<input type="hidden" name="expectedpmp_'.$obj->rowid.
'" value="'.$pmp_expected.
'"/>';
1078 print
'<td class="right">';
1079 print
price($pmp_valuation);
1082 print
'<td class="right">';
1083 print
'<a id="undochangesqty_'.$obj->rowid.
'" href="#" class="undochangesqty reposition marginrightonly" title="'.
dol_escape_htmltag($langs->trans(
"Clear")).
'">';
1084 print
img_picto(
'',
'eraser',
'class="opacitymedium"');
1086 print
'<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.
'" id="id_'.$obj->rowid.
'_input" value="'.$qty_view.
'">';
1090 print
'<td class="right">';
1093 if (!empty($obj->pmp_real)) $pmp_real = $obj->pmp_real;
1094 else $pmp_real = $product_static->pmp;
1095 $pmp_valuation_real = $pmp_real * $qty_view;
1096 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).
'">';
1098 print
'<td class="right">';
1099 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.
'">';
1102 $totalExpectedValuation += $pmp_valuation;
1103 $totalRealValuation += $pmp_valuation_real;
1105 print
'<td class="right">';
1106 print
'<a id="undochangesqty_'.$obj->rowid.
'" href="#" class="undochangesqty reposition marginrightonly" title="'.
dol_escape_htmltag($langs->trans(
"Clear")).
'">';
1107 print
img_picto(
'',
'eraser',
'class="opacitymedium"');
1109 print
'<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.
'" id="id_'.$obj->rowid.
'_input" value="'.$qty_view.
'">';
1114 print
'<td class="right">';
1115 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>';
1116 $qty_tmp =
price2num(
GETPOST(
"id_".$obj->rowid.
"_input_tmp",
'MS')) >= 0 ?
GETPOST(
"id_".$obj->rowid.
"_input_tmp") : $qty_view;
1117 print
'<input type="hidden" class="maxwidth50 right realqty" name="id_'.$obj->rowid.
'_input_tmp" id="id_'.$obj->rowid.
'_input_tmp" value="'.$qty_tmp.
'">';
1120 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1122 if (!empty($obj->pmp_expected)) $pmp_expected = $obj->pmp_expected;
1123 else $pmp_expected = $product_static->pmp;
1124 $pmp_valuation = $pmp_expected * $valuetoshow;
1125 print
'<td class="right">';
1126 print
price($pmp_expected);
1128 print
'<td class="right">';
1129 print
price($pmp_valuation);
1132 print
'<td class="right nowraponall">';
1133 print $obj->qty_view;
1137 print
'<td class="right">';
1138 if (!empty($obj->pmp_real)) $pmp_real = $obj->pmp_real;
1139 else $pmp_real = $product_static->pmp;
1140 $pmp_valuation_real = $pmp_real * $obj->qty_view;
1141 print
price($pmp_real);
1143 print
'<td class="right">';
1144 print
price($pmp_valuation_real);
1146 print
'<td class="nowraponall right">';
1148 $totalExpectedValuation += $pmp_valuation;
1149 $totalRealValuation += $pmp_valuation_real;
1151 print
'<td class="right nowraponall">';
1152 print $obj->qty_view;
1155 if ($obj->fk_movement > 0) {
1157 $stockmovment->fetch($obj->fk_movement);
1158 print $stockmovment->getNomUrl(1,
'movements');
1169 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1170 print
'<tr class="liste_total">';
1171 print
'<td colspan="4">'.$langs->trans(
"Total").
'</td>';
1172 print
'<td class="right" colspan="2">'.price($totalExpectedValuation).
'</td>';
1173 print
'<td class="right" id="totalRealValuation" colspan="3">'.price($totalRealValuation).
'</td>';
1181 if ($object->status == $object::STATUS_VALIDATED) {
1182 print
'<center><input id="submitrecord" type="submit" class="button button-save" name="save" value="'.$langs->trans(
"Save").
'"></center>';
1202 print
'<script type="text/javascript">
1203 $(document).ready(function() {
1205 $(".paginationnext:last").click(function(e){
1206 var form = $("#formrecord");
1207 var actionURL = "'.$_SERVER[
'PHP_SELF'].
"?page=".($page).$paramwithsearch.
'";
1210 data: form.serialize(),
1212 success: function(result){
1213 window.location.href = "'.$_SERVER[
'PHP_SELF'].
"?page=".($page + 1).$paramwithsearch.
'";
1218 $(".paginationprevious:last").click(function(e){
1219 var form = $("#formrecord");
1220 var actionURL = "'.$_SERVER[
'PHP_SELF'].
"?page=".($page).$paramwithsearch.
'";
1223 data: form.serialize(),
1225 success: function(result){
1226 window.location.href = "'.$_SERVER[
'PHP_SELF'].
"?page=".($page - 1).$paramwithsearch.
'";
1230 $("#idbuttonmakemovementandclose").click(function(e){
1231 var form = $("#formrecord");
1232 var actionURL = "'.$_SERVER[
'PHP_SELF'].
"?page=".($page).$paramwithsearch.
'";
1235 data: form.serialize(),
1237 success: function(result){
1238 window.location.href = "'.$_SERVER[
'PHP_SELF'].
"?page=".($page - 1).$paramwithsearch.
'&action=record";
1245 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1247 <script
type=
"text/javascript">
1248 $(
'.realqty').on(
'change',
function () {
1249 let realqty = $(
this).closest(
'tr').find(
'.realqty').val();
1250 let inputPmp = $(
this).closest(
'tr').find(
'input[class*=realpmp]');
1251 let realpmp = $(inputPmp).val();
1252 if (!isNaN(realqty) && !isNaN(realpmp)) {
1253 let realval = realqty * realpmp;
1254 $(
this).closest(
'tr').find(
'input[name^=realvaluation]').val(realval.toFixed(2));
1256 updateTotalValuation();
1259 $(
'input[class*=realpmp]').on(
'change',
function () {
1260 let inputQtyReal = $(
this).closest(
'tr').find(
'.realqty');
1261 let realqty = $(inputQtyReal).val();
1262 let inputPmp = $(
this).closest(
'tr').find(
'input[class*=realpmp]');
1263 console.log(inputPmp);
1264 let realPmpClassname = $(inputPmp).attr(
'class').match(/[\w-]*realpmp[\w-]*/g)[0];
1265 let realpmp = $(inputPmp).val();
1266 if (!isNaN(realpmp)) {
1267 $(
'.'+realPmpClassname).val(realpmp);
1269 if (!isNaN(realqty)) {
1270 let realval = realqty * realpmp;
1271 $(
this).closest(
'tr').find(
'input[name^=realvaluation]').val(realval.toFixed(2));
1273 $(
'.realqty').trigger(
'change');
1274 updateTotalValuation();
1278 $(
'input[name^=realvaluation]').on(
'change',
function () {
1279 let inputQtyReal = $(
this).closest(
'tr').find(
'.realqty');
1280 let realqty = $(inputQtyReal).val();
1281 let inputPmp = $(
this).closest(
'tr').find(
'input[class*=realpmp]');
1282 let inputRealValuation = $(
this).closest(
'tr').find(
'input[name^=realvaluation]');
1283 let realPmpClassname = $(inputPmp).attr(
'class').match(/[\w-]*realpmp[\w-]*/g)[0];
1284 let realvaluation = $(inputRealValuation).val();
1285 if (!isNaN(realvaluation) && !isNaN(realqty) && realvaluation !==
'' && realqty !==
'' && realqty !== 0) {
1286 let realpmp = realvaluation / realqty
1287 $(
'.'+realPmpClassname).val(realpmp);
1288 $(
'.realqty').trigger(
'change');
1289 updateTotalValuation();
1293 function updateTotalValuation() {
1295 $(
'input[name^=realvaluation]').each(
function( index ) {
1296 let val = $(
this).val();
1297 if(!isNaN(val)) total += parseFloat($(
this).val());
1299 let currencyFractionDigits =
new Intl.NumberFormat(
'fr-FR', {
1302 }).resolvedOptions().maximumFractionDigits;
1303 $(
'#totalRealValuation').html(total.toLocaleString(
'fr-FR', {
1304 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') && $user->hasRight('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') && $user->hasRight('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)) $sql
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.
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0)
Show tabs of a record.
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 '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
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.
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'.
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.
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.
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.
$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.