diff --git a/htdocs/core/lib/product.lib.php b/htdocs/core/lib/product.lib.php index a809f43841955..34c4f4ecd9f12 100644 --- a/htdocs/core/lib/product.lib.php +++ b/htdocs/core/lib/product.lib.php @@ -131,7 +131,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"); diff --git a/htdocs/expedition/card.php b/htdocs/expedition/card.php index 469c5ab19f584..1d294ea56cba8 100644 --- a/htdocs/expedition/card.php +++ b/htdocs/expedition/card.php @@ -284,6 +284,7 @@ $subtotalqty = 0; $j = 0; + $batch = "batchl".$i."_0"; $stockLocation = "ent1".$i."_0"; $qty = "qtyl".$i; @@ -362,6 +363,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'); @@ -1248,7 +1273,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, 0, '', $i); // Show range @@ -1358,7 +1383,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', array(), 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', array(), 1, $stockMin, 'stock DESC, e.ref'); + } else { + print img_warning().' '.$langs->trans('StockDisabled'); + } if ($tmpentrepot_id > 0 && $tmpentrepot_id == $warehouse_id) { //print $stock.' '.$quantityToBeDelivered; @@ -1592,10 +1621,13 @@ if (isModEnabled('stock')) { print ''; if ($line->product_type == Product::TYPE_PRODUCT || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) { - print $tmpwarehouseObject->getNomUrl(0).' '; - - print ''; - print '('.$stock.')'; + if ($product->stockable_product == Product::ENABLED_STOCK) { + print $tmpwarehouseObject->getNomUrl(0).' '; + print ''; + print '('.$stock.')'; + } else { + print img_warning().' '.$langs->trans('StockDisabled'); + } } else { print '('.$langs->trans("Service").')'; } @@ -1755,6 +1787,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 ' '; if (empty($disabled) && getDolGlobalString('STOCK_ALLOW_NEGATIVE_TRANSFER')) { print ''; @@ -1784,7 +1820,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 ''; } @@ -2393,6 +2433,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; @@ -2563,7 +2604,7 @@ print ''; if ($lines[$i]->product_type == Product::TYPE_SERVICE && getDolGlobalString('SHIPMENT_SUPPORTS_SERVICES')) { print '('.$langs->trans("Service").')'; - } 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); diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index 0ac90b12afbd8..cb2411e7192a8 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -1031,7 +1031,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'; @@ -1695,7 +1695,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); @@ -1758,6 +1760,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; @@ -1785,8 +1788,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; diff --git a/htdocs/expedition/class/expeditionligne.class.php b/htdocs/expedition/class/expeditionligne.class.php index 888c953f7a318..58d45ab5dd72a 100644 --- a/htdocs/expedition/class/expeditionligne.class.php +++ b/htdocs/expedition/class/expeditionligne.class.php @@ -262,6 +262,13 @@ class ExpeditionLigne extends CommonObjectLine */ 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; + /** * @var float|string */ diff --git a/htdocs/expedition/dispatch.php b/htdocs/expedition/dispatch.php index f29fd4d2c8650..729f5429284d4 100644 --- a/htdocs/expedition/dispatch.php +++ b/htdocs/expedition/dispatch.php @@ -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( @@ -901,13 +901,19 @@ // Warehouse print ''; - 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 ''; + print img_warning().' '.$langs->trans('StockDisabled'); } print "\n"; @@ -1160,7 +1166,7 @@ var errortab3 = []; var errortab4 = []; - function barcodescannerjs(){ + function barcodescannerjs() { console.log("We catch inputs in scanner box"); jQuery("#scantoolmessage").text(); @@ -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]; @@ -1192,7 +1198,7 @@ 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); @@ -1200,25 +1206,25 @@ function barcodescannerjs(){ 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); @@ -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"; @@ -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); } @@ -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; @@ -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); } @@ -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{ @@ -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; @@ -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); @@ -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")); }); diff --git a/htdocs/langs/en_US/products.lang b/htdocs/langs/en_US/products.lang index 0b8825cf058b3..f21d8fb99cf3a 100644 --- a/htdocs/langs/en_US/products.lang +++ b/htdocs/langs/en_US/products.lang @@ -437,4 +437,8 @@ AllowStockMovementVariantParentHelp=By default, a parent of a variant is a virtu ConfirmSetToDraftInventory=Are you sure you want to go back to Draft status?
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 PriceByCustomeAndMultiPricesAbility=Different prices for each customer + Multiple price segments per product/service (each customer is in one price segment) diff --git a/htdocs/langs/en_US/sendings.lang b/htdocs/langs/en_US/sendings.lang index 9f648d5f706ff..73502ff2eb745 100644 --- a/htdocs/langs/en_US/sendings.lang +++ b/htdocs/langs/en_US/sendings.lang @@ -66,6 +66,7 @@ NoLineGoOnTabToAddSome=No line, go on tab "%s" to add CreateInvoiceForThisCustomerFromSendings=Create Bills IfValidateInvoiceIsNoSendingStayUnbilled=If invoice validation is 'No', the sending will remain to status 'Unbilled' until the invoice is validated. OptionToSetSendingBilledNotEnabled=Option from module Workflow, to set sending to 'Billed' automatically when invoice is validated, is not enabled, so you will have to set the status of sendings to 'Billed' manually after the invoice has been generated. +NoWarehouseInBase=No warehouse in base # Sending methods # ModelDocument diff --git a/htdocs/product/card.php b/htdocs/product/card.php index d2e547e5e6549..1274d83543d98 100644 --- a/htdocs/product/card.php +++ b/htdocs/product/card.php @@ -633,6 +633,9 @@ $object->fk_unit = null; } + // managed_in_stock + $object->stockable_product = ($type == 0 || ($type == 1 && !empty($conf->global->STOCK_SUPPORTS_SERVICES))) ? 1 : 0; + $accountancy_code_sell = GETPOST('accountancy_code_sell', 'alpha'); $accountancy_code_sell_intra = GETPOST('accountancy_code_sell_intra', 'alpha'); $accountancy_code_sell_export = GETPOST('accountancy_code_sell_export', 'alpha'); @@ -813,6 +816,9 @@ $object->fk_default_bom = 0; } + // managed_in_stock + $object->stockable_product = GETPOSTISSET('stockable_product'); + $units = GETPOSTINT('units'); if ($units > 0) { $object->fk_unit = $units; @@ -2037,7 +2043,7 @@ $(document).ready(function() { console.log($("#statusBatchWarning")) $("#status_batch").on("change", function() { - if ($("#status_batch")[0].value == 0){ + if ($("#status_batch")[0].value == 0) { $("#statusBatchMouvToGlobal").show() } else { $("#statusBatchMouvToGlobal").hide() @@ -2051,7 +2057,7 @@ $(document).ready(function() { console.log($("#statusBatchWarning")) $("#status_batch").on("change", function() { - if ($("#status_batch")[0].value == 2){ + if ($("#status_batch")[0].value == 2) { $("#statusBatchWarning").show() } else { $("#statusBatchWarning").hide() @@ -2207,6 +2213,10 @@ print ''; print ''; */ + + print '' . $langs->trans("StockableProduct") . ''; + $checked = $object->stockable_product == 1 ? "checked" : ""; + print ''; } if ($object->isService() && isModEnabled('workstation')) { @@ -2242,6 +2252,12 @@ print ''; print ''; + + if (!empty($conf->stock->enabled) && !empty($conf->global->STOCK_SUPPORTS_SERVICES)) { + print '' . $langs->trans("StockableProduct") . ''; + $checked = $object->stockable_product == 1 ? "checked" : ""; + print ''; + } } else { if (!getDolGlobalString('PRODUCT_DISABLE_NATURE')) { // Nature @@ -2725,6 +2741,12 @@ print ''; } + // View stockable_product + if (($object->isProduct() || ($object->isService() && !empty($conf->global->STOCK_SUPPORTS_SERVICES))) && !empty($conf->stock->enabled)) { + print '' . $form->textwithpicto($langs->trans("StockableProduct"), $langs->trans('StockableProductDescription')) . ''; + print 'stockable_product == 1 ? 'checked' : '').'>'; + } + // Parent product. if (isModEnabled('variants') && ($object->isProduct() || $object->isService())) { $combination = new ProductCombination($db); diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index ab0f8a97c400a..984e405725a5c 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -769,6 +769,12 @@ class Product extends CommonObject */ public $mandatory_period; + /** + * 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; /** * 'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password') @@ -822,6 +828,7 @@ class Product extends CommonObject //'tosell' =>array('type'=>'integer', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>'0', 'index'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Active', -1=>'Cancel')), //'tobuy' =>array('type'=>'integer', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>'0', 'index'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Active', -1=>'Cancel')), 'mandatory_period' => array('type' => 'integer', 'label' => 'mandatoryperiod', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'default' => '0', 'index' => 1, 'position' => 1000), + 'stockable_product' =>array('type' => 'integer', 'label' => 'stockable_product', 'enabled' => 1, 'visible' => 1, 'default' => 1, 'notnull' => 1, 'index' => 1, 'position' => 502), ); /** @@ -833,6 +840,13 @@ class Product extends CommonObject */ const TYPE_SERVICE = 1; + /** + * Stockable product + */ + const NOT_MANAGED_IN_STOCK = 0; + const DISABLED_STOCK = 0; + const ENABLED_STOCK = 1; + /** * Constructor * @@ -938,6 +952,9 @@ public function create($user, $notrigger = 0) if (empty($this->status_buy)) { $this->status_buy = 0; } + if (empty($this->stockable_product)) { + $this->stockable_product = false; + } $price_ht = 0; $price_ttc = 0; @@ -1067,6 +1084,7 @@ public function create($user, $notrigger = 0) $sql .= ", batch_mask"; $sql .= ", fk_unit"; $sql .= ", mandatory_period"; + $sql .= ", stockable_product"; $sql .= ") VALUES ("; $sql .= "'".$this->db->idate($this->date_creation)."'"; $sql .= ", ".(!empty($this->entity) ? (int) $this->entity : (int) $conf->entity); @@ -1098,6 +1116,7 @@ public function create($user, $notrigger = 0) $sql .= ", '".$this->db->escape($this->batch_mask)."'"; $sql .= ", ".($this->fk_unit > 0 ? ((int) $this->fk_unit) : 'NULL'); $sql .= ", '".$this->db->escape($this->mandatory_period)."'"; + $sql .= ", ".((int) $this->stockable_product); $sql .= ")"; dol_syslog(get_class($this)."::Create", LOG_DEBUG); @@ -1379,6 +1398,10 @@ public function update($id, $user, $notrigger = 0, $action = 'update', $updatety $this->state_id = 0; } + if (empty($this->stockable_product)) { + $this->stockable_product = false; + } + // Barcode value $this->barcode = (empty($this->barcode) ? '' : trim($this->barcode)); @@ -1538,7 +1561,9 @@ public function update($id, $user, $notrigger = 0, $action = 'update', $updatety $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1); $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL'); $sql .= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL'); - $sql .= ", mandatory_period = ".($this->mandatory_period); + $sql .= ", mandatory_period = ".($this->mandatory_period ); + $sql .= ", stockable_product = ".(int) $this->stockable_product; + // stock field is not here because it is a denormalized value from product_stock. $sql .= " WHERE rowid = ".((int) $id); @@ -2864,7 +2889,7 @@ public function fetch($id = 0, $ref = '', $ref_ext = '', $barcode = '', $ignore_ $sql .= " p.pmp,"; } $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.sell_or_eat_by_mandatory, p.batch_mask, p.fk_unit,"; - $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf,"; + $sql .= " p.fk_price_expression, p.price_autogen, p.stockable_product, p.model_pdf,"; $sql .= " p.price_label,"; if ($separatedStock) { $sql .= " SUM(sp.reel) as stock"; @@ -2908,7 +2933,7 @@ public function fetch($id = 0, $ref = '', $ref_ext = '', $barcode = '', $ignore_ $sql .= " p.pmp,"; } $sql .= " p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.sell_or_eat_by_mandatory, p.batch_mask, p.fk_unit,"; - $sql .= " p.fk_price_expression, p.price_autogen, p.model_pdf"; + $sql .= " p.fk_price_expression, p.price_autogen, p.stockable_product, p.model_pdf"; $sql .= " ,p.price_label"; if (!$separatedStock) { $sql .= ", p.stock"; @@ -2980,12 +3005,12 @@ public function fetch($id = 0, $ref = '', $ref_ext = '', $barcode = '', $ignore_ $this->height = $obj->height; $this->height_units = $obj->height_units; - $this->surface = $obj->surface; - $this->surface_units = $obj->surface_units; - $this->volume = $obj->volume; - $this->volume_units = $obj->volume_units; - $this->barcode = $obj->barcode; - $this->barcode_type = $obj->fk_barcode_type; + $this->surface = $obj->surface; + $this->surface_units = $obj->surface_units; + $this->volume = $obj->volume; + $this->volume_units = $obj->volume_units; + $this->barcode = $obj->barcode; + $this->barcode_type = $obj->fk_barcode_type; $this->accountancy_code_buy = $obj->accountancy_code_buy; $this->accountancy_code_buy_intra = $obj->accountancy_code_buy_intra; @@ -2994,17 +3019,18 @@ public function fetch($id = 0, $ref = '', $ref_ext = '', $barcode = '', $ignore_ $this->accountancy_code_sell_intra = $obj->accountancy_code_sell_intra; $this->accountancy_code_sell_export = $obj->accountancy_code_sell_export; - $this->fk_default_warehouse = $obj->fk_default_warehouse; - $this->fk_default_workstation = $obj->fk_default_workstation; - $this->seuil_stock_alerte = $obj->seuil_stock_alerte; - $this->desiredstock = $obj->desiredstock; - $this->stock_reel = $obj->stock; - $this->pmp = $obj->pmp; - - $this->date_creation = $obj->datec; - $this->date_modification = $obj->tms; - $this->import_key = $obj->import_key; - $this->entity = $obj->entity; + $this->fk_default_warehouse = $obj->fk_default_warehouse; + $this->fk_default_workstation = $obj->fk_default_workstation; + $this->seuil_stock_alerte = $obj->seuil_stock_alerte; + $this->desiredstock = $obj->desiredstock; + $this->stock_reel = $obj->stock; + $this->stockable_product = $obj->stockable_product; + $this->pmp = $obj->pmp; + + $this->date_creation = $obj->datec; + $this->date_modification = $obj->tms; + $this->import_key = $obj->import_key; + $this->entity = $obj->entity; $this->ref_ext = $obj->ref_ext; $this->fk_price_expression = $obj->fk_price_expression; diff --git a/htdocs/product/list.php b/htdocs/product/list.php index d8a18e9c67f54..3313ebb36d5e8 100644 --- a/htdocs/product/list.php +++ b/htdocs/product/list.php @@ -102,6 +102,7 @@ $search_country = GETPOST("search_country", 'aZ09'); $search_state = GETPOST("state_id", 'intcomma'); $search_tobatch = GETPOST("search_tobatch"); +$search_stockable_product = GETPOST('search_stockable_product', 'int'); $search_accountancy_code_sell = GETPOST("search_accountancy_code_sell", 'alpha'); $search_accountancy_code_sell_intra = GETPOST("search_accountancy_code_sell_intra", 'alpha'); $search_accountancy_code_sell_export = GETPOST("search_accountancy_code_sell_export", 'alpha'); @@ -278,6 +279,18 @@ 'p.tobuy' => array('label' => $langs->transnoentitiesnoconv("Status").' ('.$langs->transnoentitiesnoconv("Buy").')', 'checked' => 1, 'position' => 1000), 'p.import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'index' => 0, 'checked' => -1, 'position' => 1100), ); + +if (! empty($conf->stock->enabled)) { + // service + if ($type == 1) { + if (! empty($conf->global->STOCK_SUPPORTS_SERVICES)) { + $arrayfields['p.stockable_product'] = array('label' => $langs->trans('StockableProduct'), 'checked' => 0, 'position' => 1001); + } + } else { + //product + $arrayfields['p.stockable_product'] = array('label' => $langs->trans('StockableProduct'), 'checked' => 0, 'position' => 1001); + } +} /*foreach ($object->fields as $key => $val) { // If $val['visible']==0, then we never show the field if (!empty($val['visible'])) { @@ -373,6 +386,7 @@ $show_childproducts = ''; $search_import_key = ''; + $search_stockable_product = ''; $search_accountancy_code_sell = ''; $search_accountancy_code_sell_intra = ''; $search_accountancy_code_sell_export = ''; @@ -458,7 +472,7 @@ } $sql .= ' p.datec as date_creation, p.tms as date_modification, p.pmp, p.stock, p.cost_price,'; $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,'; -$sql .= ' p.fk_country, p.fk_state,'; +$sql .= ' p.fk_country, p.fk_state, p.stockable_product,'; $sql .= ' p.import_key,'; if (getDolGlobalString('PRODUCT_USE_UNITS')) { $sql .= ' p.fk_unit, cu.label as cu_label,'; @@ -565,6 +579,9 @@ if (isset($search_tobuy) && dol_strlen($search_tobuy) > 0 && $search_tobuy != -1) { $sql .= " AND p.tobuy = ".((int) $search_tobuy); } +if (isset($search_stockable_product) && dol_strlen($search_stockable_product) > 0 && $search_stockable_product != -1) { + $sql .= " AND p.stockable_product = '". ((int) $search_stockable_product) . "'"; +} if (isset($search_tobatch) && dol_strlen($search_tobatch) > 0 && $search_tobatch != -1) { $sql .= " AND p.tobatch = ".((int) $search_tobatch); } @@ -650,7 +667,7 @@ $sql .= " ppe.accountancy_code_sell, ppe.accountancy_code_sell_intra, ppe.accountancy_code_sell_export, ppe.accountancy_code_buy, ppe.accountancy_code_buy_intra, ppe.accountancy_code_buy_export,"; } $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,'; -$sql .= ' p.fk_country, p.fk_state,'; +$sql .= ' p.fk_country, p.fk_state, p.stockable_product,'; $sql .= ' p.import_key'; if (getDolGlobalString('PRODUCT_USE_UNITS')) { $sql .= ', p.fk_unit, cu.label'; @@ -838,6 +855,9 @@ if ($search_finished) { $param .= "&search_finished=".urlencode($search_finished); } +if ($search_stockable_product != '') { + $param .= "&search_stockable_product=".urlencode($search_stockable_product); +} // Add $param from extra fields include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php'; @@ -1177,6 +1197,11 @@ print ' '; print ''; } +// Managed_in_stock +$array = array('-1'=>' ', '0'=>$langs->trans('No'), '1'=>$langs->trans('Yes')); +if (!empty($arrayfields['p.stockable_product']['checked'])) { + print ''.Form::selectarray('search_stockable_product', $array, $search_stockable_product).''; +} // Desired stock if (!empty($arrayfields['p.desiredstock']['checked'])) { print ''; @@ -1426,6 +1451,9 @@ print_liste_field_titre($arrayfields['p.seuil_stock_alerte']['label'], $_SERVER["PHP_SELF"], "p.seuil_stock_alerte", "", $param, '', $sortfield, $sortorder, 'right '); $totalarray['nbfield']++; } +if (!empty($arrayfields['p.stockable_product']['checked'])) { + print_liste_field_titre($arrayfields['p.stockable_product']['label'], $_SERVER['PHP_SELF'], 'p.stockable_product', '', $param, '', $sortfield, $sortorder, 'center '); +} if (!empty($arrayfields['p.desiredstock']['checked'])) { print_liste_field_titre($arrayfields['p.desiredstock']['label'], $_SERVER["PHP_SELF"], "p.desiredstock", "", $param, '', $sortfield, $sortorder, 'right '); $totalarray['nbfield']++; @@ -1575,6 +1603,7 @@ $product_static->volume_units = $obj->volume_units; $product_static->surface = $obj->surface; $product_static->surface_units = $obj->surface_units; + $product_static->stockable_product = $obj->stockable_product; if (getDolGlobalString('PRODUCT_USE_UNITS')) { $product_static->fk_unit = $obj->fk_unit; } @@ -2082,6 +2111,14 @@ $totalarray['nbfield']++; } } + + // not managed in stock + if (! empty($arrayfields['p.stockable_product']['checked'])) { + print ''; + print ($product_static->stockable_product == '1') ? $langs->trans('Yes') : $langs->trans('No'); + print ''; + } + // Desired stock if (!empty($arrayfields['p.desiredstock']['checked'])) { print ''; diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index 716f28af767c1..7601e35b2025a 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -463,7 +463,7 @@ public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = return -8; } } else { - if (isset($product->stock_warehouse[$entrepot_id]) && (empty($product->stock_warehouse[$entrepot_id]->real) || $product->stock_warehouse[$entrepot_id]->real < abs($qty))) { + if (isset($product->stock_warehouse[$entrepot_id]) && (empty($product->stock_warehouse[$entrepot_id]->real) || $product->stock_warehouse[$entrepot_id]->real < abs($qty)) && $product->stockable_product == Product::ENABLED_STOCK) { $langs->load("stocks"); $this->error = $langs->trans('qtyToTranferIsNotEnough').' : '.$product->ref; $this->errors[] = $langs->trans('qtyToTranferIsNotEnough').' : '.$product->ref; @@ -473,7 +473,7 @@ public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = } } - if ($movestock) { // Change stock for current product, change for subproduct is done after + if ($movestock && $product->stockable_product == Product::ENABLED_STOCK) { // Change stock for current product, change for subproduct is done after // Set $origin_type, origin_id and fk_project $fk_project = $this->fk_project; if (!empty($this->origin_type)) { // This is set by caller for tracking reason @@ -649,9 +649,11 @@ public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = if ($movestock && !$error) { // Call trigger - $result = $this->call_trigger('STOCK_MOVEMENT', $user); - if ($result < 0) { - $error++; + if ($product->stockable_product != Product::NOT_MANAGED_IN_STOCK ) { + $result = $this->call_trigger('STOCK_MOVEMENT', $user); + if ($result < 0) { + $error++; + } } // End call triggers // Check unicity for serial numbered equipment once all movement were done. diff --git a/htdocs/societe/card.php b/htdocs/societe/card.php index 563269809c452..7d4a9e0b1b2eb 100644 --- a/htdocs/societe/card.php +++ b/htdocs/societe/card.php @@ -101,6 +101,7 @@ $error = 0; $errors = array(); +$refalreadyexists = 0; // Get parameters $action = (GETPOST('action', 'aZ09') ? GETPOST('action', 'aZ09') : 'view');