FIX: Qty in stock must be float like it is in stock movement.

FIX: Can't create a stock transfer from product card
Performance enhancement of stock information loading.
This commit is contained in:
Laurent Destailleur 2016-04-28 10:24:43 +02:00
parent aa31a7e9c5
commit e723966583
11 changed files with 53 additions and 67 deletions

View File

@ -715,7 +715,7 @@ if ($resql)
// Get local and virtual stock and store it into cache
if (empty($productstat_cache[$generic_commande->lines[$lig]->fk_product])) {
$generic_product->load_stock();
$generic_product->load_stock('nobatch');
//$generic_product->load_virtual_stock(); Already included into load_stock
$productstat_cache[$generic_commande->lines[$lig]->fk_product]['stock_reel'] = $generic_product->stock_reel;
$productstat_cachevirtual[$generic_commande->lines[$lig]->fk_product]['stock_reel'] = $generic_product->stock_theorique;
@ -723,7 +723,7 @@ if ($resql)
$generic_product->stock_reel = $productstat_cache[$generic_commande->lines[$lig]->fk_product]['stock_reel'];
$generic_product->stock_theorique = $productstat_cachevirtual[$generic_commande->lines[$lig]->fk_product]['stock_reel'] = $generic_product->stock_theorique;
}
if (empty($conf->global->SHIPPABLE_ORDER_ICON_IN_LIST)) // Default code. Default is when this option is not set, setting it create strange result
{
$text_info .= $generic_commande->lines[$lig]->qty.' X '.$generic_commande->lines[$lig]->ref.' '.dol_trunc($generic_commande->lines[$lig]->product_label, 25);

View File

@ -66,6 +66,8 @@ ALTER TABLE llx_product ADD COLUMN height_units tinyint DEFAULT NULL;
ALTER TABLE llx_product ADD COLUMN default_vat_code varchar(10) after cost_price;
ALTER TABLE llx_product MODIFY COLUMN stock real;
CREATE TABLE llx_categorie_user
(
fk_categorie integer NOT NULL,

View File

@ -79,7 +79,7 @@ create table llx_product
surface_units tinyint DEFAULT NULL,
volume float DEFAULT NULL,
volume_units tinyint DEFAULT NULL,
stock integer, -- Current physical stock (dernormalized field)
stock real, -- Current physical stock (dernormalized field)
pmp double(24,8) DEFAULT 0 NOT NULL, -- To store valuation of stock calculated using average price method, for this product
fifo double(24,8), -- To store valuation of stock calculated using fifo method, for this product
lifo double(24,8), -- To store valuation of stock calculated using lifo method, for this product

View File

@ -24,8 +24,8 @@ create table llx_stock_mouvement
datem datetime, -- Date and hour of movement
fk_product integer NOT NULL, -- Id of product
batch varchar(30) DEFAULT NULL, -- Lot or serial number
eatby date DEFAULT NULL, -- Eatby date
sellby date DEFAULT NULL, -- Sellby date
eatby date DEFAULT NULL, -- Eatby date (deprecated, we should get value from batch number in table llx_product_lot)
sellby date DEFAULT NULL, -- Sellby date (deprecated, we should get value from batch number in table llx_product_lot)
fk_entrepot integer NOT NULL, -- Id warehouse
value real, -- Qty of movement
price double(24,8) DEFAULT 0, -- Entry price (used to calculate PMP, FIFO or LIFO value)

View File

@ -1850,7 +1850,7 @@ class Product extends CommonObject
}
}
// We should not load stock at each fetch. If someone need stock, he must call load_stock after fetch.
// We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product.
//$res=$this->load_stock();
// instead we just init the stock_warehouse array
$this->stock_warehouse = array();
@ -2847,7 +2847,7 @@ class Product extends CommonObject
//print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n";
$this->fetch($id); // Load product
$this->load_stock(); // Load stock
$this->load_stock('nobatch,novirtual'); // Load stock to get true this->stock_reel
$this->res[]= array(
'id'=>$id, // Id product
'id_parent'=>$id_parent,

View File

@ -142,8 +142,8 @@ $arrayfields=array(
'p.minbuyprice'=>array('label'=>$langs->trans("BuyingPriceMinShort"), 'checked'=>1, 'enabled'=>(! empty($user->rights->fournisseur->lire))),
'p.seuil_stock_alerte'=>array('label'=>$langs->trans("StockLimit"), 'checked'=>0, 'enabled'=>(! empty($conf->stock->enabled) && $user->rights->stock->lire && $contextpage != 'service')),
'p.desiredstock'=>array('label'=>$langs->trans("DesiredStock"), 'checked'=>1, 'enabled'=>(! empty($conf->stock->enabled) && $user->rights->stock->lire && $contextpage != 'service')),
'p.tobatch'=>array('label'=>$langs->trans("ManageLotSerial"), 'checked'=>0, 'enabled'=>(! empty($conf->productbatch->enabled))),
'p.stock'=>array('label'=>$langs->trans("PhysicalStock"), 'checked'=>1, 'enabled'=>(! empty($conf->stock->enabled) && $user->rights->stock->lire && $contextpage != 'service')),
'p.tobatch'=>array('label'=>$langs->trans("ManageLotSerial"), 'checked'=>0, 'enabled'=>(! empty($conf->productbatch->enabled))),
'p.accountancy_code_sell'=>array('label'=>$langs->trans("ProductAccountancySellCode"), 'checked'=>0),
'p.accountancy_code_buy'=>array('label'=>$langs->trans("ProductAccountancyBuyCode"), 'checked'=>0),
'p.datec'=>array('label'=>$langs->trans("DateCreation"), 'checked'=>0, 'position'=>500),
@ -428,8 +428,8 @@ else
if (! empty($arrayfields['p.minbuyprice']['checked'])) print_liste_field_titre($arrayfields['p.minbuyprice']['label'], $_SERVER["PHP_SELF"],"","",$param,'align="right"',$sortfield,$sortorder);
if (! empty($arrayfields['p.seuil_stock_alerte']['checked'])) print_liste_field_titre($arrayfields['p.seuil_stock_alerte']['label'], $_SERVER["PHP_SELF"],"p.seuil_stock_alerte","",$param,'align="right"',$sortfield,$sortorder);
if (! empty($arrayfields['p.desiredstock']['checked'])) print_liste_field_titre($arrayfields['p.desiredstock']['label'], $_SERVER["PHP_SELF"],"p.desiredstock","",$param,'align="right"',$sortfield,$sortorder);
if (! empty($arrayfields['p.tobatch']['checked'])) print_liste_field_titre($arrayfields['p.tobatch']['label'], $_SERVER["PHP_SELF"],"p.tobatch","",$param,'align="center"',$sortfield,$sortorder);
if (! empty($arrayfields['p.stock']['checked'])) print_liste_field_titre($arrayfields['p.stock']['label'], $_SERVER["PHP_SELF"],"p.stock","",$param,'align="right"',$sortfield,$sortorder);
if (! empty($arrayfields['p.tobatch']['checked'])) print_liste_field_titre($arrayfields['p.tobatch']['label'], $_SERVER["PHP_SELF"],"p.tobatch","",$param,'align="center"',$sortfield,$sortorder);
if (! empty($arrayfields['p.accountancy_code_sell']['checked'])) print_liste_field_titre($arrayfields['p.accountancy_code_sell']['label'], $_SERVER["PHP_SELF"],"p.accountancy_code_sell","",$param,'',$sortfield,$sortorder);
if (! empty($arrayfields['p.accountancy_code_buy']['checked'])) print_liste_field_titre($arrayfields['p.accountancy_code_buy']['label'], $_SERVER["PHP_SELF"],"p.accountancy_code_buy","",$param,'',$sortfield,$sortorder);
if (is_array($extrafields->attribute_label) && count($extrafields->attribute_label))
@ -515,11 +515,11 @@ else
print ' ';
print '</td>';
}
// To batch
if (! empty($arrayfields['p.tobatch']['checked'])) print '<td class="liste_titre center">'.$form->selectyesno($search_tobatch, '', '', '', 1).'</td>';
// Stock
if (! empty($arrayfields['p.stock']['checked'])) print '<td class="liste_titre">&nbsp;</td>';
// Accountancy code sell
// To batch
if (! empty($arrayfields['p.tobatch']['checked'])) print '<td class="liste_titre center">'.$form->selectyesno($search_tobatch, '', '', '', 1).'</td>';
// Accountancy code sell
if (! empty($arrayfields['p.accountancy_code_sell']['checked'])) print '<td class="liste_titre"><input class="flat" type="text" name="search_accountancy_code_sell" size="6" value="'.dol_escape_htmltag($search_accountancy_code_sell).'"></td>';
// Accountancy code sell
if (! empty($arrayfields['p.accountancy_code_buy']['checked'])) print '<td class="liste_titre"><input class="flat" type="text" name="search_accountancy_code_buy" size="6" value="'.dol_escape_htmltag($search_accountancy_code_buy).'"></td>';
@ -600,7 +600,16 @@ else
$product_static->status_buy = $objp->tobuy;
$product_static->status = $objp->tosell;
$product_static->entity = $objp->entity;
if (! empty($conf->stock->enabled) && $user->rights->stock->lire && $type != 1) // To optimize call of load_stock
{
if ($objp->fk_product_type != 1) // Not a service
{
$product_static->load_stock('nobatch'); // Load stock_reel + stock_warehouse. This also call load_virtual_stock()
}
}
$var=!$var;
print '<tr '.$bc[$var].'>';
@ -681,15 +690,6 @@ else
print '</td>';
}
if (! empty($conf->stock->enabled) && $user->rights->stock->lire && $type != 1) // To optimize call of load_stock
{
if ($objp->fk_product_type != 1) // Not a service
{
$product_static->id = $objp->rowid;
$product_static->load_stock('nobatch'); // Load stock_reel + stock_warehouse + batch detail. This also call load_virtual_stock()
}
}
// Limit alert
if (! empty($arrayfields['p.seuil_stock_alerte']['checked']))
{
@ -710,13 +710,6 @@ else
}
print '</td>';
}
// Lot/Serial
if (! empty($arrayfields['p.tobatch']['checked']))
{
print '<td align="center">';
print yn($objp->tobatch);
print '</td>';
}
// Stock
if (! empty($arrayfields['p.stock']['checked']))
{
@ -728,6 +721,13 @@ else
}
print '</td>';
}
// Lot/Serial
if (! empty($arrayfields['p.tobatch']['checked']))
{
print '<td align="center">';
print yn($objp->tobatch);
print '</td>';
}
// Accountancy code sell
if (! empty($arrayfields['p.accountancy_code_sell']['checked'])) print '<td>'.$objp->accountancy_code_sell.'</td>';
// Accountancy code sell

View File

@ -252,25 +252,12 @@ class MouvementStock extends CommonObject
// Calculate new PMP.
$newpmp=0;
//$newpmpwarehouse=0;
if (! $error)
{
// Note: PMP is calculated on stock input only (type of movement = 0 or 3). If type == 0 or 3, qty should be > 0.
// Note: Price should always be >0 or 0. PMP should be always >0 (calculated on input)
if (($type == 0 || $type == 3) && $price > 0)
{
// If we will change PMP for the warehouse we edit and the product, we must first check/clean that PMP is defined
// on every stock entry with old value (so global updated value will match recalculated value from product_stock)
/* $sql = "UPDATE ".MAIN_DB_PREFIX."product_stock SET pmp = ".($oldpmp?$oldpmp:'0');
$sql.= " WHERE pmp = 0 AND fk_product = ".$fk_product;
dol_syslog(get_class($this)."::_create", LOG_DEBUG);
$resql=$this->db->query($sql);
if (! $resql)
{
$this->errors[]=$this->db->lasterror();
$error = -4;
}
*/
$oldqtytouse=($oldqty >= 0?$oldqty:0);
// We make a test on oldpmp>0 to avoid to use normal rule on old data with no pmp field defined
if ($oldpmp > 0) $newpmp=price2num((($oldqtytouse * $oldpmp) + ($qty * $price)) / ($oldqtytouse + $qty), 'MU');
@ -278,13 +265,8 @@ class MouvementStock extends CommonObject
{
$newpmp=$price; // For this product, PMP was not yet set. We set it to input price.
}
/*
$oldqtywarehousetouse=$oldqtywarehouse;
if ($oldpmpwarehouse > 0) $newpmpwarehouse=price2num((($oldqtywarehousetouse * $oldpmpwarehouse) + ($qty * $price)) / ($oldqtywarehousetouse + $qty), 'MU');
else $newpmpwarehouse=$price;
*/
//print "oldqtytouse=".$oldqtytouse." oldpmp=".$oldpmp." oldqtywarehousetouse=".$oldqtywarehousetouse." oldpmpwarehouse=".$oldpmpwarehouse." ";
//print "qty=".$qty." newpmp=".$newpmp." newpmpwarehouse=".$newpmpwarehouse;
//print "oldqtytouse=".$oldqtytouse." oldpmp=".$oldpmp." oldqtywarehousetouse=".$oldqtywarehousetouse." ";
//print "qty=".$qty." newpmp=".$newpmp;
//exit;
}
else if ($type == 1 || $type == 2)
@ -295,7 +277,6 @@ class MouvementStock extends CommonObject
else
{
$newpmp = $oldpmp;
//$newpmpwarehouse = $oldpmpwarehouse;
}
}
@ -339,11 +320,13 @@ class MouvementStock extends CommonObject
// Update PMP and denormalized value of stock qty at product level
if (! $error)
{
$sql = "UPDATE ".MAIN_DB_PREFIX."product SET pmp = ".$newpmp.", stock = ".$this->db->ifsql("stock IS NULL", 0, "stock") . " + ".$qty;
//$sql = "UPDATE ".MAIN_DB_PREFIX."product SET pmp = ".$newpmp.", stock = ".$this->db->ifsql("stock IS NULL", 0, "stock") . " + ".$qty;
//$sql.= " WHERE rowid = ".$fk_product;
// Update pmp + denormalized fields because we change content of produt_stock. Warning: Do not use "SET p.stock", does not works with pgsql
$sql = "UPDATE ".MAIN_DB_PREFIX."product as p SET p.pmp = ".$newpmp.", ";
$sql.= " stock=(SELECT SUM(ps.reel) FROM llx_product_stock ps WHERE ps.fk_product = p.rowid)";
$sql.= " WHERE rowid = ".$fk_product;
// May be this request is better:
// UPDATE llx_product p SET p.stock= (SELECT SUM(ps.reel) FROM llx_product_stock ps WHERE ps.fk_product = p.rowid);
print $sql;
dol_syslog(get_class($this)."::_create", LOG_DEBUG);
$resql=$this->db->query($sql);
if (! $resql)

View File

@ -185,7 +185,7 @@ if ($action == 'createmovements')
{
$result=$product->fetch($id_product);
$product->load_stock(); // Load array product->stock_warehouse
$product->load_stock('novirtual'); // Load array product->stock_warehouse
// Define value of products moved
$pricesrc=0;

View File

@ -207,11 +207,10 @@ if ($action == "transfert_stock" && ! $cancel)
$db->begin();
$product->load_stock(); // Load array product->stock_warehouse
$product->load_stock('novirtual'); // Load array product->stock_warehouse
// Define value of products moved
$pricesrc=0;
//if (isset($product->stock_warehouse[GETPOST("id_entrepot_source")]->pmp)) $pricesrc=$product->stock_warehouse[GETPOST("id_entrepot_source")]->pmp;
if (isset($product->pmp)) $pricesrc=$product->pmp;
$pricedest=$pricesrc;

View File

@ -185,7 +185,7 @@ if ($action == "correct_stock" && ! $cancel)
// Transfer stock from a warehouse to another warehouse
if ($action == "transfert_stock" && ! $cancel)
{
if (! (GETPOST("id_entrepot_source",'int') > 0) || ! (GETPOST("id_entrepot_destination",'int') > 0))
if (! (GETPOST("id_entrepot",'int') > 0) || ! (GETPOST("id_entrepot_destination",'int') > 0))
{
setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Warehouse")), null, 'errors');
$error++;
@ -197,7 +197,7 @@ if ($action == "transfert_stock" && ! $cancel)
$error++;
$action='transfert';
}
if (GETPOST("id_entrepot_source",'int') == GETPOST("id_entrepot_destination",'int'))
if (GETPOST("id_entrepot",'int') == GETPOST("id_entrepot_destination",'int'))
{
setEventMessages($langs->trans("ErrorSrcAndTargetWarehouseMustDiffers"), null, 'errors');
$error++;
@ -225,7 +225,7 @@ if ($action == "transfert_stock" && ! $cancel)
$db->begin();
$object->load_stock(); // Load array product->stock_warehouse
$object->load_stock('novirtual'); // Load array product->stock_warehouse
// Define value of products moved
$pricesrc=0;
@ -254,7 +254,7 @@ if ($action == "transfert_stock" && ! $cancel)
}
else
{
$srcwarehouseid=GETPOST('id_entrepot_source','int');
$srcwarehouseid=GETPOST('id_entrepot','int');
$batch=GETPOST('batch_number');
$eatby=$d_eatby;
$sellby=$d_sellby;
@ -291,7 +291,7 @@ if ($action == "transfert_stock" && ! $cancel)
// Remove stock
$result1=$object->correct_stock(
$user,
GETPOST("id_entrepot_source"),
GETPOST("id_entrepot"),
GETPOST("nbpiece"),
1,
GETPOST("label"),
@ -390,6 +390,7 @@ if ($id > 0 || $ref)
{
$object = new Product($db);
$result = $object->fetch($id,$ref);
$object->load_stock();
$help_url='EN:Module_Stocks_En|FR:Module_Stock|ES:M&oacute;dulo_Stocks';
@ -480,7 +481,6 @@ if ($id > 0 || $ref)
print '</td></tr>';
// Real stock
$object->load_stock();
$text_stock_options = '';
$text_stock_options.= (! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)?$langs->trans("DeStockOnShipment").'<br>':'');
$text_stock_options.= (! empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER)?$langs->trans("DeStockOnValidateOrder").'<br>':'');
@ -702,9 +702,10 @@ if ($resql)
$entrepotstatic->id=$obj->rowid;
$entrepotstatic->libelle=$obj->label;
$entrepotstatic->lieu=$obj->lieu;
$stock_real = round($obj->reel, 10);
print '<tr '.$bc[$var].'>';
print '<td colspan="4">'.$entrepotstatic->getNomUrl(1).'</td>';
print '<td align="right">'.$obj->reel.($obj->reel<0?' '.img_warning():'').'</td>';
print '<td align="right">'.$stock_real.($stock_real < 0 ?' '.img_warning():'').'</td>';
// PMP
print '<td align="right">'.(price2num($object->pmp)?price2num($object->pmp,'MU'):'').'</td>';
// Value purchase

View File

@ -411,7 +411,8 @@ function getProductOrService($authentication,$id='',$ref='',$ref_ext='',$lang=''
'localtax1_tx' => $product->localtax1_tx,
'localtax2_tx' => $product->localtax2_tx,
'stock_real' => $product->stock_reel,
'stock_real' => $product->stock_reel,
'stock_virtual' => $product->stock_theorique,
'stock_alert' => $product->seuil_stock_alerte,
'pmp' => $product->pmp,
'import_key' => $product->import_key,
@ -572,7 +573,7 @@ function createProductOrService($authentication,$product)
require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
$savstockreal=$newobject->stock_reel;
$newobject->load_stock(); // This overwrite ->stock_reel
$newobject->load_stock('novirtual,nobatch'); // This overwrite ->stock_reel, surely 0 because we have just created product
$getstockreal = $newobject->stock_reel;
if ($savstockreal != $getstockreal)
@ -741,7 +742,7 @@ function updateProductOrService($authentication,$product)
require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
$savstockreal=$newobject->stock_reel;
$newobject->load_stock(); // This overwrite ->stock_reel
$newobject->load_stock('novirtual,nobatch'); // This overwrite ->stock_reel
$getstockreal = $newobject->stock_reel;
if ($savstockreal != $getstockreal)