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 ='
'."\n\t"; + $Form .=''; + $Form .=''; + $Form .=''; + $Form .=''; + $Form .=''; + $Form .=''; + + $resql=$db->query($sql); + $num=0; + $resArray=array(); + if ($resql) + { + $num = $db->num_rows($resql); + $i = 0; + + // Loop on each record found, + while ($i < $num) + { + $error=0; + $obj = $db->fetch_object($resql); + $duration=floor($obj->duration/3600).":".str_pad (floor($obj->duration%3600/60),2,"0",STR_PAD_LEFT); + switch($mode){ + case 'user': + //step 2.2 get the list of user (all or approved) + $resArray[]=array("USER" => $obj->fk_user,"TASK" =>'any',"DURATION"=>$duration,'LIST'=>$obj->task_time_list); + break; + case 'taskUser': + //step 2.3 get the list of taskUser (all or approved) + $resArray[]=array("USER" => $obj->fk_user,"TASK" =>$obj->fk_task,"DURATION"=>$duration,'LIST'=>$obj->task_time_list); + break; + default: + case 'task': + //step 2.1 get the list of task (all or approved) + $resArray[]=array("USER" => "any","TASK" =>$obj->fk_task,"DURATION"=>$duration,'LIST'=>$obj->task_time_list); + break; + } + + $i++; + } + $db->free($resql); + }else + { + dol_print_error($db); + return ''; + } +//var_dump($resArray); + //FIXME asign a service + price to each array elements (or price +auto generate name + $Form .=''."\n\t\t"; + $Form .=''; + $Form .=''; + $Form .=''; + $Form .=''; + $Form .=''; + $form = new Form($db); + foreach($resArray as $res){ + $Form .=htmlPrintServiceChoice($res["USER"],$res["TASK"],'pair',$res["DURATION"],$res['LIST'],$mysoc,$socid); + } + + $Form .='
'.$langs->trans("Step").' 2
'.$langs->trans("User").''.$langs->trans("Task").''.$langs->trans("Service").''.$langs->trans("Description").''.$langs->trans("PriceHT").''.$langs->trans("VAT").''.$langs->trans("unitDuration").''.$langs->trans("Duration").'
'; + $Form .='\n"; + + + + break;} + case 3: // review choice and list of item + quantity ( editable) + require_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture.class.php'; + require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; + $object = new Facture($db); + + $db->begin(); + $error = 0; + + $dateinvoice = time(); + //$date_pointoftax = dol_mktime(12, 0, 0, $_POST['date_pointoftaxmonth'], $_POST['date_pointoftaxday'], $_POST['date_pointoftaxyear']); + // Si facture standard + $object->socid = $socid; + $object->type = Facture::TYPE_STANDARD; + $object->date = $dateinvoice; + $object->fk_project = $projectid; + $object->fetch_thirdparty(); + $id = $object->create($user); + $resArray=$_POST['userTask']; + $task_time_array=array(); + if(is_array($resArray)){ + foreach($resArray as $uId =>$userTaskService){ + //$userTaskService[$user][$task]=array('duration', 'VAT','Desc','PriceHT','Service','unit_duration','unit_duration_unit'); + if(is_array($userTaskService ))foreach($userTaskService as $tId => $service){ + $durationTab=explode (':',$service['duration']); + $duration=$durationTab[1]*60+$durationTab[0]*3600; + $startday = dol_mktime(12, 0, 0, $month, 1, $year); + $endday = dol_mktime(12, 0, 0, $month, date('t',$startday), $year); + var_dump($endday); + $details=''; + $result =''; + if(($tId!='any') && INVOICE_SHOW_TASK)$details="\n".$service['taskLabel']; + if(($uId!='any')&& INVOICE_SHOW_USER)$details.="\n".$service['userName']; + + if($service['Service']>0){ + $product = new Product($db); + $product->fetch($service['Service']); + + $unit_duration_unit=substr($product->duration, -1); + $factor=($unit_duration_unit=='h')?3600:8*3600;//FIXME support week and month + $factor=$factor*intval(substr($product->duration,0, -1)); + $quantity= $duration/$factor; + $result = $object->addline($product->description.$details, $product->price, $quantity, $product->tva_tx, $product->localtax1_tx, $product->localtax2_tx, $service['Service'], 0, $startday, $endday, 0, 0, '', $product->price_base_type, $product->price_ttc, $product->type, -1, 0, '', 0, 0, null, 0, '', 0, 100, '', $product->fk_unit); + + + }elseif ($service['Service']<>-999){ + $factor=($service['unit_duration_unit']=='h')?3600:8*3600;//FIXME support week and month + $factor=$factor*intval($service['unit_duration']); + + $quantity= $duration/$factor; + $result = $object->addline($service['Desc'].$details, $service['PriceHT'], $quantity, $service['VAT'], '', '', '', 0, $startday, $endday, 0, 0, '', 'HT', '', 1, -1, 0, '', 0, 0, null, 0, '', 0, 100, '', ''); + + } + if($service['taskTimeList']<>'' && $result>0)$task_time_array[$result]=$service['taskTimeList']; + }else $error++; + } + }else $error++; + + // End of object creation, we show it + if ($id > 0 && ! $error) + { + $db->commit(); + foreach($task_time_array AS $idLine=> $task_time_list){ + //dol_syslog("ProjectInvoice::setnvoice".$idLine.' '.$task_time_list, LOG_DEBUG); + Update_task_time_invoice($id,$idLine,$task_time_list); + } + + header('Location: ' . $object->getNomUrl(0,'',0,1,'')); + exit(); + } + else + { + $db->rollback(); + //header('Location: ' . $_SERVER["PHP_SELF"] . '?step=0'); + setEventMessages($object->error, $object->errors, 'errors'); + + } + + break; + + case 1: + +$edit=0; + case 0: + default: + require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php'; + $htmlother = new FormOther($db); + $sqlTail=''; + + if(!$user->admin){ + $sqlTail=' JOIN llx_element_contact ON t.rowid= element_id '; + $sqlTail.=' WHERE fk_c_type_contact = "160" '; + $sqlTail.=' AND fk_socpeople="'.$userid.'"'; + } + $Form =''."\n\t"; + $Form .=''."\n\t\t"; + $Form .=''; + // $Form .=''; + // $Form .=''; + $Form .='\n\t\t"; + +//cust list + $Form .=''; +//all ts or only approved + $ts2Invoice=INVOICE_TASKTIME; + $Form .='"; +// not alreqdy invoice + $Form .=''; + + $Form .='
'.$langs->trans("Step").' '.$step.''; + $invoicingMethod=INVOICE_METHOD; + $Form .='
'.$langs->trans('Project').''; + $Form .=select_generic('projet', 'rowid','projectid','ref','title',$projectId,' - ','', 'fk_status=1',null); + $Form .='
'.$langs->trans('Month').' - '.$langs->trans('Year').''.$htmlother->select_month($month, 'month').' - '.$htmlother->selectyear($year,'year',1,10,3).'
'.$langs->trans('Month').'
'.$langs->trans('Customer').'
'.$langs->trans('Mode').' '.$langs->trans("Task").' '; + $Form .=' '.$langs->trans("User")." "; + $Form .=' '.$langs->trans("Task").'&'.$langs->trans("User")."
'.$langs->trans('Customer').''.$form->select_company($socid, 'socid', '(s.client=1 OR s.client=2 OR s.client=3)', 1).'
'.$langs->trans('TimesheetToInvoice').' '.$langs->trans("approvedOnly").' '; + $Form .=' '.$langs->trans("All")."
'.$langs->trans('TimesheetNotInvoiced'); + $Form .='
'; + + $Form .='\n"; + + break; + } +}else{ + $accessforbidden = accessforbidden("you don't have enough rights to see this page"); +} +/*************************************************** +* VIEW +* +* Put here all code to build page +****************************************************/ +$morejs=array("/timesheet/core/js/jsparameters.php","/timesheet/core/js/timesheet.js"); +llxHeader('',$langs->trans('TimesheetInvoice'),'','','','',$morejs); + + +print $Form; + + + +llxFooter(); +$db->close(); + + + +/*************************************************** +* FUNCTIONS +* +* Put here all code of the functions +****************************************************/ + + + +/*** + * Function to print the line to chose between a predefined service or an ad-hoc one + */ +function htmlPrintServiceChoice($user,$task,$class,$duration,$tasktimelist,$seller,$byer){ + global $form,$langs; + $userName=($user=='any')?(' - '):print_generic('user','rowid',$user,'lastname','firstname',' '); + $taskLabel=($task=='any')?(' - '):print_generic('projet_task','rowid',$task,'ref','label',' '); + $html=''.$userName; + $html.=''.$taskLabel; + $html.=''; + $html.=''; + $html.=''; + $defaultService=INVOICE_SERVICE; + $addchoices=array('-999'=> $langs->trans('not2invoice')); + $html.=''.select_generic('product', 'rowid','userTask['.$user.']['.$task.'][Service]','ref','description',$defaultService,$separator=' - ',$sqlTail='', $selectparam='tosell=1 AND fk_product_type=1',$addchoices).''; + $html.=''; + $html.=''; + //$html.=''; + $html.=''.$form->load_tva('userTask['.$user.']['.$task.'][VAT]', -1, $seller, $buyer, 0, 0, '', false, 1).''; + $html.=''; + $html.='
'.$langs->trans('Hour'); + $html.='
'.$langs->trans('Days').''; + $html.=''; + + $html.=''; + return $html; +} + +function hasProjectRight($userid,$projectid){ + global $db,$user; + $res=true; + if($projectid && !$user->admin){ + $sql=' SELECT rowid FROM '.MAIN_DB_PREFIX.'element_contact '; + $sql.=' WHERE fk_c_type_contact = "160" AND element_id="'.$projectid; + $sql.='" AND fk_socpeople="'.$userid.'"'; + $resql=$db->query($sql); + if (!$resql)$res=false; + } + return $res; +} + +function Update_task_time_invoice($idInvoice, $idLine,$task_time_list){ + global $db; + $res=true; + $sql='UPDATE '.MAIN_DB_PREFIX.'projet_task_time'; + $sql.=" SET invoice_id={$idInvoice}, invoice_line_id={$idLine}"; + $sql.=" WHERE rowid in ({$task_time_list})"; + dol_syslog("ProjectInvoice::setnvoice", LOG_DEBUG); + $resql=$db->query($sql); + if (!$resql)$res=false; + return $res; +} \ No newline at end of file