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');
|