Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW : allow stock management by product #29056

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion htdocs/core/lib/product.lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ function product_prepare_head($object)
$h++;
}

if ($object->isProduct() || ($object->isService() && getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) { // If physical product we can stock (or service with option)
if (($object->isProduct() || ($object->isService() && getDolGlobalString('STOCK_SUPPORTS_SERVICES'))) && $object->stockable_product == Product::ENABLED_STOCK) { // If physical product we can stock (or service with option)
if (isModEnabled('stock') && $user->hasRight('stock', 'lire')) {
$head[$h][0] = DOL_URL_ROOT."/product/stock/product.php?id=".$object->id;
$head[$h][1] = $langs->trans("Stock");
Expand Down
58 changes: 49 additions & 9 deletions htdocs/expedition/card.php
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@
$subtotalqty = 0;

$j = 0;

$batch = "batchl".$i."_0";
$stockLocation = "ent1".$i."_0";
$qty = "qtyl".$i;
Expand Down Expand Up @@ -342,6 +343,30 @@
$qty = "qtyl".$i.'_'.$j;
}
} else {
$p = new Product($db);
$res = $p->fetch($objectsrc->lines[$i]->fk_product);
if ($res > 0) {
if (GETPOST('entrepot_id', 'int') == -1) {
$qty .= '_'.$j;
}

if ($p->stockable_product == Product::DISABLED_STOCK) {
$w = new Entrepot($db);
$Tw = $w->list_array();
if (count($Tw) > 0) {
$w_Id = array_keys($Tw);
$stockLine[$i][$j]['qty'] = GETPOST($qty, 'int');

// lorsque que l'on a le stock désactivé sur un produit/service
// on force l'entrepot pour passer le test d'ajout de ligne dans expedition.class.php
//
$stockLine[$i][$j]['warehouse_id'] = $w_Id[0];
$stockLine[$i][$j]['ix_l'] = GETPOST($idl, 'int');
} else {
setEventMessage($langs->trans('NoWarehouseInBase'));
}
}
}
//shipment line for product with no batch management and no multiple stock location
if (GETPOSTINT($qty) > 0) {
$totalqty += price2num(GETPOST($qty, 'alpha'), 'MS');
Expand Down Expand Up @@ -1234,7 +1259,7 @@
$text = $product_static->getNomUrl(1);
$text .= ' - '.(!empty($line->label) ? $line->label : $line->product_label);
$description = ($showdescinproductdesc ? '' : dol_htmlentitiesbr($line->desc));

$description .= empty($product->stockable_product) ? $langs->trans('StockDisabled') : $langs->trans('StockEnabled');
print $form->textwithtooltip($text, $description, 3, '', '', $i);

// Show range
Expand Down Expand Up @@ -1344,8 +1369,11 @@
if (!getDolGlobalInt('STOCK_ALLOW_NEGATIVE_TRANSFER')) {
$stockMin = 0;
}
print $formproduct->selectWarehouses($tmpentrepot_id, 'entl'.$indiceAsked, '', 1, 0, $line->fk_product, '', 1, 0, array(), 'minwidth200', '', 1, $stockMin, 'stock DESC, e.ref');

if ($product->stockable_product == Product::ENABLED_STOCK) {
print $formproduct->selectWarehouses($tmpentrepot_id, 'entl'.$indiceAsked, '', 1, 0, $line->fk_product, '', 1, 0, array(), 'minwidth200', '', 1, $stockMin, 'stock DESC, e.ref');
} else {
print img_warning().' '.$langs->trans('StockDisabled');
}
if ($tmpentrepot_id > 0 && $tmpentrepot_id == $warehouse_id) {
//print $stock.' '.$quantityToBeDelivered;
if ($stock < $quantityToBeDelivered) {
Expand Down Expand Up @@ -1576,10 +1604,13 @@
if (isModEnabled('stock')) {
print '<td class="left">';
if ($line->product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
print $tmpwarehouseObject->getNomUrl(0).' ';

print '<!-- Show details of stock -->';
print '('.$stock.')';
if ($product->stockable_product == Product::ENABLED_STOCK) {
print $tmpwarehouseObject->getNomUrl(0).' ';
print '<!-- Show details of stock -->';
print '('.$stock.')';
} else {
print img_warning().' '.$langs->trans('StockDisabled');
}
} else {
print '<span class="opacitymedium">('.$langs->trans("Service").')</span>';
}
Expand Down Expand Up @@ -1739,6 +1770,10 @@
if ($warehouse_selected_id <= 0) { // We did not force a given warehouse, so we won't have no warehouse to change qty.
$disabled = 'disabled="disabled"';
}
// finally we overwrite the input with the product status stockable_product if it's disabled
if ($product->stockable_product == Product::DISABLED_STOCK) {
$disabled = '';
}
print '<input class="qtyl right" name="qtyl'.$indiceAsked.'_'.$subj.'" id="qtyl'.$indiceAsked.'_'.$subj.'" type="text" size="4" value="0"'.($disabled ? ' '.$disabled : '').'> ';
if (empty($disabled) && getDolGlobalString('STOCK_ALLOW_NEGATIVE_TRANSFER')) {
print '<input name="ent1' . $indiceAsked . '_' . $subj . '" type="hidden" value="' . $warehouse_selected_id . '">';
Expand All @@ -1756,7 +1791,11 @@
print img_warning().' '.$langs->trans("NoProductToShipFoundIntoStock", $warehouseObject->label);
} else {
if ($line->fk_product) {
print img_warning().' '.$langs->trans("StockTooLow");
if ($product->stockable_product == Product::ENABLED_STOCK) {
print img_warning().' '.$langs->trans('StockTooLow');
} else {
print img_warning().' '.$langs->trans('StockDisabled');
}
} else {
print '';
}
Expand Down Expand Up @@ -2364,6 +2403,7 @@
$product_static->surface_units = $lines[$i]->surface_units;
$product_static->volume = $lines[$i]->volume;
$product_static->volume_units = $lines[$i]->volume_units;
$product_static->stockable_product = $lines[$i]->stockable_product;

$text = $product_static->getNomUrl(1);
$text .= ' - '.$label;
Expand Down Expand Up @@ -2534,7 +2574,7 @@
print '<td class="linecolwarehousesource tdoverflowmax200">';
if ($lines[$i]->product_type == Product::TYPE_SERVICE && getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) {
print '<span class="opacitymedium">('.$langs->trans("Service").')</span>';
} elseif ($lines[$i]->entrepot_id > 0) {
} elseif ($lines[$i]->entrepot_id > 0 && $lines[$i]->stockable_product == Product::ENABLED_STOCK) {
$entrepot = new Entrepot($db);
$entrepot->fetch($lines[$i]->entrepot_id);
print $entrepot->getNomUrl(1);
Expand Down
19 changes: 15 additions & 4 deletions htdocs/expedition/class/expedition.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ public function addline($entrepot_id, $id, $qty, $array_options = [])
$isavirtualproduct = ($product->hasFatherOrChild(1) > 0);
// The product is qualified for a check of quantity (must be enough in stock to be added into shipment).
if (!$isavirtualproduct || !getDolGlobalString('PRODUIT_SOUSPRODUITS') || ($isavirtualproduct && !getDolGlobalString('STOCK_EXCLUDE_VIRTUAL_PRODUCTS'))) { // If STOCK_EXCLUDE_VIRTUAL_PRODUCTS is set, we do not manage stock for kits/virtual products.
if ($product_stock < $qty) {
if ($product_stock < $qty && $product->stockable_product == Product::ENABLED_STOCK) {
$langs->load("errors");
$this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnShipment', $product->ref);
$this->errorhidden = 'ErrorStockIsNotEnoughToAddProductOnShipment';
Expand Down Expand Up @@ -1632,7 +1632,9 @@ public function fetch_lines()
$sql .= ", cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc, cd.rang, cd.date_start, cd.date_end";
$sql .= ", ed.rowid as line_id, ed.qty as qty_shipped, ed.fk_element, ed.fk_elementdet, ed.element_type, ed.fk_entrepot";
$sql .= ", p.ref as product_ref, p.label as product_label, p.fk_product_type, p.barcode as product_barcode";
$sql .= ", p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.surface, p.surface_units, p.volume, p.volume_units, p.tosell as product_tosell, p.tobuy as product_tobuy, p.tobatch as product_tobatch";
$sql .= ", p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units";
$sql .= ", p.surface, p.surface_units, p.volume, p.volume_units, p.tosell as product_tosell, p.tobuy as product_tobuy";
$sql .= ", p.tobatch as product_tobatch, p.stockable_product";
$sql .= " FROM ".MAIN_DB_PREFIX."expeditiondet as ed, ".MAIN_DB_PREFIX."commandedet as cd";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
$sql .= " WHERE ed.fk_expedition = ".((int) $this->id);
Expand Down Expand Up @@ -1695,6 +1697,7 @@ public function fetch_lines()

$line->fk_expedition = $this->id; // id of parent

$line->stockable_product = $obj->stockable_product;
$line->product_type = $obj->product_type;
$line->fk_product = $obj->fk_product;
$line->fk_product_type = $obj->fk_product_type;
Expand Down Expand Up @@ -1722,8 +1725,9 @@ public function fetch_lines()
$line->surface = $obj->surface;
$line->surface_units = $obj->surface_units;
$line->volume = $obj->volume;
$line->volume_units = $obj->volume_units;
$line->fk_unit = $obj->fk_unit;
$line->volume_units = $obj->volume_units;
$line->stockable_product = $obj->stockable_product;
$line->fk_unit = $obj->fk_unit;

$line->pa_ht = $obj->pa_ht;

Expand Down Expand Up @@ -2825,6 +2829,13 @@ class ExpeditionLigne extends CommonObjectLine
public $volume;
public $volume_units;

/**
* 0=This service or product is not managed in stock, 1=This service or product is managed in stock
*
* @var boolean
*/
public $stockable_product = true;

// Invoicing
public $remise_percent;
public $tva_tx;
Expand Down
76 changes: 41 additions & 35 deletions htdocs/expedition/dispatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@

//$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, p.barcode";
$sql .= " p.ref, p.label, p.tobatch, p.fk_default_warehouse, p.barcode, p.stockable_product";
// Enable hooks to alter the SQL query (SELECT)
$parameters = array();
$reshook = $hookmanager->executeHooks(
Expand Down Expand Up @@ -901,13 +901,19 @@

// Warehouse
print '<td class="right">';
if (count($listwarehouses) > 1) {
print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_entrepot, "entrepot".$suffix, '', 1, 0, $objp->fk_product, '', 1, 0, null, 'csswarehouse'.$suffix);
} elseif (count($listwarehouses) == 1) {
print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_entrepot, "entrepot".$suffix, '', 0, 0, $objp->fk_product, '', 1, 0, null, 'csswarehouse'.$suffix);
if ($objp->stockable_product == Product::ENABLED_STOCK) {
if (count($listwarehouses) > 1) {
print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_entrepot, "entrepot".$suffix, '', 1, 0, $objp->fk_product, '', 1, 0, null, 'csswarehouse'.$suffix);
} elseif (count($listwarehouses) == 1) {
print $formproduct->selectWarehouses(GETPOST("entrepot".$suffix) ? GETPOST("entrepot".$suffix) : $objd->fk_entrepot, "entrepot".$suffix, '', 0, 0, $objp->fk_product, '', 1, 0, null, 'csswarehouse'.$suffix);
} else {
$langs->load("errors");
print $langs->trans("ErrorNoWarehouseDefined");
}
} else {
$langs->load("errors");
print $langs->trans("ErrorNoWarehouseDefined");
// on force l'entrepot pour passer le test d'ajout de ligne dans expedition.class.php
print '<input id="entrepot'.$suffix.'" name="entrepot'.$suffix.'" type="hidden" value="'.$objd->fk_entrepot.'">';
print img_warning().' '.$langs->trans('StockDisabled');
}
print "</td>\n";

Expand Down Expand Up @@ -1160,7 +1166,7 @@
var errortab3 = [];
var errortab4 = [];

function barcodescannerjs(){
function barcodescannerjs() {
console.log("We catch inputs in scanner box");
jQuery("#scantoolmessage").text();

Expand All @@ -1177,11 +1183,11 @@ function barcodescannerjs(){
errortab3 = [];
errortab4 = [];

textarray = textarray.filter(function(value){
textarray = textarray.filter(function(value) {
return value != "";
});
if(textarray.some((element) => element != "")){
$(".qtydispatchinput").each(function(){
if (textarray.some((element) => element != "")) {
$(".qtydispatchinput").each(function() {
id = $(this).attr(\'id\');
idarray = id.split(\'_\');
idproduct = idarray[2];
Expand All @@ -1192,33 +1198,33 @@ function barcodescannerjs(){
productbarcode = $("#product_"+idproduct).attr(\'data-barcode\');
console.log(productbarcode);
productbatchcode = $("#lot_number_"+id).val();
if(productbatchcode == undefined){
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){
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 == ""){
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){
textarray.forEach(function(element,index) {
console.log("Process record element="+element+" id="+id);
var verify_batch = false;
var verify_barcode = false;
switch(barcodemode){
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);
Expand Down Expand Up @@ -1249,12 +1255,12 @@ function barcodescannerjs(){

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")){
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){
if (product.Barcode != null) {
modedispatch = "dispatch";
} else {
modedispatch = "batch";
Expand All @@ -1266,7 +1272,7 @@ function barcodescannerjs(){
$("#qty_"+(nbrTrs-1)+"_"+idproduct).val(product.Qty);
$("#entrepot_"+(nbrTrs-1)+"_"+idproduct).val(product.Warehouse);

if(modedispatch == "batch"){
if (modedispatch == "batch") {
$("#lot_number_"+(nbrTrs-1)+"_"+idproduct).val(product.Batch);
}

Expand Down Expand Up @@ -1317,7 +1323,7 @@ function barcodescannerjs(){
}

/* This methode is called by parent barcodescannerjs() */
function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,mode,autodetect=false){
function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,warehousetouse,selectaddorreplace,mode,autodetect=false) {
BarcodeIsInProduct=0;
newproductrow=0
result=false;
Expand All @@ -1327,13 +1333,13 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,ware
type: \'POST\',
async: false,
success: function(response) {
if (response.status == "success"){
if (response.status == "success") {
console.log(response.message);
if(!newproductrow){
if (!newproductrow) {
newproductrow = response.object;
}
}else{
if (mode!="lotserial" && autodetect==false && !errortab4.includes(element)){
if (mode!="lotserial" && autodetect==false && !errortab4.includes(element)) {
errortab4.push(element);
console.error(response.message);
}
Expand All @@ -1344,18 +1350,18 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,ware
},
});
console.log("Product "+(index+=1)+": "+element);
if(mode == "barcode"){
if (mode == "barcode") {
testonproduct = product.Barcode
}else if (mode == "lotserial"){
}else if (mode == "lotserial") {
testonproduct = product.Batch
}
testonwarehouse = product.Warehouse;
if(testonproduct == element && testonwarehouse == warehousetouse){
if(selectaddorreplace == "add"){
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){
}else if (selectaddorreplace == "replace") {
if (product.fetched == false) {
product.Qty = barcodeproductqty
product.fetched=true
}else{
Expand All @@ -1366,11 +1372,11 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,ware
BarcodeIsInProduct+=1;
}
})
if(BarcodeIsInProduct==0 && newproductrow!=0){
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){
if (BarcodeIsInProduct > 0) {
result = true;
}
return result;
Expand All @@ -1394,7 +1400,7 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,ware

$("#autoreset").click(function() {
console.log("we click on autoreset");
$(".autoresettr").each(function(){
$(".autoresettr").each(function() {
id = $(this).attr("name");
idtab = id.split("_");
console.log("we process line "+id+" "+idtab);
Expand All @@ -1417,8 +1423,8 @@ function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,ware
return false;
});

$("#resetalltoexpected").click(function(){
$(".qtydispatchinput").each(function(){
$("#resetalltoexpected").click(function() {
$(".qtydispatchinput").each(function() {
console.log("We reset to expected "+$(this).attr("id")+" qty to dispatch");
$(this).val($(this).data("expected"));
});
Expand Down
4 changes: 4 additions & 0 deletions htdocs/langs/en_US/products.lang
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,7 @@ AllowStockMovementVariantParentHelp=By default, a parent of a variant is a virtu
ConfirmSetToDraftInventory=Are you sure you want to go back to Draft status?<br>The quantities currently set in the inventory will be reset.
WarningLineProductNotToSell=Product or service "%s" is not to sell and was cloned
PriceLabel=Price label
StockableProduct=Stock management
StockableProductDescription=If this option is enabled, the stock modification for this element is retained. If disabled, the stock modification for this element is not retained.
StockDisabled=Stock disabled
StockEnabled=Stock enabled
Loading
Loading