Pārlūkot izejas kodu

New Add js scanner expedition repartition (#26126)

* add scanner js component

* new line creation

* add security + remove unused code

* add of object to test

---------

Co-authored-by: Hystepik <lucas.marcouiller@gmail.com>
Lucas Marcouiller 1 gadu atpakaļ
vecāks
revīzija
694dc8d171

+ 10 - 2
htdocs/core/class/html.formother.class.php

@@ -62,9 +62,10 @@ class FormOther
 	 *
 	 * @param	string	$jstoexecuteonadd	Name of javascript function to call once the barcode scanning session is complete and user has click on "Add".
 	 * @param	string	$mode				'all' (both product and lot barcode) or 'product' (product barcode only) or 'lot' (lot number only)
+	 * @param	int		$warehouseselect	0 (disable warehouse select) or 1 (enable warehouse select)
 	 * @return	string						HTML component
 	 */
-	public function getHTMLScannerForm($jstoexecuteonadd = 'barcodescannerjs', $mode = 'all')
+	public function getHTMLScannerForm($jstoexecuteonadd = 'barcodescannerjs', $mode = 'all', $warehouseselect = 0)
 	{
 		global $langs;
 
@@ -87,7 +88,14 @@ class FormOther
 		$htmltoreplaceby = '<select name="selectaddorreplace"><option selected value="add">'.$langs->trans("Add").'</option><option value="replace">'.$langs->trans("ToReplace").'</option></select>';
 		$stringaddbarcode = str_replace("tmphtml", $htmltoreplaceby, $stringaddbarcode);
 		$out .= $stringaddbarcode.' <input type="text" name="barcodeproductqty" class="width50 right" value="1"><br>';
-		$out .= '<br>';
+		if ($warehouseselect > 0) {
+			require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
+			$formproduct = new FormProduct($this->db);
+			$formproduct->loadWarehouses();
+			$out .= $formproduct->selectWarehouses('', "warehousenew", '', 0, 0, 0, '', 0, 1);
+			$out .= '<br>';
+			$out .= '<br>';
+		}
 		$out .= '<textarea type="text" name="barcodelist" class="centpercent" autofocus rows="'.ROWS_3.'" placeholder="'.dol_escape_htmltag($langs->trans("ScanOrTypeOrCopyPasteYourBarCodes")).'"></textarea>';
 
 		/*print '<br>'.$langs->trans("or").'<br>';

+ 119 - 0
htdocs/expedition/ajax/searchfrombarcode.php

@@ -0,0 +1,119 @@
+<?php
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ *	\file       /htdocs/expedition/ajax/searchfrombarcode.php
+ *	\brief      File to make Ajax action on product and stock
+ */
+
+if (!defined('NOTOKENRENEWAL')) {
+	define('NOTOKENRENEWAL', 1); // Disables token renewal
+}
+if (!defined('NOREQUIREMENU')) {
+	define('NOREQUIREMENU', '1');
+}
+if (!defined('NOREQUIREHTML')) {
+	define('NOREQUIREHTML', '1');
+}
+if (!defined('NOREQUIREAJAX')) {
+	define('NOREQUIREAJAX', '1');
+}
+if (!defined('NOREQUIRESOC')) {
+	define('NOREQUIRESOC', '1');
+}
+require '../../main.inc.php';
+include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
+
+$action = GETPOST("action", "alpha");
+$barcode = GETPOST("barcode", "aZ09");
+$response = "";
+
+$fk_entrepot = GETPOST("fk_entrepot", "int");
+$fk_inventory = GETPOST("fk_inventory", "int");
+$fk_product = GETPOST("fk_product", "int");
+$reelqty = GETPOST("reelqty", "int");
+$batch = GETPOST("batch", "int");
+$mode = GETPOST("mode", "aZ");
+
+$warehousefound = 0;
+$warehouseid = 0;
+$objectreturn = array();
+$usesublevelpermission = '';
+
+$object= new Product($db);
+
+// Security check
+if (!empty($user->socid)) {
+	$socid = $user->socid;
+}
+
+$result = restrictedArea($user, $object->module, $object, $object->table_element, $usesublevelpermission, 'fk_soc', 'rowid', 0, 1);	// Call with mode return
+if (!$result) {
+	httponly_accessforbidden('Not allowed by restrictArea (module='.$object->module.' table_element='.$object->table_element.')');
+}
+
+/*
+ * View
+ */
+
+top_httphead('application/json');
+
+if ($action == "existbarcode" && !empty($barcode)) {
+	if (!empty($mode) && $mode == "lotserial") {
+		$sql = "SELECT ps.fk_entrepot, ps.fk_product, p.barcode, ps.reel, pb.batch";
+		$sql .= " FROM ".MAIN_DB_PREFIX."product_batch as pb";
+		$sql .= " JOIN ".MAIN_DB_PREFIX."product_stock as ps ON pb.fk_product_stock = ps.rowid JOIN ".MAIN_DB_PREFIX."product as p ON ps.fk_product = p.rowid";
+		$sql .= " WHERE pb.batch = '".$db->escape($barcode)."'";
+	} else {
+		$sql = "SELECT ps.fk_entrepot, ps.fk_product, p.barcode,ps.reel";
+		$sql .= " FROM ".MAIN_DB_PREFIX."product_stock as ps JOIN ".MAIN_DB_PREFIX."product as p ON ps.fk_product = p.rowid";
+		$sql .= " WHERE p.barcode = '".$db->escape($barcode)."'";
+	}
+	if (!empty($fk_entrepot)) {
+		$sql .= " AND ps.fk_entrepot = '".$db->escape($fk_entrepot)."'";
+	}
+	$result = $db->query($sql);
+	if ($result) {
+		$nbline = $db->num_rows($result);
+		for ($i=0; $i < $nbline; $i++) {
+			$obj = $db->fetch_object($result);
+			if (($mode == "barcode" && $barcode == $obj->barcode) || ($mode == "lotserial" && $barcode == $obj->batch)) {
+				if (!empty($obj->fk_entrepot) && $fk_entrepot == $obj->fk_entrepot) {
+					$warehousefound++;
+					$warehouseid = $obj->fk_entrepot;
+					$fk_product = $obj->fk_product;
+					$reelqty = $obj->reel;
+
+					$objectreturn = array('fk_warehouse'=>$warehouseid,'fk_product'=>$fk_product,'reelqty'=>$reelqty);
+				}
+			}
+		}
+		if ($warehousefound < 1) {
+			$response = array('status'=>'error','errorcode'=>'NotFound','message'=>'No warehouse found for barcode'.$barcode);
+		} elseif ($warehousefound > 1) {
+			$response = array('status'=>'error','errorcode'=>'TooManyWarehouse','message'=>'Too many warehouse found');
+		} else {
+			$response = array('status'=>'success','message'=>'Warehouse found','object'=>$objectreturn);
+		}
+	} else {
+		$response = array('status'=>'error','errorcode'=>'NotFound','message'=>"No results found for barcode");
+	}
+} else {
+	$response = array('status'=>'error','errorcode'=>'ActionError','message'=>"Error on action");
+}
+
+$response = json_encode($response);
+echo $response;

+ 242 - 3
htdocs/expedition/dispatch.php

@@ -515,6 +515,9 @@ if ($object->id > 0 || !empty($object->ref)) {
 	print '</tr></table>';
 
 	print '<br><center>';
+	if (isModEnabled('barcode') || isModEnabled('productbatch')) {
+		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>';
+	}
 	print '<a href="#" id="resetalltoexpected" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto("", 'autofill', 'class="pictofixedwidth"').$langs->trans("RestoreWithCurrentQtySaved").'</a></td>';
 	// Link to clear qty
 	print '<a href="#" id="autoreset" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto("", 'eraser', 'class="pictofixedwidth"').$langs->trans("ClearQtys").'</a></td>';
@@ -564,7 +567,7 @@ if ($object->id > 0 || !empty($object->ref)) {
 
 		//$sql = "SELECT l.rowid, l.fk_product, l.subprice, l.remise_percent, l.ref AS sref, SUM(l.qty) as qty,";
 		$sql = "SELECT l.rowid, l.fk_product, l.subprice, l.remise_percent, '' AS sref, l.qty as qty,";
-		$sql .= " p.ref, p.label, p.tobatch, p.fk_default_warehouse";
+		$sql .= " p.ref, p.label, p.tobatch, p.fk_default_warehouse, p.barcode";
 		// Enable hooks to alter the SQL query (SELECT)
 		$parameters = array();
 		$reshook = $hookmanager->executeHooks(
@@ -716,7 +719,7 @@ if ($object->id > 0 || !empty($object->ref)) {
 						if (isModEnabled('productbatch')) {
 							if ($objp->tobatch) {
 								// Product
-								print '<td>';
+								print '<td id="product_'.$i.'" data-idproduct="'.$objp->fk_product.'" data-barcode="'.$objp->barcode.'">';
 								print $linktoprod;
 								print "</td>";
 								print '<td class="dispatch_batch_number"></td>';
@@ -728,7 +731,7 @@ if ($object->id > 0 || !empty($object->ref)) {
 								}
 							} else {
 								// Product
-								print '<td>';
+								print '<td id="product_'.$i.'" data-idproduct="'.$objp->fk_product.'" data-barcode="'.$objp->barcode.'">';
 								print $linktoprod;
 								print "</td>";
 								print '<td class="dispatch_batch_number">';
@@ -1142,6 +1145,242 @@ if ($object->id > 0 || !empty($object->ref)) {
 
 	print dol_get_fiche_end();
 
+	// Popup for mass barcode scanning
+	if ($action == 'updatebyscaning') {
+		if ($permissiontoadd) {
+			// Output the javascript to manage the scanner tool.
+			print '<script>';
+
+			print '
+			var duplicatedbatchcode = [];
+			var errortab1 = [];
+			var errortab2 = [];
+			var errortab3 = [];
+			var errortab4 = [];
+
+			function barcodescannerjs(){
+				console.log("We catch inputs in scanner box");
+				jQuery("#scantoolmessage").text();
+
+				var selectaddorreplace = $("select[name=selectaddorreplace]").val();
+				var barcodemode = $("input[name=barcodemode]:checked").val();
+				var barcodeproductqty = $("input[name=barcodeproductqty]").val();
+				var warehousetouse = $("select[name=warehousenew]").val();
+				var textarea = $("textarea[name=barcodelist]").val();
+				var textarray = textarea.split(/[\s,;]+/);
+				var tabproduct = [];
+				duplicatedbatchcode = [];
+				errortab1 = [];
+				errortab2 = [];
+				errortab3 = [];
+				errortab4 = [];
+
+				textarray = textarray.filter(function(value){
+					return value != "";
+				});
+				if(textarray.some((element) => element != "")){
+					$(".qtydispatchinput").each(function(){
+						id = $(this).attr(\'id\');
+						idarray = id.split(\'_\');
+						idproduct = idarray[2];
+						id = idarray[1] + \'_\' + idarray[2];
+						console.log("Analyze the line "+id+" in inventory, barcodemode="+barcodemode);
+						warehouse = $("#entrepot_"+id).val();
+						console.log(warehouse);
+						productbarcode = $("#product_"+idproduct).attr(\'data-barcode\');
+						console.log(productbarcode);
+						productbatchcode = $("#lot_number_"+id).val();
+						if(productbatchcode == undefined){
+							productbatchcode = "";
+						}
+						console.log(productbatchcode);
+
+						if (barcodemode != "barcodeforproduct") {
+							tabproduct.forEach(product=>{
+								console.log("product.Batch="+product.Batch+" productbatchcode="+productbatchcode);
+								if(product.Batch != "" && product.Batch == productbatchcode){
+									console.log("duplicate batch code found for batch code "+productbatchcode);
+									duplicatedbatchcode.push(productbatchcode);
+								}
+							})
+						}
+						productinput = $("#qty_"+id).val();
+						if(productinput == ""){
+							productinput = 0
+						}
+						tabproduct.push({\'Id\':id,\'Warehouse\':warehouse,\'Barcode\':productbarcode,\'Batch\':productbatchcode,\'Qty\':productinput,\'fetched\':false});
+					});
+					console.log("Loop on each record entered in the textarea");
+
+					textarray.forEach(function(element,index){
+						console.log("Process record element="+element+" id="+id);
+						var verify_batch = false;
+						var verify_barcode = false;
+						switch(barcodemode){
+							case "barcodeforautodetect":
+								verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,"barcode",true);
+								verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,"lotserial",true);
+								break;
+							case "barcodeforproduct":
+								verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,"barcode");
+								break;
+							case "barcodeforlotserial":
+								verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,"lotserial");
+								break;
+							default:
+								alert(\''.dol_escape_js($langs->trans("ErrorWrongBarcodemode")).' "\'+barcodemode+\'"\');
+								throw \''.dol_escape_js($langs->trans('ErrorWrongBarcodemode')).' "\'+barcodemode+\'"\';
+						}
+
+						if (verify_batch == false && verify_barcode == false) {		/* If the 2 flags are false, not found error */
+							errortab2.push(element);
+						} else if (verify_batch == true && verify_barcode == true) {		/* If the 2 flags are true, error: we don t know which one to take */
+							errortab3.push(element);
+						} else if (verify_batch == true) {
+							console.log("element="+element);
+							console.log(duplicatedbatchcode);
+							if (duplicatedbatchcode.includes(element)) {
+								errortab1.push(element);
+							}
+						}
+					});
+
+					if (Object.keys(errortab1).length < 1 && Object.keys(errortab2).length < 1 && Object.keys(errortab3).length < 1) {
+						tabproduct.forEach(product => {
+							if(product.Qty!=0){
+								if(product.hasOwnProperty("reelqty")){
+									idprod = $("td[data-idproduct=\'"+product.fk_product+"\']").attr("id");
+									idproduct = idprod.split("_")[1];
+									console.log("We create a new line for product_"+idproduct);
+									if(product.Barcode != null){
+										modedispatch = "dispatch";
+									} else {
+										modedispatch = "batch";
+									}
+									addDispatchLine(idproduct,modedispatch);
+									console.log($("tr[name^=\'"+modedispatch+"_\'][name$=\'_"+idproduct+"\']"));
+									nbrTrs = $("tr[name^=\'"+modedispatch+"_\'][name$=\'_"+idproduct+"\']").length;
+
+									$("#qty_"+(nbrTrs-1)+"_"+idproduct).val(product.Qty);
+									$("#entrepot_"+(nbrTrs-1)+"_"+idproduct).val(product.Warehouse);
+									
+									if(modedispatch == "batch"){
+										$("#lot_number_"+(nbrTrs-1)+"_"+idproduct).val(product.Batch);
+									}
+
+								} else {
+									console.log("We change #qty_"+product.Id +" to match input in scanner box");
+									$("#qty_"+product.Id).val(product.Qty);
+								}
+							}
+						});
+						jQuery("#scantoolmessage").text("'.dol_escape_js($langs->transnoentities("QtyWasAddedToTheScannedBarcode")).'\n");
+						/* document.forms["formrecord"].submit(); */
+					} else {
+						let stringerror = "";
+						if (Object.keys(errortab1).length > 0) {
+							stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorSameBatchNumber')).': ";
+							errortab1.forEach(element => {
+								stringerror += (element + ", ")
+							});
+							stringerror = stringerror.slice(0, -2);	/* Remove last ", " */
+						}
+						if (Object.keys(errortab2).length > 0) {
+							stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorCantFindCodeInInventory')).': ";
+							errortab2.forEach(element => {
+								stringerror += (element + ", ")
+							});
+							stringerror = stringerror.slice(0, -2);	/* Remove last ", " */
+						}
+						if (Object.keys(errortab3).length > 0) {
+							stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorCodeScannedIsBothProductAndSerial')).': ";
+							errortab3.forEach(element => {
+								stringerror += (element + ", ")
+							});
+							stringerror = stringerror.slice(0, -2);	/* Remove last ", " */
+						}
+						if (Object.keys(errortab4).length > 0) {
+							stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorBarcodeNotFoundForProductWarehouse')).': ";
+							errortab4.forEach(element => {
+								stringerror += (element + ", ")
+							});
+							stringerror = stringerror.slice(0, -2);	/* Remove last ", " */
+						}
+
+						jQuery("#scantoolmessage").html(\''.dol_escape_js($langs->transnoentities("ErrorOnElementsInventory")).'\' + stringerror);
+						//alert("'.dol_escape_js($langs->trans("ErrorOnElementsInventory")).' :\n" + stringerror);
+					}
+				}
+
+			}
+
+			/* This methode is called by parent barcodescannerjs() */
+			function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,mode,autodetect=false){
+				BarcodeIsInProduct=0;
+				newproductrow=0
+				result=false;
+				tabproduct.forEach(product => {
+					$.ajax({ url: \''.DOL_URL_ROOT.'/expedition/ajax/searchfrombarcode.php\',
+						data: { "token":"'.newToken().'", "action":"existbarcode","fk_entrepot": warehousetouse, "barcode":element, "mode":mode},
+						type: \'POST\',
+						async: false,
+						success: function(response) {
+							if (response.status == "success"){
+								console.log(response.message);
+								if(!newproductrow){
+									newproductrow = response.object;
+								}
+							}else{
+								if (mode!="lotserial" && autodetect==false && !errortab4.includes(element)){
+									errortab4.push(element);
+									console.error(response.message);
+								}
+							}
+						},
+						error : function(output) {
+						console.error("Error on barcodeserialforproduct function");
+						},
+					});
+					console.log("Product "+(index+=1)+": "+element);
+					if(mode == "barcode"){
+						testonproduct = product.Barcode
+					}else if (mode == "lotserial"){
+						testonproduct = product.Batch
+					}
+					testonwarehouse = product.Warehouse;
+					if(testonproduct == element && testonwarehouse == warehousetouse){
+						if(selectaddorreplace == "add"){
+							productqty = parseInt(product.Qty,10);
+							product.Qty = productqty + parseInt(barcodeproductqty,10);
+						}else if(selectaddorreplace == "replace"){
+							if(product.fetched == false){
+								product.Qty = barcodeproductqty
+								product.fetched=true
+							}else{
+								productqty = parseInt(product.Qty,10);
+								product.Qty = productqty + parseInt(barcodeproductqty,10);
+							}
+						}
+						BarcodeIsInProduct+=1;
+					}
+				})
+				if(BarcodeIsInProduct==0 && newproductrow!=0){
+					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});
+					result = true;
+				}
+				if(BarcodeIsInProduct > 0){
+					result = true;
+				}
+				return result;
+			}
+		';
+			print '</script>';
+		}
+		include DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
+		$formother = new FormOther($db);
+		print $formother->getHTMLScannerForm("barcodescannerjs", 'all', 1);
+	}
+
 	// traitement entrepot par défaut
 	print '<script type="text/javascript">
 		$(document).ready(function () {

+ 1 - 1
htdocs/expedition/js/lib_dispatch.js.php

@@ -98,7 +98,7 @@ function addDispatchLine(index, type, mode) {
 	}
 	console.log("expedition/js/lib_dispatch.js.php qtyDispatched=" + qtyDispatched + " qtyOrdered=" + qtyOrdered+ " qty=" + qty);
 
-	if (qty <= 1) {
+	if ((mode === 'lessone' && qty <= 1) || (qtyOrdered - qtyDispatched < 1)) {
 		window.alert("Remain quantity to dispatch is too low to be split");
 	} else {
 		var oldlineqty = qtyDispatched;