diff --git a/htdocs/langs/en_US/main.lang b/htdocs/langs/en_US/main.lang index c32e4d01ddd..73c9964000e 100644 --- a/htdocs/langs/en_US/main.lang +++ b/htdocs/langs/en_US/main.lang @@ -29,6 +29,7 @@ AvailableVariables=Available substitution variables NoTranslation=No translation Translation=Translation EmptySearchString=Enter non empty search criterias +EnterADateCriteria=Enter a date criteria NoRecordFound=No record found NoRecordDeleted=No record deleted NotEnoughDataYet=Not enough data diff --git a/htdocs/langs/en_US/stocks.lang b/htdocs/langs/en_US/stocks.lang index d2c1b2cb382..1b54e53d6de 100644 --- a/htdocs/langs/en_US/stocks.lang +++ b/htdocs/langs/en_US/stocks.lang @@ -240,3 +240,4 @@ InventoryRealQtyHelp=Set value to 0 to reset qty
Keep field empty, or remove UpdateByScaning=Update by scaning UpdateByScaningProductBarcode=Update by scan (product barcode) UpdateByScaningLot=Update by scan (lot|serial barcode) +DisableStockChangeOfSubProduct=Deactivate the stock change for all the subproducts of this Kit during this movement. \ No newline at end of file diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 7d0d82ec147..e1b25b9e3b7 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -4229,9 +4229,9 @@ class Product extends CommonObject // phpcs:enable $this->res = array(); if (isset($this->sousprods) && is_array($this->sousprods)) { - foreach ($this->sousprods as $prod_name => $desc_product) - { - if (is_array($desc_product)) { $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id); + foreach ($this->sousprods as $prod_name => $desc_product) { + if (is_array($desc_product)) { + $this->fetch_prod_arbo($desc_product, "", $multiply, 1, $this->id); } } } @@ -4777,9 +4777,10 @@ class Product extends CommonObject * @param string $inventorycode Inventory code * @param string $origin_element Origin element type * @param int $origin_id Origin id of element + * @param int $disablestockchangeforsubproduct Disable stock change for sub-products of kit (usefull only if product is a subproduct) * @return int <0 if KO, >0 if OK */ - public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null) + public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0) { // phpcs:enable if ($id_entrepot) { @@ -4792,7 +4793,7 @@ class Product extends CommonObject $movementstock = new MouvementStock($this->db); $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin and ->origin->id - $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode); + $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', '', '', '', false, 0, $disablestockchangeforsubproduct); if ($result >= 0) { $this->db->commit(); @@ -4823,9 +4824,10 @@ class Product extends CommonObject * @param string $inventorycode Inventory code * @param string $origin_element Origin element type * @param int $origin_id Origin id of element + * @param int $disablestockchangeforsubproduct Disable stock change for sub-products of kit (usefull only if product is a subproduct) * @return int <0 if KO, >0 if OK */ - public function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $dlc = '', $dluo = '', $lot = '', $inventorycode = '', $origin_element = '', $origin_id = null) + public function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $dlc = '', $dluo = '', $lot = '', $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0) { // phpcs:enable if ($id_entrepot) { @@ -4838,7 +4840,7 @@ class Product extends CommonObject $movementstock = new MouvementStock($this->db); $movementstock->setOrigin($origin_element, $origin_id); - $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot); + $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct); if ($result >= 0) { $this->db->commit(); diff --git a/htdocs/product/composition/card.php b/htdocs/product/composition/card.php index 13ef4582d20..8459fa3e479 100644 --- a/htdocs/product/composition/card.php +++ b/htdocs/product/composition/card.php @@ -1,6 +1,6 @@ - * Copyright (C) 2004-2017 Laurent Destailleur + * Copyright (C) 2004-2020 Laurent Destailleur * Copyright (C) 2005 Eric Seigne * Copyright (C) 2005-2018 Regis Houssin * Copyright (C) 2006 Andre Cianfarani @@ -137,7 +137,7 @@ if ($action == 'search') $current_lang = $langs->getDefaultLang(); $sql = 'SELECT DISTINCT p.rowid, p.ref, p.label, p.fk_product_type as type, p.barcode, p.price, p.price_ttc, p.price_base_type, p.entity,'; - $sql .= ' p.fk_product_type, p.tms as datem'; + $sql .= ' p.fk_product_type, p.tms as datem, p.tobatch'; if (!empty($conf->global->MAIN_MULTILANGS)) $sql .= ', pl.label as labelm, pl.description as descriptionm'; $sql .= ' FROM '.MAIN_DB_PREFIX.'product as p'; $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_product as cp ON p.rowid = cp.fk_product'; @@ -204,7 +204,7 @@ if ($id > 0 || !empty($ref)) dol_banner_tab($object, 'ref', $linkback, $shownav, 'ref', '', '', '', 0, '', '', 0); - if ($object->type != Product::TYPE_SERVICE || empty($conf->global->PRODUIT_MULTIPRICES)) + if ($object->type != Product::TYPE_SERVICE || !empty($conf->global->STOCK_SUPPORTS_SERVICES) || empty($conf->global->PRODUIT_MULTIPRICES)) { print '
'; print '
'; @@ -320,14 +320,11 @@ if ($id > 0 || !empty($ref)) print ''."\n"; $totalsell = 0; - if (count($prods_arbo)) - { - foreach ($prods_arbo as $value) - { + if (count($prods_arbo)) { + foreach ($prods_arbo as $value) { $productstatic->fetch($value['id']); - if ($value['level'] <= 1) - { + if ($value['level'] <= 1) { print ''; $notdefined = 0; @@ -380,7 +377,7 @@ if ($id > 0 || !empty($ref)) // Qty + IncDec if ($user->rights->produit->creer || $user->rights->service->creer) { - print ''; + print ''; print ''; } else { print ''.$nb_of_subproduct.''; @@ -566,6 +563,7 @@ if ($id > 0 || !empty($ref)) $productstatic->label = $objp->label; $productstatic->type = $objp->type; $productstatic->entity = $objp->entity; + $productstatic->status_batch = $objp->tobatch; print ''.$productstatic->getNomUrl(1, '', 24).''; $labeltoshow = $objp->label; diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index c97c56b2887..631cedd0a25 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -128,26 +128,27 @@ class MouvementStock extends CommonObject * Add a movement of stock (in one direction only). * $this->origin can be also be set to save the source object of movement. * - * @param User $user User object - * @param int $fk_product Id of product - * @param int $entrepot_id Id of warehouse - * @param int $qty Qty of movement (can be <0 or >0 depending on parameter type) - * @param int $type Direction of movement: - * 0=input (stock increase by a stock transfer), 1=output (stock decrease by a stock transfer), - * 2=output (stock decrease), 3=input (stock increase) - * Note that qty should be > 0 with 0 or 3, < 0 with 1 or 2. - * @param int $price Unit price HT of product, used to calculate average weighted price (AWP or PMP in french). If 0, average weighted price is not changed. - * @param string $label Label of stock movement - * @param string $inventorycode Inventory code - * @param string $datem Force date of movement - * @param integer $eatby eat-by date. Will be used if lot does not exists yet and will be created. - * @param integer $sellby sell-by date. Will be used if lot does not exists yet and will be created. - * @param string $batch batch number - * @param boolean $skip_batch If set to true, stock movement is done without impacting batch record + * @param User $user User object + * @param int $fk_product Id of product + * @param int $entrepot_id Id of warehouse + * @param int $qty Qty of movement (can be <0 or >0 depending on parameter type) + * @param int $type Direction of movement: + * 0=input (stock increase by a stock transfer), 1=output (stock decrease by a stock transfer), + * 2=output (stock decrease), 3=input (stock increase) + * Note that qty should be > 0 with 0 or 3, < 0 with 1 or 2. + * @param int $price Unit price HT of product, used to calculate average weighted price (AWP or PMP in french). If 0, average weighted price is not changed. + * @param string $label Label of stock movement + * @param string $inventorycode Inventory code + * @param string $datem Force date of movement + * @param integer $eatby eat-by date. Will be used if lot does not exists yet and will be created. + * @param integer $sellby sell-by date. Will be used if lot does not exists yet and will be created. + * @param string $batch batch number + * @param boolean $skip_batch If set to true, stock movement is done without impacting batch record * @param int $id_product_batch Id product_batch (when skip_batch is false and we already know which record of product_batch to use) - * @return int <0 if KO, 0 if fk_product is null or product id does not exists, >0 if OK + * @param int $disablestockchangeforsubproduct Disable stock change for sub-products of kit (usefull only if product is a subproduct) + * @return int <0 if KO, 0 if fk_product is null or product id does not exists, >0 if OK */ - public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = 0, $label = '', $inventorycode = '', $datem = '', $eatby = '', $sellby = '', $batch = '', $skip_batch = false, $id_product_batch = 0) + public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = 0, $label = '', $inventorycode = '', $datem = '', $eatby = '', $sellby = '', $batch = '', $skip_batch = false, $id_product_batch = 0, $disablestockchangeforsubproduct = 0) { // phpcs:disable global $conf, $langs; @@ -571,9 +572,9 @@ class MouvementStock extends CommonObject } // Add movement for sub products (recursive call) - if (!$error && !empty($conf->global->PRODUIT_SOUSPRODUITS) && empty($conf->global->INDEPENDANT_SUBPRODUCT_STOCK)) + if (!$error && !empty($conf->global->PRODUIT_SOUSPRODUITS) && empty($conf->global->INDEPENDANT_SUBPRODUCT_STOCK) && empty($disablestockchangeforsubproduct)) { - $error = $this->_createSubProduct($user, $fk_product, $entrepot_id, $qty, $type, 0, $label, $inventorycode); // we use 0 as price, because pmp is not changed for subproduct + $error = $this->_createSubProduct($user, $fk_product, $entrepot_id, $qty, $type, 0, $label, $inventorycode); // we use 0 as price, because AWP must not change for subproduct } if ($movestock && !$error) diff --git a/htdocs/product/stock/product.php b/htdocs/product/stock/product.php index 0ea05495e5a..89df906feda 100644 --- a/htdocs/product/stock/product.php +++ b/htdocs/product/stock/product.php @@ -108,6 +108,8 @@ if (!empty($canvas)) // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context $hookmanager->initHooks(array('stockproductcard', 'globalcard')); +$error = 0; + /* * Actions @@ -221,17 +223,14 @@ if ($action == "correct_stock" && !$cancel) } } - if (!$error) - { + if (!$error) { $priceunit = price2num(GETPOST("unitprice")); $nbpiece = price2num(GETPOST("nbpiece", 'alphanohtml')); - if (is_numeric($nbpiece) && $nbpiece != 0 && $id) - { + if (is_numeric($nbpiece) && $nbpiece != 0 && $id) { $origin_element = ''; $origin_id = null; - if (GETPOST('projectid', 'int')) - { + if (GETPOST('projectid', 'int')) { $origin_element = 'project'; $origin_id = GETPOST('projectid', 'int'); } @@ -240,8 +239,13 @@ if ($action == "correct_stock" && !$cancel) $object = new Product($db); $result = $object->fetch($id); } - if ($object->hasbatch()) - { + + $disablestockchangeforsubproduct = 0; + if (GETPOST('disablesubproductstockchange')) { + $disablestockchangeforsubproduct = 1; + } + + if ($object->hasbatch()) { $result = $object->correct_stock_batch( $user, GETPOST("id_entrepot", 'int'), @@ -254,7 +258,8 @@ if ($action == "correct_stock" && !$cancel) $batchnumber, GETPOST('inventorycode', 'alphanohtml'), $origin_element, - $origin_id + $origin_id, + $disablestockchangeforsubproduct ); // We do not change value of stock for a correction } else { $result = $object->correct_stock( @@ -266,14 +271,13 @@ if ($action == "correct_stock" && !$cancel) $priceunit, GETPOST('inventorycode', 'alphanohtml'), $origin_element, - $origin_id + $origin_id, + $disablestockchangeforsubproduct ); // We do not change value of stock for a correction } - if ($result > 0) - { - if ($backtopage) - { + if ($result > 0) { + if ($backtopage) { header("Location: ".$backtopage); exit; } else { @@ -535,12 +539,14 @@ if ($id > 0 || $ref) dol_banner_tab($object, 'ref', $linkback, $shownav, 'ref'); - print '
'; - - print '
'; - print ''; - if (!$variants) { + print '
'; + + print '
'; + print '
'; + + print '
'; + if ($conf->productbatch->enabled) { print ''; } - // Stock alert threshold - print ''; - // Hook formObject $parameters = array(); $reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook print $hookmanager->resPrint; + print '
'.$langs->trans("ManageLotSerial").''; print $object->getLibStatut(0, 2); @@ -604,16 +610,23 @@ if ($id > 0 || $ref) print '
'.$form->editfieldkey($form->textwithpicto($langs->trans("StockLimit"), $langs->trans("StockLimitDesc"), 1), 'seuil_stock_alerte', $object->seuil_stock_alerte, $object, $user->rights->produit->creer).''; - print $form->editfieldval("StockLimit", 'seuil_stock_alerte', $object->seuil_stock_alerte, $object, $user->rights->produit->creer, 'string'); - print '
'; + + print '
'; + print '
'; + + print ''; + + // Stock alert threshold + print ''; + // Desired stock print '"; } - } - print "
'.$form->editfieldkey($form->textwithpicto($langs->trans("StockLimit"), $langs->trans("StockLimitDesc"), 1), 'seuil_stock_alerte', $object->seuil_stock_alerte, $object, $user->rights->produit->creer).''; + print $form->editfieldval("StockLimit", 'seuil_stock_alerte', $object->seuil_stock_alerte, $object, $user->rights->produit->creer, 'string'); + print '
'.$form->editfieldkey($form->textwithpicto($langs->trans("DesiredStock"), $langs->trans("DesiredStockDesc"), 1), 'desiredstock', $object->desiredstock, $object, $user->rights->produit->creer); print ''; @@ -727,11 +740,15 @@ if ($id > 0 || $ref) } print "
"; - print '
'; - print '
'; + print ""; + + print '
'; + print '
'; + print ''; + + print '
'; + } print dol_get_fiche_end(); } diff --git a/htdocs/product/stock/stockatdate.php b/htdocs/product/stock/stockatdate.php index f48ac986d20..91159984a35 100644 --- a/htdocs/product/stock/stockatdate.php +++ b/htdocs/product/stock/stockatdate.php @@ -347,10 +347,14 @@ print ''; print '
'; print ''.$langs->trans('Date').' '.$form->selectDate(($date ? $date : -1), 'date'); -print '   '.$langs->trans('Product').' '; +print '   '; +print img_picto('', 'product').' '; +print $langs->trans('Product').' '; $form->select_produits($productid, 'productid', '', 0, 0, -1, 2, '', 0, array(), 0, '1', 0, 'maxwidth300'); -print '   '.$langs->trans('Warehouse').' '; +print '   '; +print img_picto('', 'stock').' '; +print $langs->trans('Warehouse').' '; print $formproduct->selectWarehouses((GETPOSTISSET('fk_warehouse') ? $fk_warehouse : 'ifone'), 'fk_warehouse', '', 1); print '
'; @@ -558,7 +562,7 @@ print $hookmanager->resPrint; if (empty($date) || ! $dateIsValid) { $colspan = 6; if ($mode == 'future') $colspan++; - print ''.$langs->trans("EmptySearchString").''; + print ''.$langs->trans("EnterADateCriteria").''; } print ''; diff --git a/htdocs/product/stock/tpl/stockcorrection.tpl.php b/htdocs/product/stock/tpl/stockcorrection.tpl.php index 0885e9ef777..4f88b448239 100644 --- a/htdocs/product/stock/tpl/stockcorrection.tpl.php +++ b/htdocs/product/stock/tpl/stockcorrection.tpl.php @@ -70,7 +70,7 @@ if ($object->element == 'product') { print ''; $ident = (GETPOST("dwid") ?GETPOST("dwid", 'int') : (GETPOST('id_entrepot') ? GETPOST('id_entrepot', 'int') : ($object->element == 'product' && $object->fk_default_warehouse ? $object->fk_default_warehouse : 'ifone'))); if (empty($ident) && !empty($conf->global->MAIN_DEFAULT_WAREHOUSE)) $ident = $conf->global->MAIN_DEFAULT_WAREHOUSE; - print $formproduct->selectWarehouses($ident, 'id_entrepot', 'warehouseopen,warehouseinternal', 1, 0, 0, '', 0, 0, null, 'minwidth100'); + print img_picto('', 'stock').$formproduct->selectWarehouses($ident, 'id_entrepot', 'warehouseopen,warehouseinternal', 1, 0, 0, '', 0, 0, null, 'minwidth100'); print '   '; print ''; @@ -91,6 +92,17 @@ print ''.$langs->trans("NumberOfUnit").''; print ''; print ''; +// If product is a Kit, we ask if we must disable stock change of subproducts +if (!empty($conf->global->PRODUIT_SOUSPRODUITS) && $object->element == 'product' && $object->hasFatherOrChild(1)) { + print ''; + print ''; + print ''; + print ''; + print ' '; + print ''; + print ''; +} + // Serial / Eat-by date if (!empty($conf->productbatch->enabled) && (($object->element == 'product' && $object->hasbatch()) @@ -126,6 +138,7 @@ if (!empty($conf->projet->enabled)) { print ''.$langs->trans('Project').''; print ''; + print img_picto('', 'project'); $formproject->select_projects(-1, '', 'projectid', 0, 0, 1, 0, 0, 0, 0, '', 0, 0, 'maxwidth300'); print ''; } diff --git a/htdocs/product/stock/tpl/stocktransfer.tpl.php b/htdocs/product/stock/tpl/stocktransfer.tpl.php index 7e159a35b32..20445445e8e 100644 --- a/htdocs/product/stock/tpl/stocktransfer.tpl.php +++ b/htdocs/product/stock/tpl/stocktransfer.tpl.php @@ -71,18 +71,19 @@ print ''; if ($object->element == 'product') { print ''.$langs->trans("WarehouseSource").''; print ''; - print $formproduct->selectWarehouses((GETPOST("dwid") ?GETPOST("dwid", 'int') : (GETPOST('id_entrepot') ?GETPOST('id_entrepot', 'int') : ($object->element == 'product' && $object->fk_default_warehouse ? $object->fk_default_warehouse : 'ifone'))), 'id_entrepot', 'warehouseopen,warehouseinternal', 1); + print img_picto('', 'stock').$formproduct->selectWarehouses((GETPOST("dwid") ?GETPOST("dwid", 'int') : (GETPOST('id_entrepot') ?GETPOST('id_entrepot', 'int') : ($object->element == 'product' && $object->fk_default_warehouse ? $object->fk_default_warehouse : 'ifone'))), 'id_entrepot', 'warehouseopen,warehouseinternal', 1); print ''; } if ($object->element == 'stock') { print ''.$langs->trans("Product").''; print ''; + print img_picto('', 'product'); $form->select_produits(GETPOST('product_id', 'int'), 'product_id', (empty($conf->global->STOCK_SUPPORTS_SERVICES) ? '0' : ''), 0, 0, -1, 2, '', 0, null, 0, 1, 0, 'maxwidth500'); print ''; } print ''.$langs->trans("WarehouseTarget").''; -print $formproduct->selectWarehouses(GETPOST('id_entrepot_destination'), 'id_entrepot_destination', 'warehouseopen,warehouseinternal', 1); +print img_picto('', 'stock').$formproduct->selectWarehouses(GETPOST('id_entrepot_destination'), 'id_entrepot_destination', 'warehouseopen,warehouseinternal', 1); print ''; print ''.$langs->trans("NumberOfUnit").''; print '';