diff --git a/htdocs/langs/en_US/productbatch.lang b/htdocs/langs/en_US/productbatch.lang index 09806e2261a..0d530722306 100644 --- a/htdocs/langs/en_US/productbatch.lang +++ b/htdocs/langs/en_US/productbatch.lang @@ -5,3 +5,7 @@ ProductStatusNotOnBatch= Not Managed ProductStatusOnBatchShort= Managed ProductStatusNotOnBatchShort= Not Managed Batch=Batch +atleast1batchfield= Eat-by date or Sell-by date or Batch number +batch_number= Batch number +l_eatby= Eat-by date +l_sellby= Sell-by date diff --git a/htdocs/langs/fr_FR/productbatch.lang b/htdocs/langs/fr_FR/productbatch.lang index 24231f0e6e2..0954e74ef16 100644 --- a/htdocs/langs/fr_FR/productbatch.lang +++ b/htdocs/langs/fr_FR/productbatch.lang @@ -5,3 +5,8 @@ ProductStatusNotOnBatch= Non gérer ProductStatusOnBatchShort= Gérer ProductStatusNotOnBatchShort= Non gérer Batch=Lot +atleast1batchfield= DLC ou DLUO ou Numéro de lot +batch_number= Numéro de lot +l_eatby= DLC +l_sellby= DLUO + diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index a84aa8adc5c..b0b0e1f908f 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -29,6 +29,7 @@ * \brief File of class to manage predefined products or services */ require_once DOL_DOCUMENT_ROOT .'/core/class/commonobject.class.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php'; /** @@ -2796,7 +2797,7 @@ class Product extends CommonObject { $this->stock_reel = 0; - $sql = "SELECT ps.reel, ps.fk_entrepot, ps.pmp"; + $sql = "SELECT ps.reel, ps.fk_entrepot, ps.pmp, ps.rowid"; $sql.= " FROM ".MAIN_DB_PREFIX."product_stock as ps"; $sql.= ", ".MAIN_DB_PREFIX."entrepot as w"; $sql.= " WHERE w.entity IN (".getEntity('warehouse', 1).")"; @@ -2817,6 +2818,7 @@ class Product extends CommonObject $this->stock_warehouse[$row->fk_entrepot] = new stdClass(); $this->stock_warehouse[$row->fk_entrepot]->real = $row->reel; $this->stock_warehouse[$row->fk_entrepot]->pmp = $row->pmp; + if ($this->hasbatch()) $this->stock_warehouse[$row->fk_entrepot]->detail_batch=Productbatch::findAll($this->db,$row->rowid,1); $this->stock_reel+=$row->reel; $i++; } @@ -3320,5 +3322,47 @@ class Product extends CommonObject { return ($this->status_batch == 1 ? true : false); } + + /** + * Adjust stock in a warehouse for product with batch number + * + * @param User $user user asking change + * @param int $id_entrepot id of warehouse + * @param double $nbpiece nb of units + * @param int $movement 0 = add, 1 = remove + * @param string $label Label of stock movement + * @param double $price Price to use for stock eval + * @param date $dlc eat-by date + * @param date $dluo sell-by date + * @param string $lot Lot number + * @return int <0 if KO, >0 if OK + */ + function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label='', $price=0, $dlc='', $dluo='',$lot='') + { + if ($id_entrepot) + { + $this->db->begin(); + + require_once DOL_DOCUMENT_ROOT .'/product/stock/class/mouvementstock.class.php'; + + $op[0] = "+".trim($nbpiece); + $op[1] = "-".trim($nbpiece); + + $movementstock=new MouvementStock($this->db); + $result=$movementstock->_create($user,$this->id,$id_entrepot,$op[$movement],$movement,$price,$label,'',$dlc,$dluo,$lot); + + if ($result >= 0) + { + $this->db->commit(); + return 1; + } + else + { + dol_print_error($this->db); + $this->db->rollback(); + return -1; + } + } + } } ?> diff --git a/htdocs/product/class/productbatch.class.php b/htdocs/product/class/productbatch.class.php new file mode 100644 index 00000000000..cdfef622f45 --- /dev/null +++ b/htdocs/product/class/productbatch.class.php @@ -0,0 +1,526 @@ + + * Copyright (C) 2013-2014 Cedric GROSS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file product/class/productbatch.class.php + * \ingroup productbatch + * \brief Manage record and specific data for batch number management + */ + +require_once(DOL_DOCUMENT_ROOT."/core/class/commonobject.class.php"); + + +/** + * Manage record for batch number management + */ +class Productbatch extends CommonObject +{ + var $element='productbatch'; //!< Id that identify managed objects + private static $_table_element='product_batch'; //!< Name of table without prefix where object is stored + + var $id; + + var $tms=''; + var $fk_product_stock; + var $sellby=''; + var $eatby=''; + var $batch=''; + var $qty; + var $import_key; + + + + + /** + * Constructor + * + * @param DoliDb $db Database handler + */ + function __construct($db) + { + $this->db = $db; + return 1; + } + + + /** + * Create object into database + * + * @param User $user User that creates + * @param int $notrigger 0=launch triggers after, 1=disable triggers + * @return int <0 if KO, Id of created object if OK + */ + function create($user, $notrigger=0) + { + global $conf, $langs; + $error=0; + + // Clean parameters + $this->clean_param(); + + // Check parameters + // Put here code to add control on parameters values + + // Insert request + $sql = "INSERT INTO ".MAIN_DB_PREFIX.self::$_table_element." ("; + $sql.= "fk_product_stock,"; + $sql.= "sellby,"; + $sql.= "eatby,"; + $sql.= "batch,"; + $sql.= "qty,"; + $sql.= "import_key"; + $sql.= ") VALUES ("; + $sql.= " ".(! isset($this->fk_product_stock)?'NULL':$this->fk_product_stock).","; + $sql.= " ".(! isset($this->sellby) || dol_strlen($this->sellby)==0?'NULL':$this->db->idate($this->sellby)).","; + $sql.= " ".(! isset($this->eatby) || dol_strlen($this->eatby)==0?'NULL':$this->db->idate($this->eatby)).","; + $sql.= " ".(! isset($this->batch)?'NULL':"'".$this->db->escape($this->batch)."'").","; + $sql.= " ".(! isset($this->qty)?'NULL':$this->qty).","; + $sql.= " ".(! isset($this->import_key)?'NULL':"'".$this->db->escape($this->import_key)."'").""; + + + $sql.= ")"; + + $this->db->begin(); + + dol_syslog(get_class($this)."::create sql=".$sql, LOG_DEBUG); + $resql=$this->db->query($sql); + if (! $resql) { $error++; $this->errors[]="Error ".$this->db->lasterror(); } + if (! $error) + { + $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.self::$_table_element); + + if (! $notrigger) + { + // Uncomment this and change MYOBJECT to your own tag if you + // want this action calls a trigger. + + //// Call triggers + //include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php'; + //$interface=new Interfaces($this->db); + //$result=$interface->run_triggers('MYOBJECT_CREATE',$this,$user,$langs,$conf); + //if ($result < 0) { $error++; $this->errors=$interface->errors; } + //// End call triggers + } + } + + // Commit or rollback + if ($error) + { + foreach($this->errors as $errmsg) + { + dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR); + $this->error.=($this->error?', '.$errmsg:$errmsg); + } + $this->db->rollback(); + return -1*$error; + } + else + { + $this->db->commit(); + return $this->id; + } + } + + + /** + * Load object in memory from the database + * + * @param int $id Id object + * @return int <0 if KO, >0 if OK + */ + function fetch($id) + { + global $langs; + $sql = "SELECT"; + $sql.= " t.rowid,"; + + $sql.= " t.tms,"; + $sql.= " t.fk_product_stock,"; + $sql.= " t.sellby,"; + $sql.= " t.eatby,"; + $sql.= " t.batch,"; + $sql.= " t.qty,"; + $sql.= " t.import_key"; + + + $sql.= " FROM ".MAIN_DB_PREFIX.self::$_table_element." as t"; + $sql.= " WHERE t.rowid = ".$id; + + dol_syslog(get_class($this)."::fetch sql=".$sql, LOG_DEBUG); + $resql = $this->db->query($sql); + if ($resql) + { + if ($this->db->num_rows($resql)) + { + $obj = $this->db->fetch_object($resql); + + $this->id = $obj->rowid; + $this->tms = $this->db->jdate($obj->tms); + $this->fk_product_stock = $obj->fk_product_stock; + $this->sellby = $this->db->jdate($obj->sellby); + $this->eatby = $this->db->jdate($obj->eatby); + $this->batch = $obj->batch; + $this->qty = $obj->qty; + $this->import_key = $obj->import_key; + } + $this->db->free($resql); + + return 1; + } + else + { + $this->error = "Error ".$this->db->lasterror(); + dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR); + return -1; + } + } + + /** + * Update object into database + * + * @param User $user User that modifies + * @param int $notrigger 0=launch triggers after, 1=disable triggers + * @return int <0 if KO, >0 if OK + */ + function update($user=0, $notrigger=0) + { + global $conf, $langs; + $error=0; + + // Clean parameters + $this->clean_param(); + + // Update request + $sql = "UPDATE ".MAIN_DB_PREFIX.self::$_table_element." SET"; + $sql.= " fk_product_stock=".(isset($this->fk_product_stock)?$this->fk_product_stock:"null").","; + $sql.= " sellby=".(dol_strlen($this->sellby)!=0 ? "'".$this->db->idate($this->sellby)."'" : 'null').","; + $sql.= " eatby=".(dol_strlen($this->eatby)!=0 ? "'".$this->db->idate($this->eatby)."'" : 'null').","; + $sql.= " batch=".(isset($this->batch)?"'".$this->db->escape($this->batch)."'":"null").","; + $sql.= " qty=".(isset($this->qty)?$this->qty:"null").","; + $sql.= " import_key=".(isset($this->import_key)?"'".$this->db->escape($this->import_key)."'":"null").""; + $sql.= " WHERE rowid=".$this->id." AND tms='".$this->db->idate($this->tms)."'"; + + $this->db->begin(); + + dol_syslog(get_class($this)."::update sql=".$sql, LOG_DEBUG); + $resql = $this->db->query($sql); + if (! $resql) { $error++; $this->errors[]="Error ".$this->db->lasterror(); } + if (! $error) + { + if (! $notrigger) + { + // Uncomment this and change MYOBJECT to your own tag if you + // want this action calls a trigger. + + //// Call triggers + //include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php'; + //$interface=new Interfaces($this->db); + //$result=$interface->run_triggers('MYOBJECT_MODIFY',$this,$user,$langs,$conf); + //if ($result < 0) { $error++; $this->errors=$interface->errors; } + //// End call triggers + } + } + + // Commit or rollback + if ($error) + { + foreach($this->errors as $errmsg) + { + dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR); + $this->error.=($this->error?', '.$errmsg:$errmsg); + } + $this->db->rollback(); + return -1*$error; + } + else + { + $this->db->commit(); + return 1; + } + } + + /** + * Delete object in database + * + * @param User $user User that deletes + * @param int $notrigger 0=launch triggers after, 1=disable triggers + * @return int <0 if KO, >0 if OK + */ + function delete($user, $notrigger=0) + { + global $conf, $langs; + $error=0; + + $this->db->begin(); + + if (! $error) + { + if (! $notrigger) + { + // Uncomment this and change MYOBJECT to your own tag if you + // want this action calls a trigger. + + //// Call triggers + //include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php'; + //$interface=new Interfaces($this->db); + //$result=$interface->run_triggers('MYOBJECT_DELETE',$this,$user,$langs,$conf); + //if ($result < 0) { $error++; $this->errors=$interface->errors; } + //// End call triggers + } + } + + if (! $error) + { + $sql = "DELETE FROM ".MAIN_DB_PREFIX.self::$_table_element.""; + $sql.= " WHERE rowid=".$this->id; + + dol_syslog(get_class($this)."::delete sql=".$sql); + $resql = $this->db->query($sql); + if (! $resql) { $error++; $this->errors[]="Error ".$this->db->lasterror(); } + } + + // Commit or rollback + if ($error) + { + foreach($this->errors as $errmsg) + { + dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR); + $this->error.=($this->error?', '.$errmsg:$errmsg); + } + $this->db->rollback(); + return -1*$error; + } + else + { + $this->db->commit(); + return 1; + } + } + + + + /** + * Load an object from its id and create a new one in database + * + * @param int $fromid Id of object to clone + * @return int New id of clone + */ + function createFromClone($fromid) + { + global $user,$langs; + + $error=0; + + $object=new Productbatch($this->db); + + $this->db->begin(); + + // Load source object + $object->fetch($fromid); + $object->id=0; + $object->statut=0; + + // Clear fields + // ... + + // Create clone + $result=$object->create($user); + + // Other options + if ($result < 0) + { + $this->error=$object->error; + $error++; + } + + if (! $error) + { + + + } + + // End + if (! $error) + { + $this->db->commit(); + return $object->id; + } + else + { + $this->db->rollback(); + return -1; + } + } + + + /** + * Initialise object with example values + * Id must be 0 if object instance is a specimen + * + * @return void + */ + function initAsSpecimen() + { + $this->id=0; + + $this->tms=''; + $this->fk_product_stock=''; + $this->sellby=''; + $this->eatby=''; + $this->batch=''; + $this->import_key=''; + + + } + + /** + * Clean fields (triming) + * + * @return void + */ + private function clean_param() { + if (isset($this->fk_product_stock)) $this->fk_product_stock=(int) trim($this->fk_product_stock); + if (isset($this->batch)) $this->batch=trim($this->batch); + if (isset($this->qty)) $this->qty=(float) trim($this->qty); + if (isset($this->import_key)) $this->import_key=trim($this->import_key); + } + + /** + * Find first detail record that match eather eat-by or sell-by or batch within given warehouse + * + * @param int $fk_product_stock id product_stock for objet + * @param date $eatby eat-by date for objet + * @param date $sellby sell-by date for objet + * @param string $batch_number batch number for objet + * @return int <0 if KO, >0 if OK + */ + function find($fk_product_stock=0, $eatby='',$sellby='',$batch_number='') + { + global $langs; + $where = array(); + $sql = "SELECT"; + $sql.= " t.rowid,"; + $sql.= " t.tms,"; + $sql.= " t.fk_product_stock,"; + $sql.= " t.sellby,"; + $sql.= " t.eatby,"; + $sql.= " t.batch,"; + $sql.= " t.qty,"; + $sql.= " t.import_key"; + $sql.= " FROM ".MAIN_DB_PREFIX.self::$_table_element." as t"; + $sql.= " WHERE fk_product_stock=".$fk_product_stock; + + if (! empty($eatby)) array_push($where," eatby = '".$this->db->idate($eatby)."'"); + if (! empty($sellby)) array_push($where," sellby = '".$this->db->idate($sellby)."'"); + if (! empty($batch_number)) $sql.= " AND batch = '".$this->db->escape($batch_number)."'"; + + if (! empty($where)) $sql.= " AND (".implode(" OR ",$where).")"; + + dol_syslog(get_class($this)."::fetch sql=".$sql, LOG_DEBUG); + $resql=$this->db->query($sql); + if ($resql) + { + if ($this->db->num_rows($resql)) + { + $obj = $this->db->fetch_object($resql); + + $this->id = $obj->rowid; + + $this->tms = $this->db->jdate($obj->tms); + $this->fk_product_stock = $obj->fk_product_stock; + $this->sellby = $this->db->jdate($obj->sellby); + $this->eatby = $this->db->jdate($obj->eatby); + $this->batch = $obj->batch; + $this->qty = $obj->qty; + $this->import_key = $obj->import_key; + } + $this->db->free($resql); + + return 1; + } + else + { + $this->error="Error ".$this->db->lasterror(); + dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR); + return -1; + } + } + /** + * Return all batch detail records for given product and warehouse + * + * @param obj $db database object + * @param int $fk_product_stock id product_stock for objet + * @param int $with_qty doesn't return line with 0 quantity + * @return int <0 if KO, >0 if OK + */ + public static function findAll($db,$fk_product_stock,$with_qty=0) + { + global $langs; + $ret = array(); + $sql = "SELECT"; + $sql.= " t.rowid,"; + $sql.= " t.tms,"; + $sql.= " t.fk_product_stock,"; + $sql.= " t.sellby,"; + $sql.= " t.eatby,"; + $sql.= " t.batch,"; + $sql.= " t.qty,"; + $sql.= " t.import_key"; + + + $sql.= " FROM ".MAIN_DB_PREFIX.self::$_table_element." as t"; + $sql.= " WHERE fk_product_stock=".$fk_product_stock; + + if ($with_qty) $sql.= " AND qty<>0"; + dol_syslog("productbatch::findAll sql=".$sql, LOG_DEBUG); + $resql=$db->query($sql); + if ($resql) + { + $num = $db->num_rows($resql); + $i=0; + while ($i < $num) + { + $obj = $db->fetch_object($resql); + + $tmp = new productbatch($db); + $tmp->id = $obj->rowid; + $tmp->tms = $db->jdate($obj->tms); + $tmp->fk_product_stock = $obj->fk_product_stock; + $tmp->sellby = $db->jdate($obj->sellby); + $tmp->eatby = $db->jdate($obj->eatby); + $tmp->batch = $obj->batch; + $tmp->qty = $obj->qty; + $tmp->import_key = $obj->import_key; + + array_push($ret,$tmp); + $i++; + } + $db->free($resql); + + return $ret; + } + else + { + $error="Error ".$db->lasterror(); + dol_syslog("productbatch::find_all ".$error, LOG_ERR); + return -1; + } + } + +} +?> diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index 2652c7c45bd..143b9a8466c 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -2,6 +2,7 @@ /* Copyright (C) 2003-2006 Rodolphe Quiedeville * Copyright (C) 2005-2013 Laurent Destailleur * Copyright (C) 2011 Jean Heimburger + * Copyright (C) 2014 Cedric GROSS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -55,9 +56,13 @@ class MouvementStock * @param int $price Unit price HT of product, used to calculate average weighted price (PMP in french). If 0, average weighted price is not changed. * @param string $label Label of stock movement * @param string $datem Force date of movement + * @param date $eatby eat-by date + * @param date $sellby sell-by date + * @param string $batch batch number + * @param boolean $skip_sellby If set to true, stock mouvement is done without impacting batch record * @return int <0 if KO, 0 if fk_product is null, >0 if OK */ - function _create($user, $fk_product, $entrepot_id, $qty, $type, $price=0, $label='', $datem='') + function _create($user, $fk_product, $entrepot_id, $qty, $type, $price=0, $label='', $datem='',$eatby='',$sellby='',$batch='',$skip_sellby=false) { global $conf, $langs; @@ -132,6 +137,7 @@ class MouvementStock $num = 1; $oldqtywarehouse = $obj->reel; $oldpmpwarehouse = $obj->pmp; + $fk_product_stock = $obj->rowid; } $this->db->free($resql); } @@ -193,7 +199,17 @@ class MouvementStock $this->error=$this->db->lasterror(); dol_syslog(get_class($this)."::_create ".$this->error, LOG_ERR); $error = -3; + } else if(empty($fk_product_stock)){ + $fk_product_stock = $this->db->last_insert_id(MAIN_DB_PREFIX."product_stock"); } + + } + + // Update detail stock for sell-by date + if (($product->hasbatch()) && (! $error) && (! $skip_sellby)){ + $param_batch=array('fk_product_stock' =>$fk_product_stock, 'eatby'=>$eatby,'sellby'=>$sellby,'batchnumber'=>$batch); + $result=$this->_create_batch($param_batch, $qty); + if ($result<0) $error++; } if (! $error) @@ -314,9 +330,21 @@ class MouvementStock */ function livraison($user, $fk_product, $entrepot_id, $qty, $price=0, $label='', $datem='') { - return $this->_create($user, $fk_product, $entrepot_id, (0 - $qty), 2, $price, $label, $datem); + return $this->_create($user, $fk_product, $entrepot_id, (0 - $qty), 2, $price, $label, $datem,'','','',true); } + /** + * Decrease stock for batch record + * + * @param int $id_stock_dluo Id product_dluo + * @param int $qty Quantity + * @return int <0 if KO, >0 if OK + */ + function livraison_batch($id_stock_dluo, $qty) + { + $ret=$this->_create_batch($id_stock_dluo, (0 - $qty)); + return $ret; + } /** * Increase stock for product and subproducts @@ -327,11 +355,14 @@ class MouvementStock * @param int $qty Quantity * @param int $price Price * @param string $label Label of stock movement + * @param date $eatby eat-by date + * @param date $sellby sell-by date + * @param string $batch batch number * @return int <0 if KO, >0 if OK */ - function reception($user, $fk_product, $entrepot_id, $qty, $price=0, $label='') + function reception($user, $fk_product, $entrepot_id, $qty, $price=0, $label='', $eatby='', $sellby='', $batch='') { - return $this->_create($user, $fk_product, $entrepot_id, $qty, 3, $price, $label); + return $this->_create($user, $fk_product, $entrepot_id, $qty, 3, $price, $label, '', $eatby, $sellby, $batch); } @@ -384,6 +415,59 @@ class MouvementStock return -1; } } + + /** + * Create or update batch record + * + * @param variant $dluo Could be either int if id of product_batch or array with at leat fk_product_stock + * @param int $qty Quantity of product with batch number + * @return int <0 if KO, else return productbatch id + */ + function _create_batch($dluo, $qty ) { + $pdluo=New Productbatch($this->db); + + //Try to find an existing record with batch same batch number or id + if (is_numeric($dluo)) { + $result=$pdluo->fetch($dluo); + } else if (is_array($dluo)) { + if (isset($dluo['fk_product_stock'])) { + $vfk_product_stock=$dluo['fk_product_stock']; + $veatby = $dluo['eatby']; + $vsellby = $dluo['sellby']; + $vbatchnumber = $dluo['batchnumber']; + $result = $pdluo->find($vfk_product_stock,$veatby,$vsellby,$vbatchnumber); + } else { + dol_syslog(get_class($this)."::_create_batch array param dluo must contain at least key fk_product_stock".$error, LOG_ERR); + $result = -1; + } + } else { + dol_syslog(get_class($this)."::_create_batch error invalid param dluo".$error, LOG_ERR); + $result = -1; + } + + //batch record found so we update it + if ($result>0) { + if ($pdluo->id >0) { + $pdluo->qty +=$qty; + if ($pdluo->qty == 0) { + $result=$pdluo->delete(0,1); + } else { + $result=$pdluo->update(0,1); + } + } else { + $pdluo->fk_product_stock=$vfk_product_stock; + $pdluo->qty = $qty; + $pdluo->eatby = $veatby; + $pdluo->sellby = $vsellby; + $pdluo->batch = $vbatchnumber; + $result=$pdluo->create(0,1); + } + return $result; + } else { + return -1; + } + + } } ?> diff --git a/htdocs/product/stock/product.php b/htdocs/product/stock/product.php index 3d7acc0661b..1fd34c65c2d 100644 --- a/htdocs/product/stock/product.php +++ b/htdocs/product/stock/product.php @@ -6,6 +6,7 @@ * Copyright (C) 2005-2009 Regis Houssin * Copyright (C) 2013 Cédric Salvador * Copyright (C) 2013 Juanjo Menent + * Copyright (C) 2014 Cédric Gross * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,11 +33,14 @@ require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php'; require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php'; require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php'; +if (! empty($conf->productbatch->enabled)) require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php'; $langs->load("products"); $langs->load("orders"); $langs->load("bills"); $langs->load("stocks"); +if (! empty($conf->productbatch->enabled)) $langs->load("productbatch"); + $action=GETPOST("action"); $cancel=GETPOST('cancel'); @@ -99,14 +103,42 @@ if ($action == "correct_stock" && ! $cancel) $action='correction'; } + if (! empty($conf->productbatch->enabled)) + { + $product = new Product($db); + $result=$product->fetch($id); + + if ($product->hasbatch() && (! GETPOST("sellby")) && (! GETPOST("eatby")) && (! GETPOST("batch_number"))) { + setEventMessage($langs->trans("ErrorFieldRequired",$langs->transnoentitiesnoconv("atleast1batchfield")), 'errors'); + $error++; + $action='correction'; + } + } + if (! $error) { $priceunit=price2num(GETPOST("price")); if (is_numeric(GETPOST("nbpiece")) && $id) { + if (empty($product)) { $product = new Product($db); $result=$product->fetch($id); - + } + if ($product->hasbatch()) { + $d_eatby=dol_mktime(12, 0, 0, $_POST['eatbymonth'], $_POST['eatbyday'], $_POST['eatbyyear']); + $d_sellby=dol_mktime(12, 0, 0, $_POST['sellbymonth'], $_POST['sellbyday'], $_POST['sellbyyear']); + $result=$product->correct_stock_batch( + $user, + GETPOST("id_entrepot"), + GETPOST("nbpiece"), + GETPOST("mouvement"), + GETPOST("label"), + $priceunit, + $d_eatby, + $d_sellby, + GETPOST('batch_number') + ); // We do not change value of stock for a correction + } else { $result=$product->correct_stock( $user, GETPOST("id_entrepot"), @@ -115,6 +147,7 @@ if ($action == "correct_stock" && ! $cancel) GETPOST("label"), $priceunit ); // We do not change value of stock for a correction + } if ($result > 0) { @@ -246,6 +279,12 @@ if ($id > 0 || $ref) print $product->getLibStatut(2,1); print ''; + if ($conf->productbatch->enabled) { + print ''.$langs->trans("Status").' ('.$langs->trans("l_sellby").')'; + print $product->getLibStatut(2,2); + print ''; + } + // PMP print ''.$langs->trans("AverageUnitPricePMP").''; print ''.price($product->pmp).' '.$langs->trans("HT").''; @@ -405,7 +444,7 @@ if ($id > 0 || $ref) // Warehouse print ''; - print ''.$langs->trans("Warehouse").''; + print ''.$langs->trans("Warehouse").''; print ''; print $formproduct->selectWarehouses(($_GET["dwid"]?$_GET["dwid"]:GETPOST('id_entrepot')),'id_entrepot','',1); print ''; @@ -419,13 +458,26 @@ if ($id > 0 || $ref) // Label print ''; - print ''.$langs->trans("Label").''; + print ''.$langs->trans("Label").''; print ''; print ''; print ''; print ''.$langs->trans("UnitPurchaseValue").''; print ''; + //eat-by date + if ((! empty($conf->productbatch->enabled)) && $product->hasbatch()) { + print ''; + print ''.$langs->trans("l_eatby").''; + $form->select_date('','eatby','','',1,""); + print ''; + print ''.$langs->trans("l_sellby").''; + $form->select_date('','sellby','','',1,""); + print ''; + print ''.$langs->trans("batch_number").''; + print ''; + print ''; + } print ''; print '
 '; @@ -513,7 +565,7 @@ if (empty($action) && $product->id) print ''.$langs->trans("StockCorrection").''; } - if ($user->rights->stock->mouvement->creer) + if (($user->rights->stock->mouvement->creer) && !$product->hasbatch()) { print ''.$langs->trans("StockMovement").''; } @@ -528,15 +580,22 @@ if (empty($action) && $product->id) * Contenu des stocks */ print '
'; -print ''; +print ''; print ''; print ''; print ''; print ''; print ''; print ''; +if ( (! empty($conf->productbatch->enabled)) && $product->hasbatch()) { + print ''; + print ''; + print ''; + print ''; + print ''; +} -$sql = "SELECT e.rowid, e.label, ps.reel, ps.pmp"; +$sql = "SELECT e.rowid, e.label, ps.reel, ps.pmp, ps.rowid as product_stock_id"; $sql.= " FROM ".MAIN_DB_PREFIX."entrepot as e,"; $sql.= " ".MAIN_DB_PREFIX."product_stock as ps"; $sql.= " WHERE ps.reel != 0"; @@ -561,7 +620,7 @@ if ($resql) $entrepotstatic->id=$obj->rowid; $entrepotstatic->libelle=$obj->label; print ''; - print ''; + print ''; print ''; // PMP print ''; // Ditto : Show PMP from movement or from product @@ -579,12 +638,24 @@ if ($resql) if (price2num($obj->pmp)) $totalwithpmp += $obj->reel; $totalvalue = $totalvalue + price2num($obj->pmp*$obj->reel,'MU'); // Ditto : Show PMP from movement or from product $totalvaluesell = $totalvaluesell + price2num($product->price*$obj->reel,'MU'); // Ditto : Show PMP from movement or from product + //Batch Detail + if ((! empty($conf->productbatch->enabled)) && $product->hasbatch()) { + $details=Productbatch::findAll($db,$obj->product_stock_id); + if ($details<0) dol_print_error($db); + foreach ($details as $pdluo) { + print "\n".''; + print ''; + print ''; + print ''; + print ''; + } + } $i++; $var=!$var; } } else dol_print_error($db); -print ''; +print ''; print ''; print '
'.$langs->trans("Warehouse").'
'.$langs->trans("Warehouse").''.$langs->trans("NumberOfUnit").''.$langs->trans("AverageUnitPricePMPShort").''.$langs->trans("EstimatedStockValueShort").''.$langs->trans("SellPriceMin").''.$langs->trans("EstimatedStockValueSellShort").'
'.$langs->trans("l_eatby").''.$langs->trans("l_sellby").''.$langs->trans("batch_number").'
'.$entrepotstatic->getNomUrl(1).''.$entrepotstatic->getNomUrl(1).''.$obj->reel.($obj->reel<0?' '.img_warning():'').''.(price2num($obj->pmp)?price2num($obj->pmp,'MU'):'').'
'. dol_print_date($pdluo->eatby,'day') .''. dol_print_date($pdluo->sellby,'day') .''.$pdluo->batch.''.$pdluo->qty.($pdluo->qty<0?' '.img_warning():'').'
'.$langs->trans("Total").':
'.$langs->trans("Total").':'.$total.''; print ($totalwithpmp?price($totalvalue/$totalwithpmp):' ');