diff --git a/htdocs/core/lib/generic.lib.php b/htdocs/core/lib/generic.lib.php
new file mode 100644
index 00000000000..1fba25b349e
--- /dev/null
+++ b/htdocs/core/lib/generic.lib.php
@@ -0,0 +1,285 @@
+
+ *
+ * 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 .
+ */
+//global $db;
+global $langs;
+
+
+/*
+ * function to genegate a select list from a table, the showed text will be a concatenation of some
+ * column defined in column bit, the Least sinificative bit will represent the first colum
+ *
+ * @param object $db db Object to do the querry
+ * @param string $table table which the enum refers to (without prefix)
+ * @param string $fieldValue field of the table which the enum refers to
+ * @param string $htmlName name to the form select
+ * @param string $selected which value must be selected
+ * @param string $selectparam to add parameters to the select
+ * @param array(string) $addtionnalChoices array of additionnal fields Array['VALUE']=string to show
+ * @return string html code
+ */
+
+function select_enum($table, $fieldValue,$htmlName,$selected='',$selectparam='',$addtionnalChoices=null){
+global $langs;
+global $db;
+ if($table=='' || $fieldValue=='' || $htmlName=='' )
+ {
+ return 'error, one of the mandatory field of the function select_enum is missing';
+ }
+ $sql='SHOW COLUMNS FROM ';//llx_hr_event_time LIKE 'audience'";
+ $sql.=MAIN_DB_PREFIX.$table.' WHERE Field="';
+ $sql.=$fieldValue.'"';
+ //$sql.= " ORDER BY t.".$field;
+
+ dol_syslog('form::select_enum sql='.$sql, LOG_DEBUG);
+
+ $resql=$db->query($sql);
+
+ if ($resql)
+ {
+ $i=0;
+ //return $table."this->db".$field;
+ $num = $db->num_rows($resql);
+ if($num)
+ {
+
+ $obj = $db->fetch_object($resql);
+ if ($obj && strpos($obj->Type,'enum(')===0)
+ {
+ if(empty($selected) && !empty($obj->Default))$selected="'{$obj->Default}'";
+ $select.='\n";
+ }else{
+ $select="";
+ }
+
+ }else{
+ $select="";
+ }
+ }
+ else
+ {
+ $error++;
+ dol_print_error($db);
+ $select="";
+ }
+
+ return $select;
+
+ }
+/*
+ * function to genegate a select list from a table, the showed text will be a concatenation of some
+ * column defined in column bit, the Least sinificative bit will represent the first colum
+ *
+ * @param object $db db Object to do the querry
+ * @param string $table table which the fk refers to (without prefix)
+ * @param string $fieldValue field of the table which the fk refers to, the one to put in the Valuepart
+ * @param string $htmlName name to the form select
+ * @param string $fieldToShow1 first part of the concatenation
+ * @param string $fieldToShow1 second part of the concatenation
+ * @param string $selected which value must be selected
+ * @param string $separator separator between the tow contactened fileds
+* @param string $sqlTail to limit per entity, to filter ...
+* @param string $selectparam to add parameters to the select
+ * @param array(string) $addtionnalChoices array of additionnal fields Array['VALUE']=string to show
+
+ * @return string html code
+ */
+function select_generic($table, $fieldValue,$htmlName,$fieldToShow1,$fieldToShow2='',$selected='',$separator=' - ',$sqlTail='', $selectparam='', $addtionnalChoices=array('NULL'=>'NULL')){
+ //
+ //return 'tada';
+ global $conf,$langs,$db;
+ if($table=='' || $fieldValue=='' || $fieldToShow1=='' || $htmlName=='' )
+ {
+ return 'error, one of the mandatory field of the function select_generic is missing';
+ }
+ $select="\n";
+ if ($conf->use_javascript_ajax)
+ {
+ include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
+ $comboenhancement = ajax_combobox($htmlName);
+ $select.=$comboenhancement;
+ $nodatarole=($comboenhancement?' data-role="none"':'');
+ }
+ $select.='\n";
+ return $select;
+
+ }
+
+
+/*
+ * function to genegate a select list from a table, the showed text will be a concatenation of some
+ * column defined in column bit, the Least sinificative bit will represent the first colum
+ *
+ * @param object $db db Object to do the querry
+ * @param string $table table which the fk refers to (without prefix)
+ * @param string $fieldValue field of the table which the fk refers to, the one to put in the Valuepart
+ * @param string $selected value selected of the field value column
+ * @param string $fieldToShow1 first part of the concatenation
+ * @param string $fieldToShow1 second part of the concatenation
+ * @param string $separator separator between the tow contactened fileds
+ * @param string $sqlTail to limit per entity, to filter ...
+
+ * @return string html code
+ */
+function print_generic($table, $fieldValue,$selected,$fieldToShow1,$fieldToShow2="",$separator=' - ',$sqltail="",$sqljoin=""){
+ //return $table.$db.$field;
+ global $db;
+ if($table=="" || $fieldValue=="" || $fieldToShow1=='')
+ {
+ return "error, one of the mandatory field of the function print_generic is missing";
+ }else if (empty($selected)){
+ return "NuLL";
+ }
+
+ $sql="SELECT";
+ $sql.=" t.".$fieldValue;
+ $sql.=" ,".$fieldToShow1;
+ if(!empty($fieldToShow2))
+ $sql.=" ,".$fieldToShow2;
+ $sql.= " FROM ".MAIN_DB_PREFIX.$table." as t";
+ if(!empty($sqljoin))
+ $sql.=' '.$sqljoin;
+ $sql.= " WHERE t.".$fieldValue."=".$selected;
+ if(!empty($sqlTail))
+ $sql.=' '.$sqlTail;
+
+ dol_syslog("form::print_generic sql=".$sql, LOG_DEBUG);
+
+ $resql=$db->query($sql);
+
+ if ($resql)
+ {
+ // support AS in the fields ex $field1='CONTACT(u.firstname,' ',u.lastname) AS fullname'
+ // with sqltail= 'JOIN llx_user as u ON t.fk_user=u.rowid'
+ $starfields1=strpos($fieldToShow1,' AS ');
+ if($starfields1>0){
+ $fieldToShow1= substr($fieldToShow1, $starfields1+4);
+ }
+ $starfields2=strpos($fieldToShow2,' AS ');
+ if($starfields2>0){
+ $fieldToShow2=substr($fieldToShow2, $starfields2+4);
+ }
+
+ $num = $db->num_rows($resql);
+ if ( $num)
+ {
+ $obj = $db->fetch_object($resql);
+
+ if ($obj)
+ {
+ $select=$obj->{$fieldToShow1};
+ if(!empty($fieldToShow2))
+ $select.=$separator.$obj->{$fieldToShow2};
+ }else{
+ $select= "NULL";
+ }
+ }else{
+ $select= "NULL";
+ }
+ }
+ else
+ {
+ $error++;
+ dol_print_error($db);
+ $select.= "ERROR";
+ }
+ //$select.="\n";
+ return $select;
+ }
+
+ /*
+ * function to genegate a select list from a table, the showed text will be a concatenation of some
+ * column defined in column bit, the Least sinificative bit will represent the first colum
+ *
+ * @param object $db db Object to do the querry
+ * @param int/array $userid ID of the user you want to get the subordinate liste * @param int $userid ID of the user you want to get the subordinate liste
+ * @param int $entity entity
+ * @return array List of the subordinate ids and level [[id][lvl]]
+ */
\ No newline at end of file
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 ceaf1f5be90..94e06271636 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
@@ -101,6 +101,7 @@ ALTER TABLE llx_facturedet_rec ADD COLUMN vat_src_code varchar(10) DEFAULT '' AF
ALTER TABLE llx_extrafields ADD COLUMN langs varchar(24);
ALTER TABLE llx_supplier_proposaldet ADD COLUMN fk_unit integer DEFAULT NULL;
+ALTER TABLE llx_projet_task_time ADD COLUMN status enum('DRAFT','SUBMITTED','APPROVED','CANCELLED','REJECTED','CHALLENGED','INVOICED','UNDERAPPROVAL') DEFAULT 'DRAFT';
ALTER TABLE llx_ecm_files ADD COLUMN ref varchar(128) AFTER rowid;
ALTER TABLE llx_ecm_files CHANGE COLUMN fullpath filepath varchar(255);
diff --git a/htdocs/install/mysql/tables/llx_projet_task_time.sql b/htdocs/install/mysql/tables/llx_projet_task_time.sql
index 897364a90e1..798006597d3 100644
--- a/htdocs/install/mysql/tables/llx_projet_task_time.sql
+++ b/htdocs/install/mysql/tables/llx_projet_task_time.sql
@@ -31,5 +31,6 @@ create table llx_projet_task_time
import_key varchar(14), -- Import key
datec date, -- Date creation time
tms timestamp, -- Date update time
+ status enum('DRAFT','SUBMITTED','APPROVED','CANCELLED','REJECTED','CHALLENGED','INVOICED','UNDERAPPROVAL') DEFAULT 'DRAFT';
note text -- A comment
)ENGINE=innodb;
diff --git a/htdocs/projet/projectInvoice.php b/htdocs/projet/projectInvoice.php
new file mode 100644
index 00000000000..ec3b8b69919
--- /dev/null
+++ b/htdocs/projet/projectInvoice.php
@@ -0,0 +1,355 @@
+
+ *
+ * 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 .
+ */
+
+
+//FIXME new param needed
+define('INVOICE_METHOD','user');
+define('INVOICE_TASKTIME','all');
+define('INVOICE_SERVICE','-999');
+define('INVOICE_SHOW_TASK','1');
+define('INVOICE_SHOW_USER','1');
+
+//load class
+require '../main.inc.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/generic.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
+
+//get param
+$staticProject=new Project($db);
+$projectId=GETPOST('projectid');
+
+$socid=GETPOST('socid');
+$month=GETPOST('month');
+$year=GETPOST('year');
+$mode=GETPOST('invoicingMethod');
+$step=GETPOST('step');
+$ts2Invoice=GETPOST('ts2Invoice');
+$tsNotInvoiced=GETPOST('tsNotInvoiced');
+$userid= is_object($user)?$user->id:$user;
+//init handling object
+$form = new Form($db);
+
+//FIXME check autorisation for project and page/
+
+if ($user->rights->facture->creer & hasProjectRight($userid,$projectid))
+{
+ if($projectId>0)$staticProject->fetch($projectId);
+ if($socid==0 || !is_numeric($socid))$socid=$staticProject->socid; //FIXME check must be in place to ensure the user hqs the right to see the project details
+$edit=1;
+// avoid SQL issue
+if(empty($month) || empty($year) || empty($projectId))$step=1;
+//steps
+ switch ($step)
+ {
+ case '2':{
+ $fields=($mode=='user')?'fk_user':(($mode=='taskUser')?'fk_user,fk_task':'fk_task');
+ $sql= 'SELECT '.$fields.', SUM(tt.task_duration) as duration ';
+ $sql.=', GROUP_CONCAT(tt.rowid SEPARATOR ", ") as task_time_list';
+ $sql.=' From '.MAIN_DB_PREFIX.'projet_task_time as tt';
+ $sql.=' JOIN '.MAIN_DB_PREFIX.'projet_task as t ON tt.fk_task=t.rowid';
+ $sql.=' WHERE t.fk_projet='.$projectId;
+ $sql.=' AND MONTH(tt.task_date)='.$month;
+ $sql.=' AND YEAR(tt.task_date)='.$year;
+ if($ts2Invoice!='all'){
+ /*$sql.=' AND tt.rowid IN(SELECT GROUP_CONCAT(fk_project_tasktime_list SEPARATOR ", ")';
+ $sql.=' FROM '.MAIN_DB_PREFIX.'project_tasktime_approval';
+ $sql.=' WHERE status= "APPROVED" AND MONTH(start_date)='.$month;
+ $sql.=' AND YEAR(start_date)="'.$year.'")';
+ $sql.=' AND YEAR(start_date)="'.$year.'")'; */
+ $sql.=' AND tt.status = "APPROVED"';
+ }
+ if($tsNotInvoiced==1){
+ $sql.=' AND tt.invoice_id IS NULL';
+ }
+ $sql.=' GROUP BY '.$fields;
+ dol_syslog('timesheet::timesheetProjectInvoice step2', LOG_DEBUG);
+
+
+ $Form ='