diff --git a/htdocs/admin/inventory.php b/htdocs/admin/inventory.php new file mode 100644 index 00000000000..2548494fc79 --- /dev/null +++ b/htdocs/admin/inventory.php @@ -0,0 +1,152 @@ + + * Copyright (C) 2015 ATM Consulting + * + * 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 admin/inventory.php + * \ingroup inventory + * \brief This file is an example module setup page + * Put some comments here + */ +// Dolibarr environment +require '../main.inc.php'; + + +// Libraries +require_once DOL_DOCUMENT_ROOT . "/core/lib/admin.lib.php"; +require_once DOL_DOCUMENT_ROOT .'/inventory/lib/inventory.lib.php'; + +// Translations +$langs->load("stock"); +$langs->load("inventory"); + +// Access control +if (! $user->admin) { + accessforbidden(); +} + +// Parameters +$action = GETPOST('action', 'alpha'); + +/* + * Actions + */ +if (preg_match('/set_(.*)/',$action,$reg)) +{ + $code=$reg[1]; + if (dolibarr_set_const($db, $code, GETPOST($code), 'chaine', 0, '', $conf->entity) > 0) + { + header("Location: ".$_SERVER["PHP_SELF"]); + exit; + } + else + { + dol_print_error($db); + } +} + +if (preg_match('/del_(.*)/',$action,$reg)) +{ + $code=$reg[1]; + if (dolibarr_del_const($db, $code, 0) > 0) + { + Header("Location: ".$_SERVER["PHP_SELF"]); + exit; + } + else + { + dol_print_error($db); + } +} + +/* + * View + */ +$page_name = "inventorySetup"; +llxHeader('', $langs->trans($page_name)); + +// Subheader +$linkback = '' + . $langs->trans("BackToModuleList") . ''; +print_fiche_titre($langs->trans($page_name), $linkback); + +// Configuration header +$head = inventoryAdminPrepareHead(); +dol_fiche_head( + $head, + 'settings', + $langs->trans("Module104420Name"), + 0, + "inventory@inventory" +); + +// Setup page goes here +$form=new Form($db); +$var=false; +print ''; +print ''; +print ''."\n"; +print ''; +print ''."\n"; + +// Example with a yes / no select +$var=!$var; +print ''; +print ''; +print ''; +print ''; + +// Example with a yes / no select +$var=!$var; +print ''; +print ''; +print ''; +print ''; + +// Example with a yes / no select +$var=!$var; +print ''; +print ''; +print ''; +print ''; + +print '
'.$langs->trans("Parameters").' '.$langs->trans("Value").'
'.$langs->trans("INVENTORY_DISABLE_VIRTUAL").' '; +print '
'; +print ''; +print ''; +print $form->selectyesno("INVENTORY_DISABLE_VIRTUAL",$conf->global->INVENTORY_DISABLE_VIRTUAL,1); +print ''; +print '
'; +print '
'.$langs->trans("INVENTORY_USE_MIN_PA_IF_NO_LAST_PA").' '; +print '
'; +print ''; +print ''; +print $form->selectyesno("INVENTORY_USE_MIN_PA_IF_NO_LAST_PA",$conf->global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA,1); +print ''; +print '
'; +print '
'.$langs->trans("INVENTORY_USE_INVENTORY_DATE_FROM_DATEMVT").' '; +print '
'; +print ''; +print ''; +print $form->selectyesno("INVENTORY_USE_INVENTORY_DATE_FROM_DATEMVT",$conf->global->INVENTORY_USE_INVENTORY_DATE_FROM_DATEMVT,1); +print ''; +print '
'; +print '
'; + +llxFooter(); + +$db->close(); \ No newline at end of file diff --git a/htdocs/core/class/coreobject.class.php b/htdocs/core/class/coreobject.class.php new file mode 100644 index 00000000000..396caf029cc --- /dev/null +++ b/htdocs/core/class/coreobject.class.php @@ -0,0 +1,673 @@ + + * + * 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 htdocs/core/class/coreobject.inventory.php + * \ingroup core + * \brief File of class to manage all object. Might be replace or merge into commonobject + */ + +require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; + +class CoreObject extends CommonObject +{ + public $withChild = true; + + /** + * @var Array $_fields Fields to synchronize with Database + */ + protected $__fields=array(); + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + function __construct(DoliDB &$db) + { + $this->db = $db; + } + + /** + * Function to init fields + * + * @return bool + */ + protected function init() + { + $this->id = 0; + $this->datec = 0; + $this->tms = 0; + + if (!empty($this->__fields)) + { + foreach ($this->__fields as $field=>$info) + { + if ($this->is_date($info)) $this->{$field} = time(); + elseif ($this->is_array($info)) $this->{$field} = array(); + elseif ($this->is_int($info)) $this->{$field} = (int) 0; + elseif ($this->is_float($info)) $this->{$field} = (double) 0; + else $this->{$field} = ''; + } + + $this->to_delete=false; + $this->is_clone=false; + + return true; + } + else + { + return false; + } + + } + + /** + * Test type of field + * + * @param string $field name of field + * @param string $type type of field to test + * @return value of field or false + */ + private function checkFieldType($field, $type) + { + if (isset($this->__fields[$field]) && method_exists($this, 'is_'.$type)) + { + return $this->{'is_'.$type}($this->__fields[$field]); + } + else + { + return false; + } + } + + /** + * Function test if type is date + * + * @param array $info content informations of field + * @return bool + */ + private function is_date($info) + { + if(isset($info['type']) && $info['type']=='date') return true; + else return false; + } + + /** + * Function test if type is array + * + * @param array $info content informations of field + * @return bool + */ + private function is_array($info) + { + if(is_array($info)) + { + if(isset($info['type']) && $info['type']=='array') return true; + else return false; + } + else return false; + } + + /** + * Function test if type is null + * + * @param array $info content informations of field + * @return bool + */ + private function is_null($info) + { + if(is_array($info)) + { + if(isset($info['type']) && $info['type']=='null') return true; + else return false; + } + else return false; + } + + /** + * Function test if type is integer + * + * @param array $info content informations of field + * @return bool + */ + private function is_int($info) + { + if(is_array($info)) + { + if(isset($info['type']) && ($info['type']=='int' || $info['type']=='integer' )) return true; + else return false; + } + else return false; + } + + /** + * Function test if type is float + * + * @param array $info content informations of field + * @return bool + */ + private function is_float($info) + { + if(is_array($info)) + { + if(isset($info['type']) && $info['type']=='float') return true; + else return false; + } + else return false; + } + + /** + * Function test if type is text + * + * @param array $info content informations of field + * @return bool + */ + private function is_text($info) + { + if(is_array($info)) + { + if(isset($info['type']) && $info['type']=='text') return true; + else return false; + } + else return false; + } + + /** + * Function test if is indexed + * + * @param array $info content informations of field + * @return bool + */ + private function is_index($info) + { + if(is_array($info)) + { + if(isset($info['index']) && $info['index']==true) return true; + else return false; + } + else return false; + } + + + /** + * Function to prepare the values to insert + * + * @return array + */ + private function set_save_query() + { + $query=array(); + foreach ($this->__fields as $field=>$info) + { + if($this->is_date($info)) + { + if(empty($this->{$field})) + { + $query[$field] = NULL; + } + else + { + $query[$field] = $this->db->idate($this->{$field}); + } + } + else if($this->is_array($info)) + { + $query[$field] = serialize($this->{$field}); + } + else if($this->is_int($info)) + { + $query[$field] = (int) price2num($this->{$field}); + } + else if($this->is_float($info)) + { + $query[$field] = (double) price2num($this->{$field}); + } + elseif($this->is_null($info)) + { + $query[$field] = (is_null($this->{$field}) || (empty($this->{$field}) && $this->{$field}!==0 && $this->{$field}!=='0') ? null : $this->{$field}); + } + else + { + $query[$field] = $this->{$field}; + } + } + + return $query; + } + + + /** + * Function to concat keys of fields + * + * @return string + */ + private function get_field_list() + { + $keys = array_keys($this->__fields); + return implode(',', $keys); + } + + + /** + * Function to load data into current object this + * + * @param stdClass $obj Contain data of object from database + */ + private function set_vars_by_db(&$obj) + { + foreach ($this->__fields as $field => $info) + { + if($this->is_date($info)) + { + if(empty($obj->{$field}) || $obj->{$field} === '0000-00-00 00:00:00' || $obj->{$field} === '1000-01-01 00:00:00') $this->{$field} = 0; + else $this->{$field} = strtotime($obj->{$field}); + } + elseif($this->is_array($info)) + { + $this->{$field} = @unserialize($obj->{$field}); + // Hack for data not in UTF8 + if($this->{$field } === FALSE) @unserialize(utf8_decode($obj->{$field})); + } + elseif($this->is_int($info)) + { + $this->{$field} = (int) $obj->{$field}; + } + elseif($this->is_float($info)) + { + $this->{$field} = (double) $obj->{$field}; + } + elseif($this->is_null($info)) + { + $val = $obj->{$field}; + // zero is not null + $this->{$field} = (is_null($val) || (empty($val) && $val!==0 && $val!=='0') ? null : $val); + } + else + { + $this->{$field} = $obj->{$field}; + } + + } + } + + /** + * Get object and children from database + * + * @param int $id Id of object to load + * @param bool $loadChild used to load children from database + * @return int >0 if OK, <0 if KO, 0 if not found + */ + public function fetch($id, $loadChild = true) + { + if (empty($id)) return false; + + $sql = 'SELECT '.$this->get_field_list().', datec, tms'; + $sql.= ' FROM '.MAIN_DB_PREFIX.$this->table_element; + $sql.= ' WHERE rowid = '.$id; + + $res = $this->db->query($sql); + if($obj = $this->db->fetch_object($res)) + { + $this->id = $id; + $this->set_vars_by_db($obj); + + $this->datec = $this->db->idate($obj->datec); + $this->tms = $this->db->idate($obj->tms); + + if ($loadChild) $this->fetchChild(); + + return $this->id; + } + else + { + $this->error = $this->db->lasterror(); + $this->errors[] = $this->error; + return -1; + } + } + + + /** + * Function to instantiate a new child + * + * @param string $tabName Table name of child + * @param int $id If id is given, we try to return his key if exist or load if we try_to_load + * @param string $key Attribute name of the object id + * @param bool $try_to_load Force the fetch if an id is given + * @return int + */ + public function addChild($tabName, $id=0, $key='id', $try_to_load = false) + { + if(!empty($id)) + { + foreach($this->{$tabName} as $k=>&$object) + { + if($object->{$key} === $id) return $k; + } + } + + $k = count($this->{$tabName}); + + $className = ucfirst($tabName); + $this->{$tabName}[$k] = new $className($this->db); + if($id>0 && $key==='id' && $try_to_load) + { + $this->{$tabName}[$k]->fetch($id); + } + + return $k; + } + + + /** + * Function to set a child as to delete + * + * @param string $tabName Table name of child + * @param int $id Id of child to set as to delete + * @param string $key Attribute name of the object id + * @return bool + */ + public function removeChild($tabName, $id, $key='id') + { + foreach ($this->{$tabName} as &$object) + { + if ($object->{$key} == $id) + { + $object->to_delete = true; + return true; + } + } + return false; + } + + + /** + * Function to fetch children objects + */ + public function fetchChild() + { + if($this->withChild && !empty($this->childtables) && !empty($this->fk_element)) + { + foreach($this->childtables as &$childTable) + { + $className = ucfirst($childTable); + + $this->{$className}=array(); + + $sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.$childTable.' WHERE '.$this->fk_element.' = '.$this->id; + $res = $this->db->query($sql); + + if($res) + { + while($obj = $this->db->fetch_object($res)) + { + $o=new $className($this->db); + $o->fetch($obj->rowid); + + $this->{$className}[] = $o; + } + } + else + { + $this->errors[] = $this->db->lasterror(); + } + } + } + } + + /** + * Function to update children data + * + * @param User $user user object + */ + public function saveChild(User &$user) + { + if($this->withChild && !empty($this->childtables) && !empty($this->fk_element)) + { + foreach($this->childtables as &$childTable) + { + $className = ucfirst($childTable); + if(!empty($this->{$className})) + { + foreach($this->{$className} as $i => &$object) + { + $object->{$this->fk_element} = $this->id; + + $object->update($user); + if($this->unsetChildDeleted && isset($object->to_delete) && $object->to_delete==true) unset($this->{$className}[$i]); + } + } + } + } + } + + + /** + * Function to update object or create or delete if needed + * + * @param User $user user object + * @return < 0 if ko, > 0 if ok + */ + public function update(User &$user) + { + if (empty($this->id)) return $this->create($user); // To test, with that, no need to test on high level object, the core decide it, update just needed + elseif (isset($this->to_delete) && $this->to_delete==true) return $this->delete($user); + + $error = 0; + $this->db->begin(); + + $query = $this->set_save_query(); + $query['rowid'] = $this->id; + + $res = $this->db->update($this->table_element, $query, array('rowid')); + if ($res) + { + $result = $this->call_trigger(strtoupper($this->element). '_UPDATE', $user); + if ($result < 0) $error++; + else $this->saveChild($user); + } + else + { + $error++; + $this->error = $this->db->lasterror(); + $this->errors[] = $this->error; + } + + if (empty($error)) + { + $this->db->commit(); + return $this->id; + } + else + { + $this->db->rollback(); + return -1; + } + + } + + /** + * Function to create object in database + * + * @param User $user user object + * @return < 0 if ko, > 0 if ok + */ + public function create(User &$user) + { + if($this->id > 0) return $this->update($user); + + $error = 0; + $this->db->begin(); + + $query = $this->set_save_query(); + $query['datec'] = date("Y-m-d H:i:s", dol_now()); + + $res = $this->db->insert($this->table_element, $query); + if($res) + { + $this->id = $this->db->last_insert_id($this->table_element); + + $result = $this->call_trigger(strtoupper($this->element). '_CREATE', $user); + if ($result < 0) $error++; + else $this->saveChild($user); + } + else + { + $error++; + $this->error = $this->db->lasterror(); + $this->errors[] = $this->error; + } + + if (empty($error)) + { + $this->db->commit(); + return $this->id; + } + else + { + $this->db->rollback(); + return -1; + } + } + + /** + * Function to delete object in database + * + * @param User $user user object + * @return < 0 if ko, > 0 if ok + */ + public function delete(User &$user) + { + if ($this->id <= 0) return 0; + + $error = 0; + $this->db->begin(); + + $result = $this->call_trigger(strtoupper($this->element). '_DELETE', $user); + if ($result < 0) $error++; + + if (!$error) + { + $this->db->delete($this->table_element, array('rowid' => $this->id), array('rowid')); + if($this->withChild && !empty($this->childtables)) + { + foreach($this->childtables as &$childTable) + { + $className = ucfirst($childTable); + if (!empty($this->{$className})) + { + foreach($this->{$className} as &$object) + { + $object->delete($user); + } + } + } + } + } + + if (empty($error)) + { + $this->db->commit(); + return 1; + } + else + { + $this->error = $this->db->lasterror(); + $this->errors[] = $this->error; + $this->db->rollback(); + return -1; + } + } + + + /** + * Function to get a formatted date + * + * @param string $field Attribute to return + * @param string $format Output date format + * @return string + */ + public function getDate($field, $format='') + { + if(empty($this->{$field})) return ''; + else + { + return dol_print_date($this->{$field}, $format); + } + } + + /** + * Function to set date in field + * + * @param string $field field to set + * @param string $date formatted date to convert + * @return mixed + */ + public function setDate($field, $date) + { + if (empty($date)) + { + $this->{$field} = 0; + } + else + { + require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php'; + $this->{$field} = dol_stringtotime($date); + } + + return $this->{$field}; + } + + + /** + * Function to update current object + * + * @param array $Tab Array of values + * @return int + */ + public function setValues(&$Tab) + { + foreach ($Tab as $key => $value) + { + if($this->checkFieldType($key, 'date')) + { + $this->setDate($key, $value); + } + else if( $this->checkFieldType($key, 'array')) + { + $this->{$key} = $value; + } + else if( $this->checkFieldType($key, 'float') ) + { + $this->{$key} = (double) price2num($value); + } + else if( $this->checkFieldType($key, 'int') ) { + $this->{$key} = (int) price2num($value); + } + else + { + $this->{$key} = @stripslashes($value); + } + } + + return 1; + } + +} diff --git a/htdocs/core/class/listview.class.php b/htdocs/core/class/listview.class.php new file mode 100644 index 00000000000..dc2dbbcd161 --- /dev/null +++ b/htdocs/core/class/listview.class.php @@ -0,0 +1,1039 @@ + + + This program and all files within this directory and sub directory + 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 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 . + */ + +/** + * Class to manage the lists view + */ +class Listview +{ + /** + * Constructor + * + * @param DoliDB $db Database handler + * @param string $id html id + */ + function __construct(&$db, $id) + { + $this->db = &$db; + $this->id = $id; + $this->TTotalTmp=array(); + $this->sql = ''; + $this->form = null; + $this->totalRowToShow=0; + $this->totalRow=0; + } + + /** + * Function to init fields + * + * @param array $TParam array of configuration of list + * @return bool + */ + private function init(&$TParam) + { + global $conf, $langs, $user; + + if(!isset($TParam['hide'])) $TParam['hide']=array(); + if(!isset($TParam['link'])) $TParam['link']=array(); + if(!isset($TParam['type'])) $TParam['type']=array(); + if(!isset($TParam['orderby']['noOrder'])) $TParam['orderby']['noOrder']=array(); + if(!isset($TParam['allow-fields-select'])) $TParam['allow-fields-select'] = 0; + + if(!isset($TParam['list']))$TParam['list']=array(); + $TParam['list'] = array_merge(array( + 'messageNothing'=>$langs->trans('ListMessageNothingToShow') + ,'noheader'=>0 + ,'useBottomPagination'=>0 + ,'image'=>'' + ,'title'=>$langs->trans('List') + ,'orderDown'=>'' + ,'orderUp'=>'' + ,'id'=>$this->id + ,'head_search'=>'' + ,'export'=>array() + ,'view_type'=>'' + ),$TParam['list']); + + if (empty($TParam['limit'])) $TParam['limit'] = array(); + + $page = GETPOST('page'); + if (!empty($page)) $TParam['limit']['page'] = $page+1; // TODO dolibarr start page at 0 instead 1 + + $TParam['limit'] = array_merge(array('page'=>1, 'nbLine' => $conf->liste_limit, 'global'=>0), $TParam['limit']); + + if (GETPOST('sortfield')) + { + $TParam['sortfield'] = GETPOST('sortfield'); + $TParam['sortorder'] = GETPOST('sortorder'); + } + + include_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php'; + $this->form = new Form($this->db); + } + + + /** + * Function to know if we can search on null value + * @param string $key field name + * @param array $TParam array of configuration + * @return bool + */ + private function getSearchNull($key, &$TParam) + { + return !empty($TParam['search'][$key]['allow_is_null']); + } + + /** + * @param string $key field name + * @param array $TParam array of configuration + * @return array + */ + private function getSearchKey($key, &$TParam) + { + $TPrefixe = array(); + if(!empty($TParam['search'][$key]['table'])) + { + if (!is_array($TParam['search'][$key]['table'])) $TParam['search'][$key]['table'] = array($TParam['search'][$key]['table']); + + foreach ($TParam['search'][$key]['table'] as $prefix_table) + { + $TPrefixe[] = $prefix_table.'.'; + } + } + + $TKey=array(); + if(!empty($TParam['search'][$key]['field'])) + { + if (!is_array($TParam['search'][$key]['field'])) $TParam['search'][$key]['field'] = array($TParam['search'][$key]['field']); + + foreach ($TParam['search'][$key]['field'] as $i => $field) + { + $prefixe = !empty($TPrefixe[$i]) ? $TPrefixe[$i] : $TPrefixe[0]; + $TKey[] = $prefixe. $field ; + } + } + else + { + $TKey[] = $TPrefixe[0].$key; + } + + return $TKey; + } + + /** + * @param timestamp $date date to convert + * @return int|string Date TMS or '' + */ + private function dateToSQLDate($date) + { + return $this->db->idate($date); + } + + + /** + * @param array $TSQLMore contain some additional sql instructions + * @param string $value date with read format + * @param string $sKey field name + */ + private function addSqlFromTypeDate(&$TSQLMore, &$value, $sKey) + { + if(is_array($value)) + { + $TSQLDate=array(); + if(!empty($value['start'])) + { +// $valueDeb = $this->dateToSQLDate($value['start'].' 00:00:00'); + $TSQLDate[]=$sKey." >= '".$value['start']."'" ; + } + + if(!empty($value['end'])) + { +// $valueFin = $this->dateToSQLDate($value['end'].' 23:59:59'); + $TSQLDate[]=$sKey." <= '".$value['end']."'" ; + } + + if(!empty($TSQLDate)) $TSQLMore[] = implode(' AND ', $TSQLDate); + } + else + { +// $value = $this->dateToSQLDate($value); + $TSQLMore[]=$sKey." LIKE '".$value."%'" ; + } + } + + + /** + * @param array $TSQLMore contain some additional sql instructions + * @param string $value value to filter + * @param array $TParam array of configuration + * @param string $sKey field name + * @param string $key reference of sKey to find value into TParam + * @return bool + */ + private function addSqlFromOther(&$TSQLMore, &$value, &$TParam, $sKey, $key) + { + // Do not use empty() function, statut 0 exist + if ($value == '') return false; + elseif($value==-1) return false; + + if(isset($TParam['operator'][$key])) + { + if($TParam['operator'][$key] == '<' || $TParam['operator'][$key] == '>' || $TParam['operator'][$key]=='=') + { + $TSQLMore[] = $sKey . ' ' . $TParam['operator'][$key] . ' "' . $value . '"'; + } + elseif ($TParam['operator'][$key]=='IN') + { + $TSQLMore[] = $sKey . ' ' . $TParam['operator'][$key] . ' (' . $value . ')'; + } + else + { + if(strpos($value,'%')===false) $value = '%'.$value.'%'; + $TSQLMore[]=$sKey." LIKE '".addslashes($value)."'" ; + } + } + else + { + if(strpos($value,'%')===false) $value = '%'.$value.'%'; + $TSQLMore[]=$sKey." LIKE '".addslashes($value)."'" ; + } + + return true; + } + + + /** + * @param string $sql standard select sql + * @param array $TParam array of configuration + * @return string + */ + private function search($sql, &$TParam) + { + $ListPOST = GETPOST('Listview'); + + if (!GETPOST("button_removefilter_x") && !GETPOST("button_removefilter.x") && !GETPOST("button_removefilter")) + { + foreach ($TParam['search'] as $field => $info) + { + $TsKey = $this->getSearchKey($field, $TParam); + $TSQLMore = array(); + $allow_is_null = $this->getSearchNull($field,$TParam); + + foreach ($TsKey as $i => &$sKey) + { + $value = ''; + if (isset($ListPOST[$this->id]['search'][$field])) $value = $ListPOST[$this->id]['search'][$field]; + + if ($allow_is_null && !empty($ListPOST[$this->id]['search_on_null'][$field])) + { + $TSQLMore[] = $sKey.' IS NULL '; + $value = ''; + } + + if (isset($TParam['type'][$field]) && ($TParam['type'][$field]==='date' || $TParam['type'][$field]==='datetime')) + { + $k = 'Listview_'.$this->id.'_search_'.$field; + if ($info['search_type'] === 'calendars') + { + $value = array(); + + $timestart = dol_mktime(0, 0, 0, GETPOST($k.'_startmonth'), GETPOST($k.'_startday'), GETPOST($k.'_startyear')); + if ($timestart) $value['start'] = date('Y-m-d', $timestart); + + $timeend = dol_mktime(23, 59, 59, GETPOST($k.'_endmonth'), GETPOST($k.'_endday'), GETPOST($k.'_endyear')); + if ($timeend) $value['end'] = date('Y-m-d', $timeend); + } + else + { + $time = dol_mktime(12, 0, 0, GETPOST($k.'month'), GETPOST($k.'day'), GETPOST($k.'year')); + if ($time) $value = date('Y-m-d', $time); + } + + if (!empty($value)) $this->addSqlFromTypeDate($TSQLMore, $value, $sKey); + } + else + { + $this->addSqlFromOther($TSQLMore, $value, $TParam, $sKey, $field); + } + } + + if (!empty($TSQLMore)) + { + $sql.=' AND ( '.implode(' OR ',$TSQLMore).' ) '; + } + } + } + + if ($sqlGROUPBY!='') $sql.=' GROUP BY '.$sqlGROUPBY; + + return $sql; + } + + /** + * @param string $sql standard select sql + * @param array $TParam array of configuration + * @return string + */ + public function render($sql, $TParam=array()) + { + $TField=array(); + + $this->init($TParam); + $THeader = $this->initHeader($TParam); + + $sql = $this->search($sql,$TParam); + $sql = $this->order_by($sql, $TParam); + + $this->parse_sql($THeader, $TField, $TParam, $sql); + list($TTotal, $TTotalGroup)=$this->get_total($TField, $TParam); + + return $this->renderList($THeader, $TField, $TTotal, $TTotalGroup, $TParam); + } + + /** + * @param array $THeader the configuration of header + * @param array $TParam array of configuration + * @return array + */ + private function setSearch(&$THeader, &$TParam) + { + global $langs, $form; + + if(empty($TParam['search'])) return array(); + + $TSearch=array(); + + $nb_search_in_bar = 0; + + if(!empty($TParam['search'])) + { + foreach($THeader as $key => $libelle) + { + if(empty($TSearch[$key]))$TSearch[$key]=''; + } + } + + $ListPOST = GETPOST('Listview'); + $removeFilter = (GETPOST("button_removefilter_x") || GETPOST("button_removefilter.x") || GETPOST("button_removefilter")); + foreach($TParam['search'] as $key => $param_search) + { + $value = isset($ListPOST[$this->id]['search'][$key]) ? $ListPOST[$this->id]['search'][$key] : ''; + if ($removeFilter) $value = ''; + + $typeRecherche = (is_array($param_search) && isset($param_search['search_type'])) ? $param_search['search_type'] : $param_search; + + if(is_array($typeRecherche)) + { + $fsearch=$form->selectarray('Listview['.$this->id.'][search]['.$key.']', $typeRecherche,$value,1); + } + else if($typeRecherche==='calendar') + { + if (!$removeFilter) $value = GETPOST('Listview_'.$this->id.'_search_'.$key) ? mktime(0,0,0, (int) GETPOST('Listview_'.$this->id.'_search_'.$key.'month'), (int) GETPOST('Listview_'.$this->id.'_search_'.$key.'day'), (int) GETPOST('Listview_'.$this->id.'_search_'.$key.'year') ) : ''; + + $fsearch = $form->select_date($value, 'Listview_'.$this->id.'_search_'.$key,0, 0, 1, "", 1, 0, 1); + } + else if($typeRecherche==='calendars') + { + $value_start = $value_end = ''; + if (!$removeFilter) + { + $value_start = GETPOST('Listview_'.$this->id.'_search_'.$key.'_start') ? mktime(0,0,0, (int) GETPOST('Listview_'.$this->id.'_search_'.$key.'_startmonth'), (int) GETPOST('Listview_'.$this->id.'_search_'.$key.'_startday'), (int) GETPOST('Listview_'.$this->id.'_search_'.$key.'_startyear') ) : ''; + $value_end = GETPOST('Listview_'.$this->id.'_search_'.$key.'_end') ? mktime(0,0,0, (int) GETPOST('Listview_'.$this->id.'_search_'.$key.'_endmonth'), (int) GETPOST('Listview_'.$this->id.'_search_'.$key.'_endday'), (int) GETPOST('Listview_'.$this->id.'_search_'.$key.'_endyear') ) : ''; + } + + $fsearch = $form->select_date($value_start, 'Listview_'.$this->id.'_search_'.$key.'_start',0, 0, 1, "", 1, 0, 1) + . $form->select_date($value_end, 'Listview_'.$this->id.'_search_'.$key.'_end',0, 0, 1, "", 1, 0, 1); + + } + else if(is_string($typeRecherche)) + { + $fsearch=$TParam['search'][$key]; + } + else + { + $fsearch=''; + } + + if(!empty($param_search['allow_is_null'])) + { + $valueNull = isset($ListPOST[$this->id]['search_on_null'][$key]) ? 1 : 0; + $fsearch.=' '.$form->checkbox1('', 'Listview['.$this->id.'][search_on_null]['.$key.']',1, $valueNull,' onclick=" if($(this).is(\':checked\')){ $(this).prev().val(\'\'); }" ').img_help(1, $langs->trans('SearchOnNUllValue')); + } + + if(!empty($THeader[$key])) + { + $TSearch[$key] = $fsearch; + $nb_search_in_bar++; + } + else + { + $label = !empty($TParam['title'][$key]) ? $TParam['title'][$key] : $key ; + $TParam['list']['head_search'].= ''.$label.''; +// $TParam['list']['head_search'].='
'.$label.' '.$fsearch.'
'; + } + } + + $search_button = ' '.img_search().''; + + if(!empty($TParam['list']['head_search'])) + { + $TParam['list']['head_search']='
'.$search_button.'
'.$TParam['list']['head_search']; + } + + if($nb_search_in_bar>0) + { + end($TSearch); + list($key,$v) = each($TSearch); + $TSearch[$key].=$search_button; + } + else + { + $TSearch=array(); + } + + return $TSearch; + } + + /** + * Function to analyse and calculate the total from a column + * + * @param $TField + * @param $TParam + * @return array + */ + private function get_total(&$TField, &$TParam) + { + $TTotal=$TTotalGroup=array(); + + if(!empty($TParam['math']) && !empty($TField[0])) + { + foreach($TField[0] as $field=>$value) + { + $TTotal[$field]=''; + $TTotalGroup[$field] = ''; + } + + foreach($TParam['math'] as $field=>$typeMath) + { + if(is_array($typeMath)) + { + $targetField = $typeMath[1]; + $typeMath = $typeMath[0]; + } + else + { + $targetField = $field; + } + + if($typeMath == 'groupsum') + { + $TTotalGroup[$field] = array('target'=>$targetField, 'values'=> $this->TTotalTmp['@groupsum'][$targetField]); + } + else if($typeMath=='average') + { + $TTotal[$field]=array_sum($this->TTotalTmp[$targetField]) / count($this->TTotalTmp[$targetField]); + } + elseif($typeMath=='count') + { + $TTotal[$field]=count($this->TTotalTmp[$targetField]); + } + else + { + $TTotal[$field]=array_sum($this->TTotalTmp[$targetField]); + } + } + } + + return array($TTotal,$TTotalGroup); + } + + /** + * @return string + */ + private function getJS() + { + $javaScript = ''; + + return $javaScript; + } + + /** + * @param $TParam + * @param $TField + * @param $THeader + * @return array + */ + private function setExport(&$TParam, $TField, $THeader) + { + global $langs; + + $Tab=array(); + if(!empty($TParam['export'])) + { + $token = GETPOST('token'); + if(empty($token)) $token = md5($this->id.time().rand(1,9999)); + + $_SESSION['token_list_'.$token] = gzdeflate( serialize( array( + 'title'=>$this->title, + 'sql'=>$this->sql, + 'TBind'=>$this->TBind, + 'TChamps'=>$TField, + 'TEntete'=>$THeader + ))); + + foreach($TParam['export'] as $mode_export) + { + $Tab[] = array( + 'label'=>$langs->trans('Export'.$mode_export), + 'url'=>dol_buildpath('/abricot/downlist.php',1), + 'mode'=>$mode_export, + 'token'=>$token, + 'session_name'=>session_name() + ); + } + + } + + return $Tab; + } + + /** + * @param $TField + * @param $TTotalGroup + * @return array + */ + private function addTotalGroup($TField, $TTotalGroup) + { + global $langs; + + $Tab=array(); + $proto_total_line = array(); + $tagbase = $old_tagbase = null; + $addGroupLine = false; + + foreach($TField as $k=>&$line) + { + if(empty($proto_total_line)) + { + foreach($line as $field=>$value) + { + $proto_total_line[$field] = ''; + } + $group_line = $proto_total_line; + } + + $addGroupLine = false; + + $tagbase = ''; + foreach($line as $field=>$value) + { + if(!empty($TTotalGroup[$field])) + { + $tagbase.=$value.'|'; + $group_line[$field] = '
'.(empty($value) ? $langs->trans('Empty') : $value ).' :
'; + $group_line[$TTotalGroup[$field]['target']] = '
'.price($TTotalGroup[$field]['values'][$value]).'
'; + $addGroupLine = true; + } + } + + if(!is_null($old_tagbase) && $old_tagbase!=$tagbase && $addGroupLine) + { + $Tab[] = $previous_group_line; + } + + $old_tagbase = $tagbase; + $previous_group_line = $group_line; + $group_line = $proto_total_line; + + $Tab[] = $line; + } + + if($addGroupLine) + { + $Tab[] = $previous_group_line; + } + + return $Tab; + } + + /** + * @param $THeader + * @param $TField + * @param $TTotal + * @param $TTotalGroup + * @param $TParam + * @return string + */ + private function renderList(&$THeader, &$TField, &$TTotal, &$TTotalGroup, &$TParam) + { + global $bc; + + $TSearch = $this->setSearch($THeader, $TParam); + $TExport = $this->setExport($TParam, $TField, $THeader); + $TField = $this->addTotalGroup($TField,$TTotalGroup); + + $out = $this->getJS(); + + $dolibarr_decalage = $this->totalRow > $this->totalRowToShow ? 1 : 0; + ob_start(); + print_barre_liste($TParam['list']['title'], $TParam['limit']['page']-1, $_SERVER["PHP_SELF"], '&'.$TParam['list']['param_url'], $TParam['sortfield'], $TParam['sortorder'], '', $this->totalRowToShow+$dolibarr_decalage, $this->totalRow, $TParam['list']['image'], 0, '', '', $TParam['limit']['nbLine']); + $out .= ob_get_clean(); + + + $out.= ''; + + $out.= ''; + foreach($THeader as $field => $head) + { + $moreattrib = ''; + $search = ''; + $prefix = ''; + + if ($field === 'selectedfields') + { + $moreattrib = 'align="right" '; + $prefix = 'maxwidthsearch '; + } + + if (empty($head['width'])) $head['width'] = 'auto'; + if (!empty($head['width']) && !empty($head['text-align'])) $moreattrib .= 'style="width:'.$head['width'].';text-align:'.$head['text-align'].'"'; + + if (isset($TParam['search'][$field]['search_type']) && $TParam['search'][$field]['search_type'] !== false) + { + $TsKey = $this->getSearchKey($field, $TParam); + if (!empty($TsKey)) $search = implode(',', $TsKey); + else $search = $field; + } + + $out .= getTitleFieldOfList($head['label'], 0, $_SERVER["PHP_SELF"], $search, '', $moreparam, $moreattrib, $TParam['sortfield'], $TParam['sortorder'], $prefix); + $out .= $head['more']; + } + + //$out .= ''; + $out .= ''; + + if(count($TSearch)>0) + { + $out.=''; + + foreach ($THeader as $field => $head) + { + if ($field === 'selectedfields') + { + $out.= ''; + } + else + { + $moreattrib = 'style="width:'.$head['width'].';text-align:'.$head['text-align'].'"'; + $out .= ''; + } + } + + $out.=''; + } + + $out.=''; + + if(empty($TField)) + { + if (!empty($TParam['list']['messageNothing'])) $out .= ''; + } + else + { + $var=true; + $line_number = 0; + foreach($TField as $fields) + { + if($this->in_view($TParam, $line_number)) + { + $var=!$var; + $out.=''; + + foreach ($THeader as $field => $head) + { + $moreattrib = 'style="width:'.$head['width'].';text-align:'.$head['text-align'].'"'; + $out.=''; + } + + $out.=''; + } + + $line_number++; + } + + $out.=''; + + if (!empty($TParam['list']['haveTotal'])) + { + $out.=''; + + foreach ($THeader as $field => $head) + { + if (isset($TTotal[$field])) + { + $moreattrib = 'style="width:'.$head['width'].';text-align:'.$head['text-align'].'"'; + $out.=''; + } + } + + $out.=''; + } + } + + $out .= '
--
'.$this->form->showFilterAndCheckAddButtons(0).''.$TSearch[$field].'
'.$TParam['list']['messageNothing'].'
'.$fields[$field].'
'.price($TTotal[$field]).'
'; + + return $out; + } + + /** + * @param $db + * @param $TField + * @param array $TParam + */ + public function renderArray(&$db, $TField, $TParam=array()) + { + $this->typeRender = 'array'; + + $TField=array(); + + $this->init($TParam); + $THeader = $this->initHeader($TParam); + + $this->parse_array($THeader, $TField, $TParam); + list($TTotal, $TTotalGroup)=$this->get_total($TField, $TParam); + + $this->renderList($THeader, $TField, $TTotal, $TTotalGroup, $TParam); + } + + + /** + * @param $sql + * @param $TParam + * @return string + */ + private function order_by($sql, &$TParam) + { + global $db; + + if (!empty($TParam['sortfield'])) + { + if(strpos($sql,'LIMIT ') !== false) list($sql, $sqlLIMIT) = explode('LIMIT ', $sql); + + $sql .= $db->order($TParam['sortfield'], $TParam['sortorder']); + + if (!empty($sqlLIMIT)) $sql .= ' LIMIT '.$sqlLIMIT; + } + + return $sql; + } + + /** + * @param $THeader + * @param $TField + * @param $TParam + * @return bool + */ + private function parse_array(&$THeader, &$TField, &$TParam) + { + $this->totalRow = count($TField); + + $this->THideFlip = array_flip($TParam['hide']); + $this->TTotalTmp=array(); + + if (empty($TField)) return false; + + foreach($TField as $row) + { + $this->set_line($TField, $TParam, $row); + } + } + + + private function initHeader(&$TParam) + { + global $user,$conf; + + $THeader = array(); + + $TField=$TFieldVisibility=array(); + foreach ($TParam['title'] as $field => $value) + { + $TField[$field]=true; + } + + $contextpage=md5($_SERVER['PHP_SELF']); + if(!empty($TParam['allow-field-select'])) + { + $selectedfields = GETPOST('Listview'.$this->id.'_selectedfields'); + + if(!empty($selectedfields)) + { + include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; + $tabparam['MAIN_SELECTEDFIELDS_'.$contextpage] = $selectedfields; + $result=dol_set_user_param($this->db, $conf, $user, $tabparam); + } + + $tmpvar='MAIN_SELECTEDFIELDS_'.$contextpage; + if (! empty($user->conf->{$tmpvar})) + { + $tmparray = explode(',', $user->conf->{$tmpvar}); + $TParam['hide'] = array(); + foreach($TField as $field => $dummy) + { + $label = $TParam['title'][$field]; + if(!in_array($field, $tmparray)) + { + $TParam['hide'][] = $field; + $visible = 0; + } + else + { + $visible = 1; + } + + $TFieldVisibility[$field] = array( + 'label'=>$label + ,'checked'=>$visible + ); + } + } + else + { + foreach($TField as $field=>$dummy) + { + $label = isset($TParam['title'][$field]) ? $TParam['title'][$field] : $field; + $visible = (!in_array($field, $TParam['hide'])) ? 1 : 0; + $TFieldVisibility[$field]=array( + 'label'=>$label, + 'checked'=>$visible + ); + } + } + + $selectedfields = $this->form->multiSelectArrayWithCheckbox('Listview'.$this->id.'_selectedfields', $TFieldVisibility, $contextpage); // This also change content of $arrayfields_0 + } + + foreach ($TParam['title'] as $field => $label) + { + $visible = (!in_array($field, $TParam['hide'])) ? 1 : 0; + if($visible) + { + $THeader[$field] = array( + 'label'=>$label, + 'order'=>(in_array($field, $TParam['orderby']['noOrder']) ? 0 : 1), + 'width'=>(!empty($TParam['size']['width'][$field]) ? $TParam['size']['width'][$field] : 'auto'), + 'text-align'=>(!empty($TParam['position']['text-align'][$field]) ? $TParam['position']['text-align'][$field] : 'auto'), + 'more'=>'' + ); + } + } + + if(!empty($selectedfields)) + { + $THeader['selectedfields']['label']='
'.$selectedfields.'
'; + } + + return $THeader; + } + + /** + * @param $TParam + * @param $line_number + * @return bool + */ + private function in_view(&$TParam, $line_number) + { + global $conf; + + if(!empty($_REQUEST['get-all-for-export'])) return true; + + $page_number = !empty($TParam['limit']['page']) ? $TParam['limit']['page'] : 1; + $line_per_page = !empty($TParam['limit']['nbLine']) ? $TParam['limit']['nbLine'] : $conf->liste_limit; + + $start = ($page_number-1) * $line_per_page; + $end = ($page_number* $line_per_page) -1; + + if($line_number>=$start && $line_number<=$end) return true; + else return false; + } + + /** + * @param $TField + * @param $TParam + * @param $currentLine + */ + private function set_line(&$TField, &$TParam, $currentLine) + { + global $conf; + + $line_number = count($TField); + + if($this->in_view($TParam,$line_number)) + { + $this->totalRowToShow++; + $row=array(); $trans = array(); + foreach($currentLine as $field=>$value) + { + if(is_object($value)) + { + if(get_class($value)=='stdClass') {$value=print_r($value, true);} + else $value=(string) $value; + } + + $trans['@'.$field.'@'] = $value; + + if(!empty($TParam['math'][$field])) + { + $float_value = (double) strip_tags($value); + $this->TTotalTmp[$field][] = $float_value; + } + + if(!in_array($field,$TParam['hide'])) + { + $row[$field]=$value; + + if(isset($TParam['eval'][$field]) && in_array($field,array_keys($row))) + { + $strToEval = 'return '.strtr( $TParam['eval'][$field] , array_merge( $trans, array('@val@'=>$row[$field]) )).';'; + $row[$field] = eval($strToEval); + } + + if(isset($TParam['type'][$field]) && !isset($TParam['eval'][$field])) + { + if($TParam['type'][$field]=='date' || $TParam['type'][$field]=='datetime' ) + { + if($row[$field] != '0000-00-00 00:00:00' && $row[$field] != '1000-01-01 00:00:00' && $row[$field] != '0000-00-00' && !empty($row[$field])) + { + if($TParam['type'][$field]=='datetime')$row[$field] = dol_print_date(strtotime($row[$field]),'dayhoursec'); + else $row[$field] = dol_print_date(strtotime($row[$field]),'day'); + } + else + { + $row[$field] = ''; + } + } + + if($TParam['type'][$field]=='hour') { $row[$field] = date('H:i', strtotime($row[$field])); } + if($TParam['type'][$field]=='money') { $row[$field] = '
'.price($row[$field],0,'',1,-1,2).'
'; } + if($TParam['type'][$field]=='number') { $row[$field] = '
'.price($row[$field]).'
'; } + if($TParam['type'][$field]=='integer') { $row[$field] = '
'.(int)$row[$field].'
'; } + } + + if(isset($TParam['link'][$field])) + { + if(empty($row[$field]) && $row[$field]!==0 && $row[$field]!=='0')$row[$field]='(vide)'; + $row[$field]= strtr( $TParam['link'][$field], array_merge( $trans, array('@val@'=>$row[$field]))) ; + } + + if(isset($TParam['translate'][$field])) + { + if(isset($TParam['translate'][$field][''])) unset($TParam['translate'][$field]['']); + + $row[$field] = strtr( $row[$field] , $TParam['translate'][$field]); + } + } + } + } + else + { + $row = array(); + + foreach($currentLine as $field=>&$value) + { + if(!isset($this->THideFlip[$field])) + { + if(isset($TParam['math'][$field]) && !empty($TParam['math'][$field])) + { + $float_value = (double) strip_tags($value); + $this->TTotalTmp[$field][] = $float_value; + } + + $row[$field] = $value; + } + } + } + + if(!empty($TParam['math'][$field])) + { + foreach($row as $field=>$value) + { + if(!empty($TParam['math'][$field]) && is_array($TParam['math'][$field])) + { + $toField = $TParam['math'][$field][1]; + $float_value = (double) strip_tags($row[$toField]); + $this->TTotalTmp['@groupsum'][$toField][ $row[$field] ] += $float_value; + } + } + } + + $TField[] = $row; + } + + /** + * @param $sql + * @param $TParam + * @return string + */ + private function limitSQL($sql, &$TParam) + { + if(!empty($TParam['limit']['global']) && strpos($sql,'LIMIT ')===false ) + { + $sql.=' LIMIT '.(int) $TParam['limit']['global']; + } + + return $sql; + } + + /** + * @param $THeader + * @param $TField + * @param $TParam + * @param $sql + */ + private function parse_sql(&$THeader, &$TField, &$TParam, $sql) + { + $this->sql = $this->limitSQL($sql, $TParam); + + $this->TTotalTmp=array(); + $this->THideFlip = array_flip($TParam['hide']); + + $res = $this->db->query($this->sql); + if($res!==false) + { + $this->totalRow = $this->db->num_rows($res); + dol_syslog(get_class($this)."::parse_sql id=".$this->id." sql=".$this->sql, LOG_DEBUG); + + while($currentLine = $this->db->fetch_object($res)) + { + $this->set_line($TField, $TParam, $currentLine); + } + } + else + { + dol_syslog(get_class($this)."::parse_sql id=".$this->id." sql=".$this->sql, LOG_ERR); + } + } +} diff --git a/htdocs/core/db/DoliDB.class.php b/htdocs/core/db/DoliDB.class.php index 9fc1739334c..fb398270fa6 100644 --- a/htdocs/core/db/DoliDB.class.php +++ b/htdocs/core/db/DoliDB.class.php @@ -295,5 +295,122 @@ abstract class DoliDB implements Database { return $this->lastqueryerror; } + /* + * Add quote to field value if necessary + * + * @param string|int $value value to protect + * @return string|int + */ + function quote($value) { + + if(is_null($value)) return 'NULL'; + else if(is_numeric($value)) return $value; + else return "'".$this->escape( $value )."'"; + + } + + /** + * Generate and execute Update SQL commande + * + * @param string $table table to update + * @param array $values array of values to update + * @param int|string|array $key key of value to select row to update + * @return bool|result false or boolean + */ + function update($table,$fields,$key){ + + foreach ($fields as $k => $v) { + + if (is_array($key)){ + $i=array_search($k , $key ); + if ( $i !== false) { + $where[] = $key[$i].'=' . $this->quote( $v ) ; + continue; + } + } else { + if ( $k == $key) { + $where[] = $k.'=' .$this->quote( $v ) ; + continue; + } + } + + $tmp[] = $k.'='.$this->quote($v); + } + $sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET '.implode( ',', $tmp ).' WHERE ' . implode(' AND ',$where) ; + $res = $this->query( $sql ); + + if($res===false) { + //error + return false; + } + + return true; + } + + /** + * Generate and execute Insert SQL commande + * + * @param string $table table to update + * @param array $values array of values to update + * @return bool|result false or boolean + */ + function insert($table,$fields){ + + foreach ($fields as $k => $v) { + + $keys[] = $k; + $values[] = $this->quote($v); + + } + + $sql = 'INSERT INTO '.MAIN_DB_PREFIX.$table.' + ( '.implode( ",", $keys ).' ) + VALUES ( '.implode( ",", $values ).' ) '; + + $res = $this->query($sql); + if($res===false) { + + return false; + } + + return true; + } + + /** + * Generate and execute Delete SQL commande + * + * @param string $table table for the delete + * @param array $values array of values to delete + * @param int|string|array $key key of value to select row to update + * @return bool|result false or boolean + */ + function delete($table,$fields,$key){ + foreach ($fields as $k => $v) { + if (is_array($key)){ + $i=array_search($k , $key ); + if ( $i !== false) { + $where[] = $key[$i].'=' . $this->quote( $v ) ; + continue; + } + } else { + if ( $k == $key) { + $where[] = $k.'='.$this->quote( $v ) ; + continue; + } + } + + } + + $sql = 'DELETE FROM '.MAIN_DB_PREFIX.$table.' WHERE '.implode(' AND ',$where); + + $res = $this->query( $sql ); + if($res===false) { + return false; + } + + return true; + + } + } diff --git a/htdocs/core/js/listview.js b/htdocs/core/js/listview.js new file mode 100644 index 00000000000..d68a7993218 --- /dev/null +++ b/htdocs/core/js/listview.js @@ -0,0 +1,146 @@ +var Listview_include = true; + +function Listview_OrderDown(idListe, column) { + var base_url = document.location.href; + + base_url = Listview_recup_form_param(idListe,base_url); + base_url = Listview_removeParam(base_url,'Listview['+encodeURIComponent(idListe)+'][orderBy]'); + + base_url = Listview_removeParam(base_url,'get-all-for-export'); + + document.location.href=Listview_modifyUrl(base_url,"Listview["+encodeURIComponent(idListe)+"][orderBy]["+encodeURIComponent(column)+"]","DESC"); +} +function Listview_OrderUp(idListe, column) { + + var base_url = document.location.href; + + base_url = Listview_recup_form_param(idListe,base_url); + base_url = Listview_removeParam(base_url,'Listview['+encodeURIComponent(idListe)+'][orderBy]'); + + base_url = Listview_removeParam(base_url,'get-all-for-export'); + + document.location.href=Listview_modifyUrl(base_url,"Listview["+encodeURIComponent(idListe)+"][orderBy]["+encodeURIComponent(column)+"]","ASC"); +} +function Listview_modifyUrl(strURL,paramName,paramNewValue){ + if (strURL.indexOf(paramName+'=')!=-1){ + + var strFirstPart=strURL.substring(0,strURL.indexOf(paramName+'=',0))+paramName+'='; + var strLastPart=""; + if (strURL.indexOf('&',strFirstPart.length-1)>0) + strLastPart=strURL.substring(strURL.indexOf('&',strFirstPart.length-1),strURL.length); + strURL=strFirstPart+paramNewValue+strLastPart; + } + else{ + if (strURL.search('=')!=-1) // permet de verifier s'il y a dej� des param�tres dans l'URL + strURL+='&'+paramName+'='+paramNewValue; + else + strURL+='?'+paramName+'='+paramNewValue; + } + + return strURL; +} +function Listview_removeParam(strURL, paramMask) { + var cpt=0; + var url = ''; + + while(strURL.indexOf(paramMask)!=-1 && cpt++ <50){ + var strFirstPart= strURL.substring(0,strURL.indexOf(paramMask)-1); + + var strLastPart=''; + if (strURL.indexOf('&',strFirstPart.length+1)>0) { + strLastPart = strURL.substring(strURL.indexOf('&',strFirstPart.length+1),strURL.length); + } + + url = strFirstPart+strLastPart; + + } + + if(url=='')url = strURL; + + return url; +} + +function Listview_recup_form_param(idListe,base_url) { + + $('#'+idListe+' tr.barre-recherche [listviewtbs],#'+idListe+' tr.barre-recherche-head input,#'+idListe+' tr.barre-recherche-head select,#'+idListe+' div.tabsAction input[listviewtbs]').each(function(i,item) { + if($(item).attr("name")) { + base_url = Listview_modifyUrl(base_url, $(item).attr("name") , $(item).val()); + } + + }); + + return base_url; +} + +function Listview_GoToPage(idListe,pageNumber){ + + var base_url = document.location.href; + + base_url = Listview_recup_form_param(idListe,base_url); + base_url =Listview_modifyUrl(base_url,"Listview["+encodeURIComponent(idListe)+"][page]",pageNumber); + + base_url = Listview_removeParam(base_url,'get-all-for-export'); + + document.location.href=base_url; +} +function Listview_submitSearch(obj) { + + $form = $(obj).closest('form'); + console.log($form); + if($form.length>0){ + $form.submit(); + } +} +function Listview_launch_downloadAs(mode,url,token,session_name) { + $('#listviewdAS_export_form').remove(); + + $form = $('
'); + $form.append(''); + $form.append(''); + $form.append(''); + + $('body').append($form); + + $('#listviewdAS_export_form').submit(); + +} + +function Listview_downloadAs(obj, mode,url,token,session_name) { + + $form = $(obj).closest('form'); + $div = $form.find('div.tabsAction'); + $div.append(''); + $div.append(''); + $div.append(''); + $div.append(''); + $div.append(''); + + Listview_submitSearch(obj); +} + +$(document).ready(function() { + $('tr.barre-recherche input').keypress(function(e) { + if(e.which == 13) { + + var id_list = $(this).closest('table').attr('id'); + + $('#'+id_list+' .list-search-link').click(); + + } + }); + + var $_GET = {}; + + document.location.search.replace(/\??(?:([^=]+)=([^&]*)&?)/g, function () { + function decode(s) { + return decodeURIComponent(s.split("+").join(" ")); + } + + $_GET[decode(arguments[1])] = decode(arguments[2]); + }); + + if(typeof $_GET["get-all-for-export"] != "undefined") { + Listview_launch_downloadAs($_GET['mode'],$_GET['url'],$_GET['token'],$_GET['session_name']); + } + +}); diff --git a/htdocs/core/modules/modInventory.class.php b/htdocs/core/modules/modInventory.class.php new file mode 100644 index 00000000000..11b4c992e6a --- /dev/null +++ b/htdocs/core/modules/modInventory.class.php @@ -0,0 +1,316 @@ + + * Copyright (C) 2004-2012 Laurent Destailleur + * Copyright (C) 2005-2012 Regis Houssin + * + * 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 . + */ + +/** + * \defgroup inventory Module inventory + * \brief Example of a module descriptor. + * Such a file must be copied into htdocs/inventory/core/modules directory. + * \file htdocs/inventory/core/modules/modinventory.class.php + * \ingroup inventory + * \brief Description and activation file for module inventory + */ +include_once DOL_DOCUMENT_ROOT .'/core/modules/DolibarrModules.class.php'; + + +/** + * Description and activation class for module inventory + */ +class modinventory extends DolibarrModules +{ + /** + * Constructor. Define names, constants, directories, boxes, permissions + * + * @param DoliDB $db Database handler + */ + function __construct($db) + { + global $langs,$conf; + + $this->db = $db; + + // Id for module (must be unique). + // Use here a free id (See in Home -> System information -> Dolibarr for list of used modules id). + $this->numero = 104420; // 104000 to 104999 for ATM CONSULTING + // Key text used to identify module (for permissions, menus, etc...) + $this->rights_class = 'inventory'; + + // Family can be 'crm','financial','hr','projects','products','ecm','technic','other' + // It is used to group modules in module setup page + $this->family = "products"; + // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module) + $this->name = preg_replace('/^mod/i','',get_class($this)); + // Module description, used if translation string 'ModuleXXXDesc' not found (where XXX is value of numeric property 'numero' of module) + $this->description = "Description of module inventory"; + // Possible values for version are: 'development', 'experimental', 'dolibarr' or version + $this->version = 'dolibarr'; + // Key used in llx_const table to save module status enabled/disabled (where MYMODULE is value of property name of module in uppercase) + $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name); + // Where to store the module in setup page (0=common,1=interface,2=others,3=very specific) + $this->special = 0; + // Name of image file used for this module. + // If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue' + // If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module' + $this->picto='inventory'; + + // Defined all module parts (triggers, login, substitutions, menus, css, etc...) + // for default path (eg: /inventory/core/xxxxx) (0=disable, 1=enable) + // for specific path of parts (eg: /inventory/core/modules/barcode) + // for specific css file (eg: /inventory/css/inventory.css.php) + //$this->module_parts = array( + // 'triggers' => 0, // Set this to 1 if module has its own trigger directory (core/triggers) + // 'login' => 0, // Set this to 1 if module has its own login method directory (core/login) + // 'substitutions' => 0, // Set this to 1 if module has its own substitution function file (core/substitutions) + // 'menus' => 0, // Set this to 1 if module has its own menus handler directory (core/menus) + // 'theme' => 0, // Set this to 1 if module has its own theme directory (theme) + // 'tpl' => 0, // Set this to 1 if module overwrite template dir (core/tpl) + // 'barcode' => 0, // Set this to 1 if module has its own barcode directory (core/modules/barcode) + // 'models' => 0, // Set this to 1 if module has its own models directory (core/modules/xxx) + // 'css' => array('/inventory/css/inventory.css.php'), // Set this to relative path of css file if module has its own css file + // 'js' => array('/inventory/js/inventory.js'), // Set this to relative path of js file if module must load a js on all pages + // 'hooks' => array('hookcontext1','hookcontext2') // Set here all hooks context managed by module + // 'dir' => array('output' => 'othermodulename'), // To force the default directories names + // 'workflow' => array('WORKFLOW_MODULE1_YOURACTIONTYPE_MODULE2'=>array('enabled'=>'! empty($conf->module1->enabled) && ! empty($conf->module2->enabled)', 'picto'=>'yourpicto@inventory')) // Set here all workflow context managed by module + // ); + $this->module_parts = array(); + + // Data directories to create when module is enabled. + // Example: this->dirs = array("/inventory/temp"); + $this->dirs = array(); + + // Config pages. Put here list of php page, stored into inventory/admin directory, to use to setup module. + $this->config_page_url = array("inventory.php"); + + // Dependencies + $this->hidden = false; // A condition to hide module + $this->depends = array('modStock'); // List of modules id that must be enabled if this module is enabled + $this->requiredby = array(); // List of modules id to disable if this one is disabled + $this->conflictwith = array(); // List of modules id this module is in conflict with + $this->phpmin = array(5,0); // Minimum version of PHP required by module + $this->need_dolibarr_version = array(3,0); // Minimum version of Dolibarr required by module + $this->langfiles = array("inventory"); + + // Constants + // List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 'current' or 'allentities', deleteonunactive) + // Example: $this->const=array(0=>array('MYMODULE_MYNEWCONST1','chaine','myvalue','This is a constant to add',1), + // 1=>array('MYMODULE_MYNEWCONST2','chaine','myvalue','This is another constant to add',0, 'current', 1) + // ); + $this->const = array(); + + // Array to add new pages in new tabs + // Example: $this->tabs = array('objecttype:+tabname1:Title1:mylangfile@inventory:$user->rights->inventory->read:/inventory/mynewtab1.php?id=__ID__', // To add a new tab identified by code tabname1 + // 'objecttype:+tabname2:Title2:mylangfile@inventory:$user->rights->othermodule->read:/inventory/mynewtab2.php?id=__ID__', // To add another new tab identified by code tabname2 + // 'objecttype:-tabname:NU:conditiontoremove'); // To remove an existing tab identified by code tabname + // where objecttype can be + // 'categories_x' to add a tab in category view (replace 'x' by type of category (0=product, 1=supplier, 2=customer, 3=member) + // 'contact' to add a tab in contact view + // 'contract' to add a tab in contract view + // 'group' to add a tab in group view + // 'intervention' to add a tab in intervention view + // 'invoice' to add a tab in customer invoice view + // 'invoice_supplier' to add a tab in supplier invoice view + // 'member' to add a tab in fundation member view + // 'opensurveypoll' to add a tab in opensurvey poll view + // 'order' to add a tab in customer order view + // 'order_supplier' to add a tab in supplier order view + // 'payment' to add a tab in payment view + // 'payment_supplier' to add a tab in supplier payment view + // 'product' to add a tab in product view + // 'propal' to add a tab in propal view + // 'project' to add a tab in project view + // 'stock' to add a tab in stock view + // 'thirdparty' to add a tab in third party view + // 'user' to add a tab in user view + $this->tabs = array(); + + // Dictionaries + if (! isset($conf->inventory->enabled)) + { + $conf->inventory=new stdClass(); + $conf->inventory->enabled=0; + } + $this->dictionaries=array(); + /* Example: + if (! isset($conf->inventory->enabled)) $conf->inventory->enabled=0; // This is to avoid warnings + $this->dictionaries=array( + 'langs'=>'mylangfile@inventory', + 'tabname'=>array(MAIN_DB_PREFIX."table1",MAIN_DB_PREFIX."table2",MAIN_DB_PREFIX."table3"), // List of tables we want to see into dictonnary editor + 'tablib'=>array("Table1","Table2","Table3"), // Label of tables + 'tabsql'=>array('SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'table1 as f','SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'table2 as f','SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'table3 as f'), // Request to select fields + 'tabsqlsort'=>array("label ASC","label ASC","label ASC"), // Sort order + 'tabfield'=>array("code,label","code,label","code,label"), // List of fields (result of select to show dictionary) + 'tabfieldvalue'=>array("code,label","code,label","code,label"), // List of fields (list of fields to edit a record) + 'tabfieldinsert'=>array("code,label","code,label","code,label"), // List of fields (list of fields for insert) + 'tabrowid'=>array("rowid","rowid","rowid"), // Name of columns with primary key (try to always name it 'rowid') + 'tabcond'=>array($conf->inventory->enabled,$conf->inventory->enabled,$conf->inventory->enabled) // Condition to show each dictionary + ); + */ + + // Boxes + // Add here list of php file(s) stored in core/boxes that contains class to show a box. + $this->boxes = array(); // List of boxes + // Example: + //$this->boxes=array(array(0=>array('file'=>'myboxa.php','note'=>'','enabledbydefaulton'=>'Home'),1=>array('file'=>'myboxb.php','note'=>''),2=>array('file'=>'myboxc.php','note'=>''));); + + // Permissions + $this->rights = array(); // Permission array used by this module + $r=0; + + // Add here list of permission defined by an id, a label, a boolean and two constant strings. + // Example: + // $this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used) + // $this->rights[$r][1] = 'Permision label'; // Permission label + // $this->rights[$r][3] = 1; // Permission by default for new user (0/1) + // $this->rights[$r][4] = 'level1'; // In php code, permission will be checked by test if ($user->rights->permkey->level1->level2) + // $this->rights[$r][5] = 'level2'; // In php code, permission will be checked by test if ($user->rights->permkey->level1->level2) + // $r++; + + $this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used) + $this->rights[$r][1] = 'inventoryReadPermission'; // Permission label + $this->rights[$r][3] = 1; // Permission by default for new user (0/1) + $this->rights[$r][4] = 'read'; // In php code, permission will be checked by test if ($user->rights->permkey->level1->level2) + $r++; + + $this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used) + $this->rights[$r][1] = 'inventoryCreatePermission'; // Permission label + $this->rights[$r][3] = 0; // Permission by default for new user (0/1) + $this->rights[$r][4] = 'create'; // In php code, permission will be checked by test if ($user->rights->permkey->level1->level2) + $r++; + + $this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used) + $this->rights[$r][1] = 'inventoryWritePermission'; // Permission label + $this->rights[$r][3] = 0; // Permission by default for new user (0/1) + $this->rights[$r][4] = 'write'; // In php code, permission will be checked by test if ($user->rights->permkey->level1->level2) + $r++; + + $this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used) + $this->rights[$r][1] = 'inventoryValidatePermission'; // Permission label + $this->rights[$r][3] = 0; // Permission by default for new user (0/1) + $this->rights[$r][4] = 'validate'; // In php code, permission will be checked by test if ($user->rights->permkey->level1->level2) + $r++; + + $this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used) + $this->rights[$r][1] = 'inventoryChangePMPPermission'; // Permission label + $this->rights[$r][3] = 0; // Permission by default for new user (0/1) + $this->rights[$r][4] = 'changePMP'; // In php code, permission will be checked by test if ($user->rights->permkey->level1->level2) + $r++; + + // Main menu entries + $this->menu = array(); // List of menus to add + $r=0; + + + $this->menu[$r]=array( + 'fk_menu'=>'fk_mainmenu=products', // Put 0 if this is a top menu + 'type'=>'left', // This is a Top menu entry + 'titre'=>'Inventory', + 'mainmenu'=>'products', + 'leftmenu'=>'inventory_left', + 'url'=>'/inventory/list.php', + 'langs'=>'inventory', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + 'position'=>100+$r, + 'enabled'=>'$conf->inventory->enabled', // Define condition to show or hide menu entry. Use '$conf->inventory->enabled' if entry must be visible if module is enabled. + 'perms'=>'$user->rights->inventory->read', // Use 'perms'=>'$user->rights->inventory->level1->level2' if you want your menu with a permission rules + 'target'=>'', + 'user'=>2 + ); // 0=Menu for internal users, 1=external users, 2=both + $r++; + + $this->menu[$r]=array( + 'fk_menu'=>'fk_mainmenu=products,fk_leftmenu=inventory_left', // Put 0 if this is a top menu + 'type'=>'left', // This is a Top menu entry + 'titre'=>'NewInventory', + 'mainmenu'=>'products', + 'leftmenu'=>'inventory_left_create', + 'url'=>'/inventory/inventory.php?action=create', + 'position'=>100+$r, + 'enabled'=>'$conf->inventory->enabled', // Define condition to show or hide menu entry. Use '$conf->inventory->enabled' if entry must be visible if module is enabled. + 'perms'=>'$user->rights->inventory->create', // Use 'perms'=>'$user->rights->inventory->level1->level2' if you want your menu with a permission rules + 'target'=>'', + 'user'=>2 + ); // 0=Menu for internal users, 1=external users, 2=both + $r++; + + $this->menu[$r]=array( + 'fk_menu'=>'fk_mainmenu=products,fk_leftmenu=inventory_left', // Put 0 if this is a top menu + 'type'=>'left', // This is a Top menu entry + 'titre'=>'ListInventory', + 'mainmenu'=>'products', + 'leftmenu'=>'inventory_left_list', + 'url'=>'/inventory/list.php', + 'langs'=>'inventory', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory. + 'position'=>100+$r, + 'enabled'=>'$conf->inventory->enabled', // Define condition to show or hide menu entry. Use '$conf->inventory->enabled' if entry must be visible if module is enabled. + 'perms'=>'$user->rights->inventory->read', // Use 'perms'=>'$user->rights->inventory->level1->level2' if you want your menu with a permission rules + 'target'=>'', + 'user'=>2 + ); // 0=Menu for internal users, 1=external users, 2=both + $r++; + + // Exports + $r=1; + + // Example: + // $this->export_code[$r]=$this->rights_class.'_'.$r; + // $this->export_label[$r]='CustomersInvoicesAndInvoiceLines'; // Translation key (used only if key ExportDataset_xxx_z not found) + // $this->export_enabled[$r]='1'; // Condition to show export in list (ie: '$user->id==3'). Set to 1 to always show when module is enabled. + // $this->export_permission[$r]=array(array("facture","facture","export")); + // $this->export_fields_array[$r]=array('s.rowid'=>"IdCompany",'s.nom'=>'CompanyName','s.address'=>'Address','s.zip'=>'Zip','s.town'=>'Town','s.fk_pays'=>'Country','s.phone'=>'Phone','s.siren'=>'ProfId1','s.siret'=>'ProfId2','s.ape'=>'ProfId3','s.idprof4'=>'ProfId4','s.code_compta'=>'CustomerAccountancyCode','s.code_compta_fournisseur'=>'SupplierAccountancyCode','f.rowid'=>"InvoiceId",'f.facnumber'=>"InvoiceRef",'f.datec'=>"InvoiceDateCreation",'f.datef'=>"DateInvoice",'f.total'=>"TotalHT",'f.total_ttc'=>"TotalTTC",'f.tva'=>"TotalVAT",'f.paye'=>"InvoicePaid",'f.fk_statut'=>'InvoiceStatus','f.note'=>"InvoiceNote",'fd.rowid'=>'LineId','fd.description'=>"LineDescription",'fd.price'=>"LineUnitPrice",'fd.tva_tx'=>"LineVATRate",'fd.qty'=>"LineQty",'fd.total_ht'=>"LineTotalHT",'fd.total_tva'=>"LineTotalTVA",'fd.total_ttc'=>"LineTotalTTC",'fd.date_start'=>"DateStart",'fd.date_end'=>"DateEnd",'fd.fk_product'=>'ProductId','p.ref'=>'ProductRef'); + // $this->export_entities_array[$r]=array('s.rowid'=>"company",'s.nom'=>'company','s.address'=>'company','s.zip'=>'company','s.town'=>'company','s.fk_pays'=>'company','s.phone'=>'company','s.siren'=>'company','s.siret'=>'company','s.ape'=>'company','s.idprof4'=>'company','s.code_compta'=>'company','s.code_compta_fournisseur'=>'company','f.rowid'=>"invoice",'f.facnumber'=>"invoice",'f.datec'=>"invoice",'f.datef'=>"invoice",'f.total'=>"invoice",'f.total_ttc'=>"invoice",'f.tva'=>"invoice",'f.paye'=>"invoice",'f.fk_statut'=>'invoice','f.note'=>"invoice",'fd.rowid'=>'invoice_line','fd.description'=>"invoice_line",'fd.price'=>"invoice_line",'fd.total_ht'=>"invoice_line",'fd.total_tva'=>"invoice_line",'fd.total_ttc'=>"invoice_line",'fd.tva_tx'=>"invoice_line",'fd.qty'=>"invoice_line",'fd.date_start'=>"invoice_line",'fd.date_end'=>"invoice_line",'fd.fk_product'=>'product','p.ref'=>'product'); + // $this->export_sql_start[$r]='SELECT DISTINCT '; + // $this->export_sql_end[$r] =' FROM ('.MAIN_DB_PREFIX.'facture as f, '.MAIN_DB_PREFIX.'facturedet as fd, '.MAIN_DB_PREFIX.'societe as s)'; + // $this->export_sql_end[$r] .=' LEFT JOIN '.MAIN_DB_PREFIX.'product as p on (fd.fk_product = p.rowid)'; + // $this->export_sql_end[$r] .=' WHERE f.fk_soc = s.rowid AND f.rowid = fd.fk_facture'; + // $this->export_sql_order[$r] .=' ORDER BY s.nom'; + // $r++; + } + + /** + * Function called when module is enabled. + * The init function add constants, boxes, permissions and menus (defined in constructor) into Dolibarr database. + * It also creates data directories + * + * @param string $options Options when enabling module ('', 'noboxes') + * @return int 1 if OK, 0 if KO + */ + function init($options='') + { + $sql = array(); + define('INC_FROM_DOLIBARR', true); + dol_include_once('/inventory/config.php'); + dol_include_once("/inventory/script/create-maj-base.php"); + + return $this->_init($sql, $options); + } + + /** + * Function called when module is disabled. + * Remove from database constants, boxes and permissions from Dolibarr database. + * Data directories are not deleted + * + * @param string $options Options when enabling module ('', 'noboxes') + * @return int 1 if OK, 0 if KO + */ + function remove($options='') + { + $sql = array(); + + return $this->_remove($sql, $options); + } + +} diff --git a/htdocs/install/mysql/migration/5.0.0-6.0.0.sql b/htdocs/install/mysql/migration/5.0.0-6.0.0.sql index c2edae8b592..4a383fcb505 100644 --- a/htdocs/install/mysql/migration/5.0.0-6.0.0.sql +++ b/htdocs/install/mysql/migration/5.0.0-6.0.0.sql @@ -182,3 +182,40 @@ ALTER TABLE llx_supplier_proposaldet ADD INDEX idx_supplier_proposaldet_fk_produ ALTER TABLE llx_supplier_proposaldet ADD CONSTRAINT fk_supplier_proposaldet_fk_unit FOREIGN KEY (fk_unit) REFERENCES llx_c_units (rowid); ALTER TABLE llx_supplier_proposaldet ADD CONSTRAINT fk_supplier_proposaldet_fk_supplier_proposal FOREIGN KEY (fk_supplier_proposal) REFERENCES llx_supplier_proposal (rowid); +-- NEW inventory module +CREATE TABLE llx_inventory +( +rowid integer NOT NULL AUTO_INCREMENT PRIMARY KEY, +datec datetime DEFAULT NULL, +tms timestamp, +fk_warehouse integer DEFAULT 0, +entity integer DEFAULT 0, +status integer DEFAULT 0, +title varchar(255) NOT NULL, +date_inventory datetime DEFAULT NULL +) +ENGINE=InnoDB; + +CREATE TABLE llx_inventorydet +( +rowid integer NOT NULL AUTO_INCREMENT PRIMARY KEY, +datec datetime DEFAULT NULL, +tms timestamp, +fk_inventory integer DEFAULT 0, +fk_warehouse integer DEFAULT 0, +fk_product integer DEFAULT 0, +entity integer DEFAULT 0, +qty_view double DEFAULT 0, +qty_stock double DEFAULT 0, +qty_regulated double DEFAULT 0, +pmp double DEFAULT 0, +pa double DEFAULT 0, +new_pmp double DEFAULT 0 +) +ENGINE=InnoDB; + +ALTER TABLE llx_inventory ADD INDEX idx_inventory_tms (tms); +ALTER TABLE llx_inventory ADD INDEX idx_inventory_datec (datec); +ALTER TABLE llx_inventorydet ADD INDEX idx_inventorydet_tms (tms); +ALTER TABLE llx_inventorydet ADD INDEX idx_inventorydet_datec (datec); +ALTER TABLE llx_inventorydet ADD INDEX idx_inventorydet_fk_inventory (fk_inventory); \ No newline at end of file diff --git a/htdocs/install/mysql/tables/llx_inventory.key.sql b/htdocs/install/mysql/tables/llx_inventory.key.sql new file mode 100644 index 00000000000..bf76381e108 --- /dev/null +++ b/htdocs/install/mysql/tables/llx_inventory.key.sql @@ -0,0 +1,21 @@ +-- =================================================================== +-- Copyright (C) 2012 Laurent Destailleur +-- Copyright (C) 2017 ATM Consulting +-- +-- 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 . +-- +-- =================================================================== + +ALTER TABLE llx_inventory ADD INDEX idx_inventory_tms (tms); +ALTER TABLE llx_inventory ADD INDEX idx_inventory_datec (datec); diff --git a/htdocs/install/mysql/tables/llx_inventory.sql b/htdocs/install/mysql/tables/llx_inventory.sql new file mode 100644 index 00000000000..f906699f5dd --- /dev/null +++ b/htdocs/install/mysql/tables/llx_inventory.sql @@ -0,0 +1,31 @@ +-- =================================================================== +-- Copyright (C) 2012 Laurent Destailleur +-- Copyright (C) 2017 ATM Consulting +-- +-- 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 . +-- +-- =================================================================== + +CREATE TABLE llx_inventory +( +rowid integer NOT NULL AUTO_INCREMENT PRIMARY KEY, +datec datetime DEFAULT NULL, +tms timestamp, +fk_warehouse integer DEFAULT 0, +entity integer DEFAULT 0, +status integer DEFAULT 0, +title varchar(255) NOT NULL, +date_inventory datetime DEFAULT NULL +) +ENGINE=InnoDB; diff --git a/htdocs/install/mysql/tables/llx_inventorydet.key.sql b/htdocs/install/mysql/tables/llx_inventorydet.key.sql new file mode 100644 index 00000000000..3cef44ba52a --- /dev/null +++ b/htdocs/install/mysql/tables/llx_inventorydet.key.sql @@ -0,0 +1,22 @@ +-- =================================================================== +-- Copyright (C) 2012 Laurent Destailleur +-- Copyright (C) 2017 ATM Consulting +-- +-- 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 . +-- +-- =================================================================== + +ALTER TABLE llx_inventorydet ADD INDEX idx_inventorydet_tms (tms); +ALTER TABLE llx_inventorydet ADD INDEX idx_inventorydet_datec (datec); +ALTER TABLE llx_inventorydet ADD INDEX idx_inventorydet_fk_inventory (fk_inventory); diff --git a/htdocs/install/mysql/tables/llx_inventorydet.sql b/htdocs/install/mysql/tables/llx_inventorydet.sql new file mode 100644 index 00000000000..20e48b877b5 --- /dev/null +++ b/htdocs/install/mysql/tables/llx_inventorydet.sql @@ -0,0 +1,36 @@ +-- =================================================================== +-- Copyright (C) 2012 Laurent Destailleur +-- Copyright (C) 2017 ATM Consulting +-- +-- 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 . +-- +-- =================================================================== + +CREATE TABLE llx_inventorydet +( +rowid integer NOT NULL AUTO_INCREMENT PRIMARY KEY, +datec datetime DEFAULT NULL, +tms timestamp, +fk_inventory integer DEFAULT 0, +fk_warehouse integer DEFAULT 0, +fk_product integer DEFAULT 0, +entity integer DEFAULT 0, +qty_view double DEFAULT 0, +qty_stock double DEFAULT 0, +qty_regulated double DEFAULT 0, +pmp double DEFAULT 0, +pa double DEFAULT 0, +new_pmp double DEFAULT 0 +) +ENGINE=InnoDB; diff --git a/htdocs/inventory/ajax/ajax.inventory.php b/htdocs/inventory/ajax/ajax.inventory.php new file mode 100644 index 00000000000..c1f372806c8 --- /dev/null +++ b/htdocs/inventory/ajax/ajax.inventory.php @@ -0,0 +1,51 @@ +rights->inventory->write)) { echo -1; exit; } + + $fk_det_inventory = GETPOST('fk_det_inventory'); + + $det = new Inventorydet($db); + if( $det->fetch( $fk_det_inventory)) + { + $det->qty_view+=GETPOST('qty'); + $det->update($user); + + echo $det->qty_view; + } + else + { + echo -2; + } + + break; + + case 'pmp': + if (!$user->rights->inventory->write || !$user->rights->inventory->changePMP) { echo -1; exit; } + + $fk_det_inventory = GETPOST('fk_det_inventory'); + + $det = new Inventorydet($db); + if( $det->fetch( $fk_det_inventory)) + { + $det->new_pmp=price2num(GETPOST('pmp')); + $det->update($user); + + echo $det->new_pmp; + } + else + { + echo -2; + } + + break; + } + diff --git a/htdocs/inventory/class/inventory.class.php b/htdocs/inventory/class/inventory.class.php new file mode 100644 index 00000000000..14230d90893 --- /dev/null +++ b/htdocs/inventory/class/inventory.class.php @@ -0,0 +1,710 @@ + + * + * 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 htdocs/inventory/class/product.inventory.php + * \ingroup product + * \brief File of class to manage predefined products stock + */ + +require_once DOL_DOCUMENT_ROOT.'/core/class/coreobject.class.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; + +/** + * Class to manage inventories + */ +class Inventory extends CoreObject +{ + public $element='inventory'; + public $table_element='inventory'; + public $fk_element='fk_inventory'; + protected $childtables=array('inventorydet'); // To test if we can delete object + protected $isnolinkedbythird = 1; // No field fk_soc + protected $ismultientitymanaged = 1; // 0=No test on entity, 1=Test with field entity, 2=Test with link by societe + + /** + * Warehouse Id + * @var int + */ + public $fk_warehouse; + /** + * Entity Id + * @var int + */ + public $entity; + + /** + * Status + * @var int + */ + public $status; + /** + * Inventory Date + * @var date + */ + public $date_inventory; + /** + * Inventory Title + * @var string + */ + public $title; + + /** + * Attribute object linked with database + * @var array + */ + protected $__fields=array( + 'fk_warehouse'=>array('type'=>'integer','index'=>true) + ,'entity'=>array('type'=>'integer','index'=>true) + ,'status'=>array('type'=>'integer','index'=>true) + ,'date_inventory'=>array('type'=>'date') + ,'title'=>array('type'=>'string') + ); + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct(DoliDB &$db) + { + global $conf; + + parent::__construct($db); + parent::init(); + + $this->status = 0; + $this->entity = $conf->entity; + $this->errors = array(); + $this->amount = 0; + } + + /** + * Function to sort children object + */ + public function sortDet() + { + if(!empty($this->Inventorydet)) usort($this->Inventorydet, array('Inventory', 'customSort')); + } + + /** + * Get object and children from database + * + * @param int $id Id of object to load + * @param bool $loadChild used to load children from database + * @return int >0 if OK, <0 if KO, 0 if not found + */ + public function fetch($id, $loadChild = true) + { + if(!$loadChild) $this->withChild = false; + + $res = parent::fetch($id, $loadChild); + if($res > 0) + { + $this->sortDet(); + $this->amount = 0; + if(!empty($this->Inventorydet )) + { + foreach($this->Inventorydet as &$det) + { + $this->amount += $det->qty_view * $det->pmp; + } + } + } + + return $res; + } + + /** + * Custom function call by usort + * + * @param Inventorydet $objA first Inventorydet object + * @param Inventorydet $objB second Inventorydet object + * @return int + */ + private function customSort(&$objA, &$objB) + { + $r = strcmp(strtoupper(trim($objA->product->ref)), strtoupper(trim($objB->product->ref))); + + if ($r < 0) $r = -1; + elseif ($r > 0) $r = 1; + else $r = 0; + + return $r; + } + + /** + * @param User $user user object + * @return int + */ + public function changePMP(User &$user) + { + $error = 0; + $this->db->begin(); + + if(!empty($this->Inventorydet)) + { + foreach ($this->Inventorydet as $k => &$Inventorydet) + { + if($Inventorydet->new_pmp>0) + { + $Inventorydet->pmp = $Inventorydet->new_pmp; + $Inventorydet->new_pmp = 0; + + $res = $this->db->query('UPDATE '.MAIN_DB_PREFIX.'product as p SET pmp = '.$Inventorydet->pmp.' WHERE rowid = '.$Inventorydet->fk_product ); + if (!$res) + { + $error++; + $this->error = $this->db->lasterror(); + $this->errors[] = $this->db->lasterror(); + } + } + } + } + + $res = parent::update($user); + if (!$res) + { + $error++; + $this->error = $this->db->lasterror(); + $this->errors[] = $this->db->lasterror(); + } + + + if (!$error) + { + $this->db->commit(); + return 1; + } + else + { + $this->db->rollback(); + return -1; + } + } + + /** + * Function to update object or create or delete if needed + * + * @param User $user user object + * @return < 0 if ko, > 0 if ok + */ + public function update(User &$user) + { + $error = 0; + $this->db->begin(); + + // if we valid the inventory we save the stock at the same time + if ($this->status) + { + $res = $this->regulate(); + if ($res < 0) + { + $error++; + $this->error = $this->db->lasterror(); + $this->errors[] = $this->db->lasterror(); + } + } + + $res = parent::update($user); + if (!$res) + { + $error++; + $this->error = $this->db->lasterror(); + $this->errors[] = $this->db->lasterror(); + } + + if (!$error) + { + $this->db->commit(); + return $this->id; + } + else + { + $this->db->rollback(); + return -1; + } + } + + /** + * Function to update current object + * + * @param array $Tab Array of values + * @return int + */ + public function setValues(&$Tab) + { + global $langs; + + if (isset($Tab['qty_to_add'])) + { + foreach ($Tab['qty_to_add'] as $k => $qty) + { + $qty = (float) price2num($qty); + + if ($qty < 0) + { + $this->errors[] = $langs->trans('inventoryErrorQtyAdd'); + return -1; + } + + $product = new Product($this->db); + $product->fetch($this->Inventorydet[$k]->fk_product); + + $this->Inventorydet[$k]->pmp = $product->pmp; + $this->Inventorydet[$k]->qty_view += $qty; + } + } + + return parent::setValues($Tab); + } + + /** + * Function to delete all Inventorydet + * + * @param User $user user object + * @return < 0 if ko, > 0 if ok + */ + public function deleteAllLine(User &$user) + { + foreach($this->Inventorydet as &$det) + { + $det->to_delete = true; + } + + $res = $this->update($user); + + if ($res > 0) $this->Inventorydet = array(); + else return -1; + } + + /** + * Function to add Inventorydet + * + * @param int $fk_product fk_product of Inventorydet + * @param int $fk_warehouse fk_warehouse target + * @return bool + */ + public function addProduct($fk_product, $fk_warehouse=0) + { + $k = $this->addChild('Inventorydet'); + $det = &$this->Inventorydet[$k]; + + $det->fk_inventory = $this->id; + $det->fk_product = $fk_product; + $det->fk_warehouse = empty($fk_warehouse) ? $this->fk_warehouse : $fk_warehouse; + + $det->load_product(); + + $date = $this->getDate('date_inventory', 'Y-m-d'); + if(empty($date)) $date = $this->getDate('datec', 'Y-m-d'); + $det->setStockDate($date, $fk_warehouse); + + return true; + } + + /** + * Duplication method product to add datem + * Adjust stock in a warehouse for product + * + * @param int $fk_product id of product + * @param int $fk_warehouse 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 Unit price HT of product, used to calculate average weighted price (PMP in french). If 0, average weighted price is not changed. + * @param string $inventorycode Inventory code + * @return int <0 if KO, >0 if OK + */ + public function correctStock($fk_product, $fk_warehouse, $nbpiece, $movement, $label='', $price=0, $inventorycode='') + { + global $conf, $user; + + if ($fk_warehouse) + { + $this->db->begin(); + + require_once DOL_DOCUMENT_ROOT .'/product/stock/class/mouvementstock.class.php'; + + $op[0] = "+".trim($nbpiece); + $op[1] = "-".trim($nbpiece); + + $datem = empty($conf->global->INVENTORY_USE_INVENTORY_DATE_FROM_DATEMVT) ? dol_now() : $this->date_inventory; + + $movementstock=new MouvementStock($this->db); + $movementstock->origin = new stdClass(); + $movementstock->origin->element = 'inventory'; + $movementstock->origin->id = $this->id; + $result=$movementstock->_create($user,$fk_product,$fk_warehouse,$op[$movement],$movement,$price,$label,$inventorycode, $datem); + + if ($result >= 0) + { + $this->db->commit(); + return 1; + } + else + { + $this->error=$movementstock->error; + $this->errors=$movementstock->errors; + + $this->db->rollback(); + return -1; + } + } + } + + /** + * Function to regulate stock + * + * @return int + */ + public function regulate() + { + global $langs,$conf; + + if($conf->global->INVENTORY_DISABLE_VIRTUAL) + { + $pdt_virtuel = false; + // Test if virtual product is enabled + if($conf->global->PRODUIT_SOUSPRODUITS) + { + $pdt_virtuel = true; + $conf->global->PRODUIT_SOUSPRODUITS = 0; + } + } + + foreach ($this->Inventorydet as $k => $Inventorydet) + { + $product = new Product($this->db); + $product->fetch($Inventorydet->fk_product); + + if ($Inventorydet->qty_view != $Inventorydet->qty_stock) + { + $Inventorydet->qty_regulated = $Inventorydet->qty_view - $Inventorydet->qty_stock; + $nbpiece = abs($Inventorydet->qty_regulated); + $movement = (int) ($Inventorydet->qty_view < $Inventorydet->qty_stock); // 0 = add ; 1 = remove + + //$href = dol_buildpath('/inventory/inventory.php?id='.$this->id.'&action=view', 1); + + $res = $this->correctStock($product->id, $Inventorydet->fk_warehouse, $nbpiece, $movement, $langs->trans('inventoryMvtStock')); + if ($res < 0) return -1; + } + } + + if($conf->global->INVENTORY_DISABLE_VIRTUAL) + { + // Test if virtual product was enabled before regulate + if($pdt_virtuel) $conf->global->PRODUIT_SOUSPRODUITS = 1; + } + + return 1; + } + + /** + * Get the title + * @return string + */ + public function getTitle() + { + global $langs; + + return !empty($this->title) ? $this->title : $langs->trans('inventoryTitle').' '.$this->id; + } + + + /** + * Return clicable link of object (with eventually picto) + * + * @param int $withpicto Add picto into link + * @return string + */ + public function getNomUrl($withpicto = 1) + { + return ''.($withpicto ? img_picto('','object_list.png','',0).' ' : '').$this->getTitle().''; + } + + /** + * Function to add products by default from warehouse and children + * + * @param int $fk_warehouse id of warehouse + * @param int $fk_category id of category + * @param int $fk_supplier id of supplier + * @param int $only_prods_in_stock only product with stock + * + * @return int + */ + public function addProductsFor($fk_warehouse,$fk_category=0,$fk_supplier=0,$only_prods_in_stock=0) + { + $warehouse = new Entrepot($this->db); + $warehouse->fetch($fk_warehouse); + $TChildWarehouses = array($fk_warehouse); + $warehouse->get_children_warehouses($fk_warehouse, $TChildWarehouses); + + $sql = 'SELECT ps.fk_product, ps.fk_entrepot'; + $sql.= ' FROM '.MAIN_DB_PREFIX.'product_stock ps'; + $sql.= ' INNER JOIN '.MAIN_DB_PREFIX.'product p ON (p.rowid = ps.fk_product)'; + $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_product cp ON (cp.fk_product = p.rowid)'; + $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'product_fournisseur_price pfp ON (pfp.fk_product = p.rowid)'; + $sql.= ' WHERE ps.fk_entrepot IN ('.implode(', ', $TChildWarehouses).')'; + + if ($fk_category>0) $sql.= ' AND cp.fk_categorie='.$fk_category; + if ($fk_supplier>0) $sql.= ' AND pfp.fk_soc = '.$fk_supplier; + if (!empty($only_prods_in_stock)) $sql.= ' AND ps.reel > 0'; + + $sql.=' GROUP BY ps.fk_product, ps.fk_entrepot ORDER BY p.ref ASC,p.label ASC'; + + $res = $this->db->query($sql); + if($res) + { + while($obj = $this->db->fetch_object($res)) + { + $this->addProduct($obj->fk_product, $obj->fk_entrepot); + } + + return 1; + } + else + { + $this->error = $this->db->lasterror(); + $this->errors[] = $this->db->lasterror(); + return -1; + } + } + + /** + * Return clicable link of inventory object + * + * @param int $id id of inventory + * @param int $withpicto Add picto into link + * @return string + */ + static function getLink($id, $withpicto=1) + { + global $langs,$db; + + $inventory = new Inventory($db); + if($inventory->fetch($id, false) > 0) return $inventory->getNomUrl($withpicto); + else return $langs->trans('InventoryUnableToFetchObject'); + } + + /** + * Function to get the sql select of inventory + * @param string $type 'All' to get all data + * @return string + */ + static function getSQL($type) + { + global $conf; + + $sql = ''; + if($type == 'All') + { + $sql = 'SELECT i.rowid,i.title, e.label, i.date_inventory, i.fk_warehouse, i.datec, i.tms, i.status'; + $sql.= ' FROM '.MAIN_DB_PREFIX.'inventory i'; + $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX.'entrepot e ON (e.rowid = i.fk_warehouse)'; + $sql.= ' WHERE i.entity = '.(int) $conf->entity; + } + + return $sql; + } +} + +class Inventorydet extends CoreObject +{ + public $element='inventorydet'; + public $table_element='inventorydet'; + protected $isnolinkedbythird = 1; // No field fk_soc + protected $ismultientitymanaged = 0; // 0=No test on entity, 1=Test with field entity, 2=Test with link by societe + + public $fk_inventory; + public $fk_warehouse; + public $fk_product; + public $entity; + public $qty_view; + public $qty_stock; + public $qty_regulated; + public $pmp; + public $pa; + public $new_pmp; + + protected $__fields=array( + 'fk_inventory'=>array('type'=>'int') + ,'fk_warehouse'=>array('type'=>'int') + ,'fk_product'=>array('type'=>'int') + ,'entity'=>array('type'=>'int') + ,'qty_view'=>array('type'=>'float') + ,'qty_stock'=>array('type'=>'float') + ,'qty_regulated'=>array('type'=>'float') + ,'pmp'=>array('type'=>'float') + ,'pa'=>array('type'=>'float') + ,'new_pmp'=>array('type'=>'float') + ); + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + function __construct(DoliDB &$db) + { + global $conf; + + parent::__construct($db); + parent::init(); + + $this->entity = $conf->entity; + $this->errors = array(); + + $this->product = null; + $this->current_pa = 0; + } + + /** + * Get object and children from database + * + * @param int $id id of inventorydet object + * @param bool $loadChild load children + * @return int + */ + function fetch($id, $loadChild = true) + { + $res = parent::fetch($id); + $this->load_product(); + $this->fetch_current_pa(); + + return $res; + } + + /** + * Function to get the unit buy price + * + * @return bool + */ + function fetch_current_pa() + { + global $db,$conf; + + if(empty($conf->global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)) return false; + + if($this->pa > 0) + { + $this->current_pa = $this->pa; + } + else + { + dol_include_once('/fourn/class/fournisseur.product.class.php'); + $p= new ProductFournisseur($db); + $p->find_min_price_product_fournisseur($this->fk_product); + + if($p->fourn_qty>0) $this->current_pa = $p->fourn_price / $p->fourn_qty; + } + + return true; + } + + /** + * Function to set pa attribute from date en fk_warehouse + * + * @param date $date date value + * @param int $fk_warehouse fk_warehouse target + */ + function setStockDate($date, $fk_warehouse) + { + list($pmp, $stock) = $this->getPmpStockFromDate($date, $fk_warehouse); + + $this->qty_stock = $stock; + $this->pmp = $pmp; + + $last_pa = 0; + $sql = 'SELECT price FROM '.MAIN_DB_PREFIX.'stock_mouvement'; + $sql.= ' WHERE fk_entrepot = '.$fk_warehouse; + $sql.= ' AND fk_product = '.$this->fk_product; + $sql.= ' AND (origintype=\'order_supplier\' || origintype=\'invoice_supplier\')'; + $sql.= ' AND price > 0'; + $sql.= ' AND datem <= \''.$date.' 23:59:59\''; + $sql.= ' ORDER BY datem DESC LIMIT 1'; + + $res = $this->db->query($sql); + if($res && $obj = $this->db->fetch_object($res)) + { + $last_pa = $obj->price; + } + + $this->pa = $last_pa; + } + + + /** + * Get the last pmp and last stock from date and warehouse + * + * @param date $date date to check + * @param int $fk_warehouse id of warehouse + * @return array + */ + function getPmpStockFromDate($date, $fk_warehouse) + { + $res = $this->product->load_stock(); + + if($res>0) + { + $stock = isset($this->product->stock_warehouse[$fk_warehouse]->real) ? $this->product->stock_warehouse[$fk_warehouse]->real : 0; + $pmp = $this->product->pmp; + } + + //All Stock mouvement between now and inventory date + $sql = 'SELECT value, price'; + $sql.= ' FROM '.MAIN_DB_PREFIX.'stock_mouvement'; + $sql.= ' WHERE fk_product = '.$this->product->id; + $sql.= ' AND fk_entrepot = '.$fk_warehouse; + $sql.= ' AND datem > \''.date('Y-m-d 23:59:59', strtotime($date)).'\''; + $sql.= ' ORDER BY datem DESC'; + + $res = $this->db->query($sql); + + $laststock = $stock; + $lastpmp = $pmp; + + if($res) + { + while($mouvement = $this->db->fetch_object($res)) + { + $price = ($mouvement->price > 0 && $mouvement->value > 0) ? $mouvement->price : $lastpmp; + $stock_value = $laststock * $lastpmp; + $laststock -= $mouvement->value; + $last_stock_value = $stock_value - ($mouvement->value * $price); + $lastpmp = ($laststock != 0) ? $last_stock_value / $laststock : $lastpmp; + } + } + + return array($lastpmp, $laststock); + } + + /** + * Fetch the product linked with the line + * @return void + */ + function load_product() + { + global $db; + + if($this->fk_product>0) + { + $this->product = new Product($db); + $this->product->fetch($this->fk_product); + } + } +} diff --git a/htdocs/inventory/img/bt-save.png b/htdocs/inventory/img/bt-save.png new file mode 100644 index 00000000000..f20d292ca91 Binary files /dev/null and b/htdocs/inventory/img/bt-save.png differ diff --git a/htdocs/inventory/img/inventory.png b/htdocs/inventory/img/inventory.png new file mode 100644 index 00000000000..e54ad0eef7d Binary files /dev/null and b/htdocs/inventory/img/inventory.png differ diff --git a/htdocs/inventory/img/plus.png b/htdocs/inventory/img/plus.png new file mode 100644 index 00000000000..b7072a96960 Binary files /dev/null and b/htdocs/inventory/img/plus.png differ diff --git a/htdocs/inventory/img/plus16.png b/htdocs/inventory/img/plus16.png new file mode 100644 index 00000000000..ad17ac89391 Binary files /dev/null and b/htdocs/inventory/img/plus16.png differ diff --git a/htdocs/inventory/inventory.php b/htdocs/inventory/inventory.php new file mode 100644 index 00000000000..c10cbd1ab20 --- /dev/null +++ b/htdocs/inventory/inventory.php @@ -0,0 +1,648 @@ + + * + * 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 htdocs/inventory/inventory.php + * \ingroup product + * \brief File of class to manage inventory + */ + +require_once '../main.inc.php'; + +require_once DOL_DOCUMENT_ROOT.'/core/class/listview.class.php'; +require_once DOL_DOCUMENT_ROOT.'/inventory/class/inventory.class.php'; +require_once DOL_DOCUMENT_ROOT.'/inventory/lib/inventory.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; +include_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php'; + +$langs->load('stock'); +$langs->load('inventory'); + +if(empty($user->rights->inventory->read)) accessforbidden(); + +_action(); + +function _action() +{ + global $user, $db, $conf, $langs; + + /******************************************************************* + * ACTIONS + * + * Put here all code to do according to value of "action" parameter + ********************************************************************/ + + $action=GETPOST('action'); + + switch($action) { + case 'create': + if (empty($user->rights->inventory->create)) accessforbidden(); + + $inventory = new Inventory($db); + + card_warehouse( $inventory); + + break; + + case 'confirmCreate': + if (empty($user->rights->inventory->create)) accessforbidden(); + + $inventory = new Inventory($db); + $inventory->setValues($_POST); + + $fk_inventory = $inventory->create($user); + if($fk_inventory>0) { + + $fk_category = (int) GETPOST('fk_category'); + $fk_supplier = (int) GETPOST('fk_supplier'); + $fk_warehouse = (int) GETPOST('fk_warehouse'); + $only_prods_in_stock = (int) GETPOST('OnlyProdsInStock'); + + $inventory->addProductsFor($fk_warehouse,$fk_category,$fk_supplier,$only_prods_in_stock); + $inventory->update($user); + + header('Location: '.dol_buildpath('/inventory/inventory.php?id='.$inventory->id.'&action=edit', 1)); + + } + else{ + + setEventMessage($inventory->error,'errors'); + header('Location: '.dol_buildpath('/inventory/inventory.php?action=create', 1)); + } + + break; + + case 'edit': + if (!$user->rights->inventory->write) accessforbidden(); + + + $inventory = new Inventory($db); + $inventory->fetch(GETPOST('id')); + + card($inventory, GETPOST('action')); + + break; + + case 'save': + if (!$user->rights->inventory->write) accessforbidden(); + + + $id = GETPOST('id'); + + $inventory = new Inventory($db); + $inventory->fetch($id); + + $inventory->setValues($_REQUEST); + + if ($inventory->errors) + { + setEventMessage($inventory->errors, 'errors'); + card( $inventory, 'edit'); + } + else + { + $inventory->udpate($user); + header('Location: '.dol_buildpath('inventory/inventory.php?id='.$inventory->getId().'&action=view', 1)); + } + + break; + + case 'confirm_regulate': + if (!$user->rights->inventory->write) accessforbidden(); + $id = GETPOST('id'); + + $inventory = new Inventory($db); + $inventory->fetch($id); + + if($inventory->status == 0) { + $inventory->status = 1; + $inventory->update($user); + + card( $inventory, 'view'); + + + } + else { + card( $inventory, 'view'); + } + + break; + + case 'confirm_changePMP': + + $id = GETPOST('id'); + + $inventory = new Inventory($db); + $inventory->fetch( $id ); + + $inventory->changePMP($user); + + card( $inventory, 'view'); + + break; + + case 'add_line': + if (!$user->rights->inventory->write) accessforbidden(); + + $id = GETPOST('id'); + $fk_warehouse = GETPOST('fk_warehouse'); + + $inventory = new Inventory($db); + $inventory->fetch( $id ); + + $fk_product = GETPOST('fk_product'); + if ($fk_product>0) + { + $product = new Product($db); + if($product->fetch($fk_product)<=0 || $product->type != 0) { + setEventMessage($langs->trans('ThisIsNotAProduct'),'errors'); + } + else{ + + //Check product not already exists + $alreadyExists = false; + if(!empty($inventory->Inventorydet)) { + foreach ($inventory->Inventorydet as $invdet) + { + if ($invdet->fk_product == $product->id + && $invdet->fk_warehouse == $fk_warehouse) + { + $alreadyExists = true; + break; + } + } + } + if (!$alreadyExists) + { + if($inventory->addProduct($product->id, $fk_warehouse)) { + setEventMessage($langs->trans('ProductAdded')); + } + } + else + { + setEventMessage($langs->trans('inventoryWarningProductAlreadyExists'), 'warnings'); + } + + } + + $inventory->update($user); + $inventory->sortDet(); + } + + card( $inventory, 'edit'); + + break; + + case 'confirm_delete_line': + if (!$user->rights->inventory->write) accessforbidden(); + + + //Cette action devrais se faire uniquement si le status de l'inventaire est à 0 mais aucune vérif + $rowid = GETPOST('rowid'); + $Inventorydet = new Inventorydet($db); + if($Inventorydet->fetch($rowid)>0) { + $Inventorydet->delete($user); + setEventMessage("ProductDeletedFromInventory"); + } + $id = GETPOST('id'); + $inventory = new Inventory($db); + $inventory->fetch( $id); + + card($inventory, 'edit'); + + break; + case 'confirm_flush': + if (!$user->rights->inventory->create) accessforbidden(); + + + $id = GETPOST('id'); + + $inventory = new Inventory($db); + $inventory->fetch($id); + + $inventory->deleteAllLine($user); + + setEventMessage($langs->trans('InventoryFlushed')); + + card( $inventory, 'edit'); + + + break; + case 'confirm_delete': + if (!$user->rights->inventory->create) accessforbidden(); + + + $id = GETPOST('id'); + + $inventory = new Inventory($db); + $inventory->fetch($id); + + $inventory->delete($user); + + setEventMessage($langs->trans('InventoryDeleted')); + + header('Location: '.dol_buildpath('/inventory/list.php', 1)); + exit; + + break; + case 'exportCSV': + + $id = GETPOST('id'); + + $inventory = new Inventory($db); + $inventory->fetch($id); + + _exportCSV($inventory); + + exit; + break; + + default: + if (!$user->rights->inventory->write) accessforbidden(); + + $id = GETPOST('id'); + + $inventory = new Inventory($db); + $inventory->fetch($id); + + card($inventory, $action ); + + + break; + } + +} + +function card_warehouse(&$inventory) +{ + global $langs,$conf,$db, $user, $form; + + dol_include_once('/categories/class/categorie.class.php'); + + llxHeader('',$langs->trans('inventorySelectWarehouse'),'',''); + print dol_get_fiche_head(inventoryPrepareHead($inventory)); + + echo '
'; + echo ''; + + $formproduct = new FormProduct($db); + + ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
trans('Title') ?>
trans('Date') ?>select_date(time(),'date_inventory'); ?>
trans('inventorySelectWarehouse') ?>selectWarehouses('', 'fk_warehouse') ?>
trans('SelectCategory') ?>select_all_categories(0,'', 'fk_category') ?>
trans('SelectFournisseur') ?>select_thirdparty('','fk_supplier','s.fournisseur = 1') ?>
trans('OnlyProdsInStock') ?>
+ '; + print ''; + print ''; + + echo '
'; + + llxFooter(''); +} + +function card(&$inventory, $action='edit') +{ + global $langs, $conf, $db, $user,$form; + + llxHeader('',$langs->trans('inventoryEdit'),'',''); + + if($action == 'changePMP') + { + print $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$inventory->id, $langs->trans('ApplyNewPMP'), $langs->trans('ConfirmApplyNewPMP', $inventory->getTitle()), 'confirm_changePMP', array(),'no',1); + } + else if($action == 'flush') + { + print $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$inventory->id,$langs->trans('FlushInventory'),$langs->trans('ConfirmFlushInventory',$inventory->getTitle()),'confirm_flush',array(),'no',1); + } + else if($action == 'delete') + { + print $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$inventory->id,$langs->trans('Delete'),$langs->trans('ConfirmDelete',$inventory->getTitle()),'confirm_delete',array(),'no',1); + } + else if($action == 'delete_line') + { + print $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$inventory->id.'&rowid='.GETPOST('rowid'),$langs->trans('DeleteLine'),$langs->trans('ConfirmDeleteLine',$inventory->getTitle()),'confirm_delete_line',array(),'no',1); + } + else if($action == 'regulate') + { + print $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$inventory->id,$langs->trans('RegulateStock'),$langs->trans('ConfirmRegulateStock',$inventory->getTitle()),'confirm_regulate',array(),'no',1); + } + + $warehouse = new Entrepot($db); + $warehouse->fetch($inventory->fk_warehouse); + + print dol_get_fiche_head(inventoryPrepareHead($inventory, $langs->trans('inventoryOfWarehouse', $warehouse->libelle), empty($action) ? '': '&action='.$action)); + + $lines = array(); + card_line($inventory, $lines, $action); + + print ''.$langs->trans('inventoryOnDate')." ".$inventory->getDate('date_inventory').'

'; + + $inventoryTPL = array( + 'id'=> $inventory->id + ,'date_cre' => $inventory->getDate('date_cre', 'd/m/Y') + ,'date_maj' => $inventory->getDate('date_maj', 'd/m/Y H:i') + ,'fk_warehouse' => $inventory->fk_warehouse + ,'status' => $inventory->status + ,'entity' => $inventory->entity + ,'amount' => price( round($inventory->amount,2) ) + ,'amount_actual'=>price (round($inventory->amount_actual,2)) + + ); + + $can_validate = !empty($user->rights->inventory->validate); + $view_url = dol_buildpath('/inventory/inventory.php', 1); + + $view = array( + 'mode' => $action + ,'url' => dol_buildpath('/inventory/inventory.php', 1) + ,'can_validate' => (int) $user->rights->inventory->validate + ,'is_already_validate' => (int) $inventory->status + ,'token'=>$_SESSION['newtoken'] + ); + + include './tpl/inventory.tpl.php'; + + llxFooter(''); +} + + +function card_line(&$inventory, &$lines, $mode) +{ + global $db,$langs,$user,$conf; + $inventory->amount_actual = 0; + + $TCacheEntrepot = array(); + + foreach ($inventory->Inventorydet as $k => $Inventorydet) + { + + $product = & $Inventorydet->product; + $stock = $Inventorydet->qty_stock; + + $pmp = $Inventorydet->pmp; + $pmp_actual = $pmp * $stock; + $inventory->amount_actual+=$pmp_actual; + + $last_pa = $Inventorydet->pa; + $current_pa = $Inventorydet->current_pa; + + $e = new Entrepot($db); + if(!empty($TCacheEntrepot[$Inventorydet->fk_warehouse])) $e = $TCacheEntrepot[$Inventorydet->fk_warehouse]; + elseif($e->fetch($Inventorydet->fk_warehouse) > 0) $TCacheEntrepot[$e->id] = $e; + + $qty = (float)GETPOST('qty_to_add')[$k]; + + $lines[]=array( + 'produit' => $product->getNomUrl(1).' - '.$product->label, + 'entrepot'=>$e->getNomUrl(1), + 'barcode' => $product->barcode, + 'qty' =>($mode == 'edit' ? ' '.img_picto($langs->trans('Add'), 'plus16@inventory').'' : '' ), + 'qty_view' => ($Inventorydet->qty_view ? $Inventorydet->qty_view : 0), + 'qty_stock' => $stock, + 'qty_regulated' => ($Inventorydet->qty_regulated ? $Inventorydet->qty_regulated : 0), + 'action' => ($user->rights->inventory->write && $mode=='edit' ? ''.img_picto($langs->trans('inventoryDeleteLine'), 'delete').'' : ''), + 'pmp_stock'=>round($pmp_actual,2), + 'pmp_actual'=> round($pmp * $Inventorydet->qty_view,2), + 'pmp_new'=>(!empty($user->rights->inventory->changePMP) && $mode == 'edit' ? ' '.img_picto($langs->trans('Save'), 'bt-save.png@inventory').'' : price($Inventorydet->new_pmp)), + 'pa_stock'=>round($last_pa * $stock,2), + 'pa_actual'=>round($last_pa * $Inventorydet->qty_view,2), + 'current_pa_stock'=>round($current_pa * $stock,2), + 'current_pa_actual'=>round($current_pa * $Inventorydet->qty_view,2), + 'k'=>$k, + 'id'=>$Inventorydet->id + ); + } + +} + +function _exportCSV(&$inventory) { + global $conf; + + header('Content-Type: application/octet-stream'); + header('Content-disposition: attachment; filename=inventory-'. $inventory->getId().'-'.date('Ymd-His').'.csv'); + header('Pragma: no-cache'); + header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0'); + header('Expires: 0'); + + echo 'Ref;Label;barcode;qty theorique;PMP;dernier PA;'; + if(!empty($conf->global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)) echo 'PA courant;'; + echo 'qty réelle;PMP;dernier PA;'; + if(!empty($conf->global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)) echo 'PA courant;'; + echo 'qty regulée;'."\r\n"; + + foreach ($inventory->Inventorydet as $k => $Inventorydet) + { + $product = & $Inventorydet->product; + $stock = $Inventorydet->qty_stock; + + $pmp = $Inventorydet->pmp; + $pmp_actual = $pmp * $stock; + $inventory->amount_actual+=$pmp_actual; + + $last_pa = $Inventorydet->pa; + $current_pa = $Inventorydet->current_pa; + + if(!empty($conf->global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)) { + $row=array( + 'produit' => $product->ref + ,'label'=>$product->label + ,'barcode' => $product->barcode + ,'qty_stock' => $stock + ,'pmp_stock'=>round($pmp_actual,2) + ,'pa_stock'=>round($last_pa * $stock,2) + ,'current_pa_stock'=>round($current_pa * $stock,2) + ,'qty_view' => $Inventorydet->qty_view ? $Inventorydet->qty_view : 0 + ,'pmp_actual'=>round($pmp * $Inventorydet->qty_view,2) + ,'pa_actual'=>round($last_pa * $Inventorydet->qty_view,2) + ,'current_pa_actual'=>round($current_pa * $Inventorydet->qty_view,2) + ,'qty_regulated' => $Inventorydet->qty_regulated ? $Inventorydet->qty_regulated : 0 + + ); + + } + else{ + $row=array( + 'produit' => $product->ref + ,'label'=>$product->label + ,'barcode' => $product->barcode + ,'qty_stock' => $stock + ,'pmp_stock'=>round($pmp_actual,2) + ,'pa_stock'=>round($last_pa * $stock,2) + ,'qty_view' => $Inventorydet->qty_view ? $Inventorydet->qty_view : 0 + ,'pmp_actual'=>round($pmp * $Inventorydet->qty_view,2) + ,'pa_actual'=>round($last_pa * $Inventorydet->qty_view,2) + + ,'qty_regulated' => $Inventorydet->qty_regulated ? $Inventorydet->qty_regulated : 0 + + ); + + } + + + echo '"'.implode('";"', $row).'"'."\r\n"; + + } + + exit; +} + +function _footerList($view,$total_pmp,$total_pmp_actual,$total_pa,$total_pa_actual, $total_current_pa,$total_current_pa_actual) { + global $conf,$user,$langs; + + if ($view['can_validate'] == 1) { ?> + +   + barcode->enabled)) { ?> +   + + + + global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)){ + echo ''.price($total_current_pa).''; + } + ?> +   + + rights->inventory->changePMP)) { + echo ' '; + } + ?> + + global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)){ + echo ''.price($total_current_pa_actual).''; + } + ?> + +   + +   + + + + +   Produit + trans('Warehouse'); ?> + barcode->enabled)) { ?> + trans('Barcode'); ?> + + + trans('TheoricalQty'); ?> + global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)){ + echo ''.$langs->trans('TheoricalValue').''; + } + else { + echo ''.$langs->trans('TheoricalValue').''; + } + + ?> + + + trans('RealQty'); ?> + + + global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)) $colspan++; + if(!empty($conf->global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)) $colspan++; + + echo ''.$langs->trans('RealValue').''; + + ?> + + trans('RegulatedQty'); ?> + + + # + + + + + +   + trans('PMP'); ?> + trans('LastPA'); ?> + global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)){ + echo ''.$langs->trans('CurrentPA').''; + } + + ?> +   + trans('PMP'); ?> + rights->inventory->changePMP)) { + echo ''.$langs->trans('ColumnNewPMP').''; + } + ?> + trans('LastPA'); ?> + global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)){ + echo ''.$langs->trans('CurrentPA').''; + } + + ?> +   + +   + + + + * Copyright (C) 2015 ATM Consulting + * + * 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 lib/inventory.lib.php + * \ingroup inventory + * \brief This file is an example module library + */ + +/** + * Define head array for tabs of inventory tools setup pages + * + * @return Array of head + */ +function inventoryAdminPrepareHead() +{ + global $langs, $conf; + + $langs->load("inventory@inventory"); + + $h = 0; + $head = array(); + + $head[$h][0] = dol_buildpath("/admin/inventory.php", 1); + $head[$h][1] = $langs->trans("Parameters"); + $head[$h][2] = 'settings'; + $h++; + + + // Show more tabs from modules + // Entries must be declared in modules descriptor with line + //$this->tabs = array( + // 'entity:+tabname:Title:@inventory:/inventory/mypage.php?id=__ID__' + //); // to add new tab + //$this->tabs = array( + // 'entity:-tabname:Title:@inventory:/inventory/mypage.php?id=__ID__' + //); // to remove a tab + complete_head_from_modules($conf, $langs, $object, $head, $h, 'inventory'); + + return $head; +} + +function inventoryPrepareHead(&$inventory, $title='Inventory', $get='') +{ + global $langs; + + return array( + array(dol_buildpath('/inventory/inventory.php?id='.$inventory->id.$get, 1), $langs->trans($title),'inventory') + ); +} + + + +function inventorySelectProducts(&$inventory) +{ + global $conf,$db,$langs; + + $except_product_id = array(); + + foreach ($inventory->Inventorydet as $Inventorydet) + { + $except_product_id[] = $Inventorydet->fk_product; + } + + ob_start(); + $form = new Form($db); + $form->select_produits(-1, 'fk_product'); + + $TChildWarehouses = array($inventory->fk_warehouse); + $e = new Entrepot($db); + $e->fetch($inventory->fk_warehouse); + if(method_exists($e, 'get_children_warehouses')) $e->get_children_warehouses($e->id, $TChildWarehouses); + + $Tab = array(); + $sql = 'SELECT rowid, label + FROM '.MAIN_DB_PREFIX.'entrepot WHERE rowid IN('.implode(', ', $TChildWarehouses).')'; + if(method_exists($e, 'get_children_warehouses')) $sql.= ' ORDER BY fk_parent'; + $resql = $db->query($sql); + while($res = $db->fetch_object($resql)) { + $Tab[$res->rowid] = $res->label; + } + print '   '; + print $langs->trans('Warehouse').' : '.$form::selectarray('fk_warehouse', $Tab); + + $select_html = ob_get_clean(); + + return $select_html; +} + diff --git a/htdocs/inventory/list.php b/htdocs/inventory/list.php new file mode 100644 index 00000000000..d55996d0407 --- /dev/null +++ b/htdocs/inventory/list.php @@ -0,0 +1,104 @@ + + * + * 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 htdocs/inventory/inventory.php + * \ingroup product + * \brief File of class to manage inventory + */ + +require_once '../main.inc.php'; + +require_once DOL_DOCUMENT_ROOT.'/core/class/listview.class.php'; +require_once DOL_DOCUMENT_ROOT.'/inventory/class/inventory.class.php'; +require_once DOL_DOCUMENT_ROOT.'/inventory/lib/inventory.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; +include_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php'; + +$langs->load("stock"); +$langs->load("inventory"); + +if (empty($user->rights->inventory->read)) accessforbidden(); + +llxHeader('',$langs->trans('inventoryListTitle'),'',''); + +echo '
'; + +$inventory = new Inventory($db); +$list = new ListView($db, 'listInventory'); + +$THide = array('label','title'); + +echo $list->render(Inventory::getSQL('All'), array( + 'limit' => array( + 'nbLine' => GETPOST('limit') + ), + 'allow-field-select' => true, + 'link'=>array( + 'fk_warehouse'=>''.img_picto('','object_stock.png','',0).' @label@' + ), + 'translate'=>array(), + 'hide'=>$THide, + 'type'=>array( + 'datec'=>'date', + 'tms'=>'datetime', + 'date_inventory'=>'date' + ), + 'list'=>array( + 'title'=>$langs->trans('inventoryListTitle'), + 'messageNothing'=>$langs->trans('inventoryListEmpty'), + 'image' => 'inventory.png@inventory' + ), + 'title'=>array( + 'rowid'=>$langs->trans('Title'), + 'date_inventory'=>$langs->trans('InventoryDate'), + 'datec'=>$langs->trans('DateCreation'), + 'fk_warehouse'=>$langs->trans('Warehouse'), + 'tms'=>$langs->trans('DateUpdate'), + 'status'=>$langs->trans('Status') + ), + 'eval'=>array( + 'status' => '(@val@ ? img_picto("'.$langs->trans("inventoryValidate").'", "statut4") : img_picto("'.$langs->trans("inventoryDraft").'", "statut3"))', + 'rowid'=>'Inventory::getLink(@val@)' + ), + 'position' => array( + 'text-align' => array('status' => 'right') + + ), + 'search'=>array( + 'rowid' => array('search_type' => true, 'table' => array('i'), 'field' => array('title')), + 'date_inventory'=>array('search_type' => 'calendars', 'table' => array('i'), 'field' => array('date_inventory')), + 'status'=>array('search_type' => array(1=>$langs->trans("inventoryValidate"), 0=>$langs->trans("inventoryDraft"))) + ) +)); + + +/*if (!empty($user->rights->inventory->create)) +{ + print '
'; + print ''.$langs->trans('inventoryCreate').''; + print '
'; +}*/ + +echo '
'; + +llxFooter(''); +$db->close(); \ No newline at end of file diff --git a/htdocs/inventory/tpl/inventory.tpl.php b/htdocs/inventory/tpl/inventory.tpl.php new file mode 100644 index 00000000000..e3fa8e5421d --- /dev/null +++ b/htdocs/inventory/tpl/inventory.tpl.php @@ -0,0 +1,205 @@ + + +status != 1) { ?> + trans('AddInventoryProduct'); ?> : +
+ + + + + + +
+ + +
+ + +
Cet inventaire est validé
+ + + + + + + $row) { + + $total_pmp+=$row['pmp_stock']; + $total_pa+=$row['pa_stock']; + $total_pmp_actual+=$row['pmp_actual']; + $total_pa_actual+=$row['pa_actual']; + + if($i%20 === 0) + { + _headerList($view); + } // Fin IF principal + ?> + + + + barcode->enabled)) { ?> + + + + + + + global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)){ + echo ''; + $total_current_pa+=$row['current_pa_stock']; + } + + ?> + + + + + rights->inventory->changePMP)) { + echo ''; + } + ?> + + global->INVENTORY_USE_MIN_PA_IF_NO_LAST_PA)){ + echo ''; + $total_current_pa_actual+=$row['current_pa_actual']; + } + + ?> + + + + + + + +
  '.price($row['current_pa_stock']).'   + + '.$row['pmp_new'].''.price($row['current_pa_actual']).'
+ + status != 1) { ?> +
+ + trans('ExportCSV') ?> + trans('Modify') ?> + rights->inventory->changePMP)) { + echo ''.$langs->trans('ApplyPMP').''; + } + + if ($can_validate == 1) { ?> + trans('RegulateStock') ?> + + + + + + + trans('Flush'); ?> +     + trans('Delete') ?> + +
+ + status == 1) { ?> + + +
+

Date de création : getDate('datec') ?> +
Dernière mise à jour : getDate('tms') ?>

+ + + diff --git a/htdocs/langs/en_US/inventory.lang b/htdocs/langs/en_US/inventory.lang new file mode 100644 index 00000000000..db92cb9a14a --- /dev/null +++ b/htdocs/langs/en_US/inventory.lang @@ -0,0 +1,54 @@ +# Dolibarr language file - Source file is en_US - inventory + +Module104420Name = Inventory +Module104420Desc = Create and manage your inventory + +inventorySetup = Inventory Setup + +inventoryCreatePermission=Create new inventory +inventoryReadPermission=View inventories +inventoryWritePermission=Update inventories +inventoryValidatePermission=Validate inventory + +inventoryTitle=Inventory +inventoryListTitle=Inventories +inventoryListEmpty=No inventory in progress + +inventoryCreateDelete=Create/Delete inventory +inventoryCreate=Create new +inventoryEdit=Edit +inventoryValidate=Validated +inventoryDraft=Running +inventorySelectWarehouse=Warehouse choice +inventoryConfirmCreate=Create +inventoryOfWarehouse=Inventory for warehouse : %s +inventoryErrorQtyAdd=Error : one quantity is leaser than zero +inventoryMvtStock=By inventory +inventoryWarningProductAlreadyExists=This product is already into list +SelectCategory=Category filter +SelectFournisseur=Supplier filter +inventoryOnDate=Inventory +INVENTORY_DISABLE_VIRTUAL=Allow to not destock child product from a kit on inventory +INVENTORY_USE_MIN_PA_IF_NO_LAST_PA=Use the buy price if no last buy price can be found +INVENTORY_USE_INVENTORY_DATE_FROM_DATEMVT=Stock mouvment have date of inventory +inventoryChangePMPPermission=Allow to change PMP value for a product +ColumnNewPMP=New unit PMP +OnlyProdsInStock=Do not add product without stock +TheoricalQty=Theorique qty +TheoricalValue=Theorique qty +LastPA=Last BP +CurrentPA=Curent BP +RealQty=Real Qty +RealValue=Real Value +RegulatedQty=Regulated Qty +AddInventoryProduct=Add product to inventory +AddProduct=Add +ApplyPMP=Apply PMP +FlushInventory=Flush inventory +ConfirmFlushInventory=Do you confirm this action ? +InventoryFlushed=Inventory flushed +ExitEditMode=Exit edition +inventoryDeleteLine=Delete line +RegulateStock=Regulate Stock +ListInventory=List +NewInventory=New inventory \ No newline at end of file