Merge pull request #19765 from FHenry/dev_19763

NEW: Can invoice task time per different services
This commit is contained in:
Laurent Destailleur 2022-07-08 00:06:16 +02:00 committed by GitHub
commit d97b882cf8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 302 additions and 93 deletions

View File

@ -3820,6 +3820,7 @@ class Facture extends CommonInvoice
return -2; return -2;
} }
} else { } else {
$this->errors[]='status of invoice must be Draft to allow use of ->addline()';
dol_syslog(get_class($this)."::addline status of invoice must be Draft to allow use of ->addline()", LOG_ERR); dol_syslog(get_class($this)."::addline status of invoice must be Draft to allow use of ->addline()", LOG_ERR);
return -3; return -3;
} }

View File

@ -259,7 +259,7 @@ TimeSpentInvoiced=Time spent billed
TimeSpentForIntervention=Time spent TimeSpentForIntervention=Time spent
TimeSpentForInvoice=Time spent TimeSpentForInvoice=Time spent
OneLinePerUser=One line per user OneLinePerUser=One line per user
ServiceToUseOnLines=Service to use on lines ServiceToUseOnLines=Service to use on lines by default
InvoiceGeneratedFromTimeSpent=Invoice %s has been generated from time spent on project InvoiceGeneratedFromTimeSpent=Invoice %s has been generated from time spent on project
InterventionGeneratedFromTimeSpent=Intervention %s has been generated from time spent on project InterventionGeneratedFromTimeSpent=Intervention %s has been generated from time spent on project
ProjectBillTimeDescription=Check if you enter timesheet on tasks of project AND you plan to generate invoice(s) from the timesheet to bill the customer of the project (do not check if you plan to create invoice that is not based on entered timesheets). Note: To generate invoice, go on tab 'Time spent' of the project and select lines to include. ProjectBillTimeDescription=Check if you enter timesheet on tasks of project AND you plan to generate invoice(s) from the timesheet to bill the customer of the project (do not check if you plan to create invoice that is not based on entered timesheets). Note: To generate invoice, go on tab 'Time spent' of the project and select lines to include.

View File

@ -6159,6 +6159,43 @@ class Product extends CommonObject
dol_print_error($this->db); dol_print_error($this->db);
} }
} }
/**
* Return the duration in Hours of a service base on duration fields
* @return int -1 KO, >= 0 is the duration in hours
*/
public function getProductDurationHours()
{
global $langs;
if (empty($this->duration_value)) {
$this->errors[]='ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice';
return -1;
}
if ($this->duration_unit == 'i') {
$prodDurationHours = 1. / 60;
}
if ($this->duration_unit == 'h') {
$prodDurationHours = 1.;
}
if ($this->duration_unit == 'd') {
$prodDurationHours = 24.;
}
if ($this->duration_unit == 'w') {
$prodDurationHours = 24. * 7;
}
if ($this->duration_unit == 'm') {
$prodDurationHours = 24. * 30;
}
if ($this->duration_unit == 'y') {
$prodDurationHours = 24. * 365;
}
$prodDurationHours *= $this->duration_value;
return $prodDurationHours;
}
} }

View File

@ -127,6 +127,7 @@ class Task extends CommonObjectLine
public $timespent_fk_user; public $timespent_fk_user;
public $timespent_thm; public $timespent_thm;
public $timespent_note; public $timespent_note;
public $timespent_fk_product;
public $comments = array(); public $comments = array();
@ -1227,6 +1228,7 @@ class Task extends CommonObjectLine
$sql .= ", task_date_withhour"; $sql .= ", task_date_withhour";
$sql .= ", task_duration"; $sql .= ", task_duration";
$sql .= ", fk_user"; $sql .= ", fk_user";
$sql .= ", fk_product";
$sql .= ", note"; $sql .= ", note";
$sql .= ", datec"; $sql .= ", datec";
$sql .= ") VALUES ("; $sql .= ") VALUES (";
@ -1236,6 +1238,7 @@ class Task extends CommonObjectLine
$sql .= ", ".(empty($this->timespent_withhour) ? 0 : 1); $sql .= ", ".(empty($this->timespent_withhour) ? 0 : 1);
$sql .= ", ".((int) $this->timespent_duration); $sql .= ", ".((int) $this->timespent_duration);
$sql .= ", ".((int) $this->timespent_fk_user); $sql .= ", ".((int) $this->timespent_fk_user);
$sql .= ", ".((int) $this->timespent_fk_product);
$sql .= ", ".(isset($this->timespent_note) ? "'".$this->db->escape($this->timespent_note)."'" : "null"); $sql .= ", ".(isset($this->timespent_note) ? "'".$this->db->escape($this->timespent_note)."'" : "null");
$sql .= ", '".$this->db->idate($now)."'"; $sql .= ", '".$this->db->idate($now)."'";
$sql .= ")"; $sql .= ")";
@ -1523,6 +1526,7 @@ class Task extends CommonObjectLine
$sql .= " t.task_date_withhour,"; $sql .= " t.task_date_withhour,";
$sql .= " t.task_duration,"; $sql .= " t.task_duration,";
$sql .= " t.fk_user,"; $sql .= " t.fk_user,";
$sql .= " t.fk_product,";
$sql .= " t.thm,"; $sql .= " t.thm,";
$sql .= " t.note"; $sql .= " t.note";
$sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as t"; $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as t";
@ -1541,6 +1545,7 @@ class Task extends CommonObjectLine
$this->timespent_withhour = $obj->task_date_withhour; $this->timespent_withhour = $obj->task_date_withhour;
$this->timespent_duration = $obj->task_duration; $this->timespent_duration = $obj->task_duration;
$this->timespent_fk_user = $obj->fk_user; $this->timespent_fk_user = $obj->fk_user;
$this->timespent_fk_product = $obj->fk_product;
$this->timespent_thm = $obj->thm; // hourly rate $this->timespent_thm = $obj->thm; // hourly rate
$this->timespent_note = $obj->note; $this->timespent_note = $obj->note;
} }
@ -1694,6 +1699,7 @@ class Task extends CommonObjectLine
$sql .= " task_date_withhour = ".(empty($this->timespent_withhour) ? 0 : 1).","; $sql .= " task_date_withhour = ".(empty($this->timespent_withhour) ? 0 : 1).",";
$sql .= " task_duration = ".((int) $this->timespent_duration).","; $sql .= " task_duration = ".((int) $this->timespent_duration).",";
$sql .= " fk_user = ".((int) $this->timespent_fk_user).","; $sql .= " fk_user = ".((int) $this->timespent_fk_user).",";
$sql .= " fk_product = ".((int) $this->timespent_fk_product).",";
$sql .= " note = ".(isset($this->timespent_note) ? "'".$this->db->escape($this->timespent_note)."'" : "null"); $sql .= " note = ".(isset($this->timespent_note) ? "'".$this->db->escape($this->timespent_note)."'" : "null");
$sql .= " WHERE rowid = ".((int) $this->timespent_id); $sql .= " WHERE rowid = ".((int) $this->timespent_id);

View File

@ -73,6 +73,7 @@ $search_task_ref = GETPOST('search_task_ref', 'alpha');
$search_task_label = GETPOST('search_task_label', 'alpha'); $search_task_label = GETPOST('search_task_label', 'alpha');
$search_user = GETPOST('search_user', 'int'); $search_user = GETPOST('search_user', 'int');
$search_valuebilled = GETPOST('search_valuebilled', 'int'); $search_valuebilled = GETPOST('search_valuebilled', 'int');
$search_product_ref = GETPOST('search_product_ref', 'alpha');
$search_company = GETPOST('$search_company', 'alpha'); $search_company = GETPOST('$search_company', 'alpha');
$limit = GETPOST('limit', 'int') ?GETPOST('limit', 'int') : $conf->liste_limit; $limit = GETPOST('limit', 'int') ?GETPOST('limit', 'int') : $conf->liste_limit;
@ -166,6 +167,7 @@ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x'
$search_task_label = ''; $search_task_label = '';
$search_user = 0; $search_user = 0;
$search_valuebilled = ''; $search_valuebilled = '';
$search_product_ref = '';
$toselect = array(); $toselect = array();
$search_array_options = array(); $search_array_options = array();
$action = ''; $action = '';
@ -220,6 +222,7 @@ if ($action == 'addtimespent' && $user->rights->projet->time) {
$object->timespent_date = dol_mktime(12, 0, 0, GETPOST("timemonth", 'int'), GETPOST("timeday", 'int'), GETPOST("timeyear", 'int')); $object->timespent_date = dol_mktime(12, 0, 0, GETPOST("timemonth", 'int'), GETPOST("timeday", 'int'), GETPOST("timeyear", 'int'));
} }
$object->timespent_fk_user = GETPOST("userid", 'int'); $object->timespent_fk_user = GETPOST("userid", 'int');
$object->timespent_fk_product = GETPOST("fk_product", 'int');
$result = $object->addTimeSpent($user); $result = $object->addTimeSpent($user);
if ($result >= 0) { if ($result >= 0) {
setEventMessages($langs->trans("RecordSaved"), null, 'mesgs'); setEventMessages($langs->trans("RecordSaved"), null, 'mesgs');
@ -271,18 +274,18 @@ if (($action == 'updateline' || $action == 'updatesplitline') && !$cancel && $us
$object->timespent_date = dol_mktime(12, 0, 0, GETPOST("timelinemonth"), GETPOST("timelineday"), GETPOST("timelineyear")); $object->timespent_date = dol_mktime(12, 0, 0, GETPOST("timelinemonth"), GETPOST("timelineday"), GETPOST("timelineyear"));
} }
$object->timespent_fk_user = GETPOST("userid_line", 'int'); $object->timespent_fk_user = GETPOST("userid_line", 'int');
$object->timespent_fk_product = GETPOST("fk_product", 'int');
$result = 0; $result = 0;
if (in_array($object->timespent_fk_user, $childids) || $user->rights->projet->all->creer) { if (in_array($object->timespent_fk_user, $childids) || $user->rights->projet->all->creer) {
$result = $object->addTimeSpent($user); $result = $object->addTimeSpent($user);
}
if ($result >= 0) { if ($result >= 0) {
setEventMessages($langs->trans("RecordSaved"), null, 'mesgs'); setEventMessages($langs->trans("RecordSaved"), null, 'mesgs');
} else { } else {
setEventMessages($langs->trans($object->error), null, 'errors'); setEventMessages($langs->trans($object->error), null, 'errors');
$error++; $error++;
} }
}
} else { } else {
$object->fetch($id, $ref); $object->fetch($id, $ref);
@ -298,6 +301,7 @@ if (($action == 'updateline' || $action == 'updatesplitline') && !$cancel && $us
$object->timespent_date = dol_mktime(12, 0, 0, GETPOST("timelinemonth", 'int'), GETPOST("timelineday", 'int'), GETPOST("timelineyear", 'int')); $object->timespent_date = dol_mktime(12, 0, 0, GETPOST("timelinemonth", 'int'), GETPOST("timelineday", 'int'), GETPOST("timelineyear", 'int'));
} }
$object->timespent_fk_user = GETPOST("userid_line", 'int'); $object->timespent_fk_user = GETPOST("userid_line", 'int');
$object->timespent_fk_product = GETPOST("fk_product", 'int');
$result = 0; $result = 0;
if (in_array($object->timespent_fk_user, $childids) || $user->rights->projet->all->creer) { if (in_array($object->timespent_fk_user, $childids) || $user->rights->projet->all->creer) {
@ -391,36 +395,22 @@ if ($action == 'confirm_generateinvoice') {
$generateinvoicemode = GETPOST('generateinvoicemode', 'string'); $generateinvoicemode = GETPOST('generateinvoicemode', 'string');
$invoiceToUse = GETPOST('invoiceid', 'int'); $invoiceToUse = GETPOST('invoiceid', 'int');
$prodDurationHours = 1.0; $prodDurationHoursBase = 1.0;
$product_data_cache = array();
if ($idprod > 0) { if ($idprod > 0) {
$tmpproduct->fetch($idprod); $tmpproduct->fetch($idprod);
if ($result<0) {
$error++;
setEventMessages($tmpproduct->error, $tmpproduct->errors, 'errors');
}
if (empty($tmpproduct->duration_value)) { $prodDurationHoursBase=$tmpproduct->getProductDurationHours();
if ($prodDurationHoursBase<0) {
$error++; $error++;
$langs->load("errors"); $langs->load("errors");
setEventMessages($langs->trans("ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice"), null, 'errors'); setEventMessages(null, $tmpproduct->errors, 'errors');
} }
if ($tmpproduct->duration_unit == 'i') {
$prodDurationHours = 1. / 60;
}
if ($tmpproduct->duration_unit == 'h') {
$prodDurationHours = 1.;
}
if ($tmpproduct->duration_unit == 'd') {
$prodDurationHours = 24.;
}
if ($tmpproduct->duration_unit == 'w') {
$prodDurationHours = 24. * 7;
}
if ($tmpproduct->duration_unit == 'm') {
$prodDurationHours = 24. * 30;
}
if ($tmpproduct->duration_unit == 'y') {
$prodDurationHours = 24. * 365;
}
$prodDurationHours *= $tmpproduct->duration_value;
$dataforprice = $tmpproduct->getSellPrice($mysoc, $projectstatic->thirdparty, 0); $dataforprice = $tmpproduct->getSellPrice($mysoc, $projectstatic->thirdparty, 0);
$pu_ht = empty($dataforprice['pu_ht']) ? 0 : $dataforprice['pu_ht']; $pu_ht = empty($dataforprice['pu_ht']) ? 0 : $dataforprice['pu_ht'];
@ -428,7 +418,7 @@ if ($action == 'confirm_generateinvoice') {
$localtax1 = $dataforprice['localtax1']; $localtax1 = $dataforprice['localtax1'];
$localtax2 = $dataforprice['localtax2']; $localtax2 = $dataforprice['localtax2'];
} else { } else {
$prodDurationHours = 1; $prodDurationHoursBase = 1;
$pu_ht = 0; $pu_ht = 0;
$txtva = get_default_tva($mysoc, $projectstatic->thirdparty); $txtva = get_default_tva($mysoc, $projectstatic->thirdparty);
@ -456,26 +446,70 @@ if ($action == 'confirm_generateinvoice') {
foreach ($toselect as $key => $value) { foreach ($toselect as $key => $value) {
// Get userid, timepent // Get userid, timepent
$object->fetchTimeSpent($value); $object->fetchTimeSpent($value);
$arrayoftasks[$object->timespent_fk_user]['timespent'] += $object->timespent_duration; $arrayoftasks[$object->timespent_fk_user][(int) $object->timespent_fk_product]['timespent'] += $object->timespent_duration;
$arrayoftasks[$object->timespent_fk_user]['totalvaluetodivideby3600'] += ($object->timespent_duration * $object->timespent_thm); $arrayoftasks[$object->timespent_fk_user][(int) $object->timespent_fk_product]['totalvaluetodivideby3600'] += ($object->timespent_duration * $object->timespent_thm);
} }
foreach ($arrayoftasks as $userid => $value) { foreach ($arrayoftasks as $userid => $data) {
$fuser->fetch($userid); $fuser->fetch($userid);
//$pu_ht = $value['timespent'] * $fuser->thm; //$pu_ht = $value['timespent'] * $fuser->thm;
$username = $fuser->getFullName($langs); $username = $fuser->getFullName($langs);
foreach ($data as $fk_product=>$timespent_data) {
// Define qty per hour // Define qty per hour
$qtyhour = $value['timespent'] / 3600; $qtyhour = $timespent_data['timespent'] / 3600;
$qtyhourtext = convertSecondToTime($value['timespent'], 'all', $conf->global->MAIN_DURATION_OF_WORKDAY); $qtyhourtext = convertSecondToTime($timespent_data['timespent'], 'all', $conf->global->MAIN_DURATION_OF_WORKDAY);
// If no unit price known // If no unit price known
if (empty($pu_ht)) { if (empty($pu_ht)) {
$pu_ht = price2num($value['totalvaluetodivideby3600'] / 3600, 'MU'); $pu_ht = price2num($timespent_data['totalvaluetodivideby3600'] / 3600, 'MU');
} }
// Add lines // Add lines
$lineid = $tmpinvoice->addline($langs->trans("TimeSpentForInvoice", $username).' : '.$qtyhourtext, $pu_ht, round($qtyhour / $prodDurationHours, 2), $txtva, $localtax1, $localtax2, ($idprod > 0 ? $idprod : 0)); $prodDurationHours = $prodDurationHoursBase;
$idprodline=$idprod;
$pu_htline = $pu_ht;
$txtvaline = $txtva;
$localtax1line = $localtax1;
$localtax2line = $localtax2;
if (!empty($fk_product) && $fk_product!==$idprod) {
if (!array_key_exists($fk_product, $product_data_cache)) {
$result = $tmpproduct->fetch($fk_product);
if ($result < 0) {
$error++;
setEventMessages($tmpproduct->error, $tmpproduct->errors, 'errors');
}
$prodDurationHours = $tmpproduct->getProductDurationHours();
if ($prodDurationHours < 0) {
$error++;
$langs->load("errors");
setEventMessages(null, $tmpproduct->errors, 'errors');
}
$dataforprice = $tmpproduct->getSellPrice($mysoc, $projectstatic->thirdparty, 0);
$pu_htline = empty($dataforprice['pu_ht']) ? 0 : $dataforprice['pu_ht'];
$txtvaline = $dataforprice['tva_tx'];
$localtax1line = $dataforprice['localtax1'];
$localtax2line = $dataforprice['localtax2'];
$product_data_cache[$fk_product] = array('duration'=>$prodDurationHours,'dataforprice'=>$dataforprice);
} else {
$prodDurationHours = $product_data_cache[$fk_product]['duration'];
$pu_htline = empty($product_data_cache[$fk_product]['dataforprice']['pu_ht']) ? 0 : $product_data_cache[$fk_product]['dataforprice']['pu_ht'];
$txtvaline = $product_data_cache[$fk_product]['dataforprice']['tva_tx'];
$localtax1line = $product_data_cache[$fk_product]['dataforprice']['localtax1'];
$localtax2line = $product_data_cache[$fk_product]['dataforprice']['localtax2'];
}
$idprodline=$fk_product;
}
// Add lines
$lineid = $tmpinvoice->addline($langs->trans("TimeSpentForInvoice", $username).' : '.$qtyhourtext, $pu_htline, round($qtyhour / $prodDurationHours, 2), $txtvaline, $localtax1line, $localtax2line, ($idprodline > 0 ? $idprodline : 0));
if ($lineid<0) {
$error++;
setEventMessages(null, $tmpinvoice->errors, 'errors');
}
// Update lineid into line of timespent // Update lineid into line of timespent
$sql = 'UPDATE '.MAIN_DB_PREFIX.'projet_task_time SET invoice_line_id = '.((int) $lineid).', invoice_id = '.((int) $tmpinvoice->id); $sql = 'UPDATE '.MAIN_DB_PREFIX.'projet_task_time SET invoice_line_id = '.((int) $lineid).', invoice_id = '.((int) $tmpinvoice->id);
@ -487,8 +521,10 @@ if ($action == 'confirm_generateinvoice') {
break; break;
} }
} }
}
} elseif ($generateinvoicemode == 'onelineperperiod') { // One line for each time spent line } elseif ($generateinvoicemode == 'onelineperperiod') { // One line for each time spent line
$arrayoftasks = array(); $arrayoftasks = array();
$withdetail=GETPOST('detail_time_duration', 'alpha'); $withdetail=GETPOST('detail_time_duration', 'alpha');
foreach ($toselect as $key => $value) { foreach ($toselect as $key => $value) {
// Get userid, timepent // Get userid, timepent
@ -518,6 +554,7 @@ if ($action == 'confirm_generateinvoice') {
$arrayoftasks[$object->timespent_id]['note'] .= ' - '.$langs->trans("Duration").': '.convertSecondToTime($object->timespent_duration, 'all', $conf->global->MAIN_DURATION_OF_WORKDAY); $arrayoftasks[$object->timespent_id]['note'] .= ' - '.$langs->trans("Duration").': '.convertSecondToTime($object->timespent_duration, 'all', $conf->global->MAIN_DURATION_OF_WORKDAY);
} }
$arrayoftasks[$object->timespent_id]['user'] = $object->timespent_fk_user; $arrayoftasks[$object->timespent_id]['user'] = $object->timespent_fk_user;
$arrayoftasks[$object->timespent_id]['fk_product'] = $object->timespent_fk_product;
} }
foreach ($arrayoftasks as $timespent_id => $value) { foreach ($arrayoftasks as $timespent_id => $value) {
@ -533,7 +570,49 @@ if ($action == 'confirm_generateinvoice') {
} }
// Add lines // Add lines
$lineid = $tmpinvoice->addline($value['note'], $pu_ht, round($qtyhour / $prodDurationHours, 2), $txtva, $localtax1, $localtax2, ($idprod > 0 ? $idprod : 0)); $prodDurationHours = $prodDurationHoursBase;
$idprodline=$idprod;
$pu_htline = $pu_ht;
$txtvaline = $txtva;
$localtax1line = $localtax1;
$localtax2line = $localtax2;
if (!empty($value['fk_product']) && $value['fk_product']!==$idprod) {
if (!array_key_exists($value['fk_product'], $product_data_cache)) {
$result = $tmpproduct->fetch($value['fk_product']);
if ($result < 0) {
$error++;
setEventMessages($tmpproduct->error, $tmpproduct->errors, 'errors');
}
$prodDurationHours = $tmpproduct->getProductDurationHours();
if ($prodDurationHours < 0) {
$error++;
$langs->load("errors");
setEventMessages(null, $tmpproduct->errors, 'errors');
}
$dataforprice = $tmpproduct->getSellPrice($mysoc, $projectstatic->thirdparty, 0);
$pu_htline = empty($dataforprice['pu_ht']) ? 0 : $dataforprice['pu_ht'];
$txtvaline = $dataforprice['tva_tx'];
$localtax1line = $dataforprice['localtax1'];
$localtax2line = $dataforprice['localtax2'];
$product_data_cache[$value['fk_product']] = array('duration'=>$prodDurationHours,'dataforprice'=>$dataforprice);
} else {
$prodDurationHours = $product_data_cache[$value['fk_product']]['duration'];
$pu_htline = empty($product_data_cache[$value['fk_product']]['dataforprice']['pu_ht']) ? 0 : $product_data_cache[$value['fk_product']]['dataforprice']['pu_ht'];
$txtvaline = $product_data_cache[$value['fk_product']]['dataforprice']['tva_tx'];
$localtax1line = $product_data_cache[$value['fk_product']]['dataforprice']['localtax1'];
$localtax2line = $product_data_cache[$value['fk_product']]['dataforprice']['localtax2'];
}
$idprodline=$value['fk_product'];
}
$lineid = $tmpinvoice->addline($value['note'], $pu_htline, round($qtyhour / $prodDurationHours, 2), $txtvaline, $localtax1line, $localtax2line, ($idprodline > 0 ? $idprodline : 0));
if ($lineid<0) {
$error++;
setEventMessages(null, $tmpinvoice->errors, 'errors');
}
//var_dump($lineid);exit; //var_dump($lineid);exit;
// Update lineid into line of timespent // Update lineid into line of timespent
@ -552,40 +631,82 @@ if ($action == 'confirm_generateinvoice') {
// Get userid, timepent // Get userid, timepent
$object->fetchTimeSpent($value); // Call method to get list of timespent for a timespent line id (We use the utiliy method found into Task object) $object->fetchTimeSpent($value); // Call method to get list of timespent for a timespent line id (We use the utiliy method found into Task object)
// $object->id is now the task id // $object->id is now the task id
$arrayoftasks[$object->id]['timespent'] += $object->timespent_duration; $arrayoftasks[$object->id][(int) $object->timespent_fk_product]['timespent'] += $object->timespent_duration;
$arrayoftasks[$object->id]['totalvaluetodivideby3600'] += ($object->timespent_duration * $object->timespent_thm); $arrayoftasks[$object->id][(int) $object->timespent_fk_product]['totalvaluetodivideby3600'] += ($object->timespent_duration * $object->timespent_thm);
} }
foreach ($arrayoftasks as $task_id => $value) { foreach ($arrayoftasks as $task_id => $data) {
$ftask = new Task($db); $ftask = new Task($db);
$ftask->fetch($task_id); $ftask->fetch($task_id);
// Define qty per hour
$qtyhour = $value['timespent'] / 3600;
$qtyhourtext = convertSecondToTime($value['timespent'], 'all', $conf->global->MAIN_DURATION_OF_WORKDAY);
if ($idprod > 0) { foreach ($data as $fk_product=>$timespent_data) {
$qtyhour = $timespent_data['timespent'] / 3600;
$qtyhourtext = convertSecondToTime($timespent_data['timespent'], 'all', $conf->global->MAIN_DURATION_OF_WORKDAY);
// Add lines
$prodDurationHours = $prodDurationHoursBase;
$idprodline=$idprod;
$pu_htline = $pu_ht;
$txtvaline = $txtva;
$localtax1line = $localtax1;
$localtax2line = $localtax2;
if (!empty($fk_product) && $fk_product!==$idprod) {
if (!array_key_exists($fk_product, $product_data_cache)) {
$result = $tmpproduct->fetch($fk_product);
if ($result < 0) {
$error++;
setEventMessages($tmpproduct->error, $tmpproduct->errors, 'errors');
}
$prodDurationHours = $tmpproduct->getProductDurationHours();
if ($prodDurationHours < 0) {
$error++;
$langs->load("errors");
setEventMessages(null, $tmpproduct->errors, 'errors');
}
$dataforprice = $tmpproduct->getSellPrice($mysoc, $projectstatic->thirdparty, 0);
$pu_htline = empty($dataforprice['pu_ht']) ? 0 : $dataforprice['pu_ht'];
$txtvaline = $dataforprice['tva_tx'];
$localtax1line = $dataforprice['localtax1'];
$localtax2line = $dataforprice['localtax2'];
$product_data_cache[$fk_product] = array('duration'=>$prodDurationHours,'dataforprice'=>$dataforprice);
} else {
$prodDurationHours = $product_data_cache[$fk_product]['duration'];
$pu_htline = empty($product_data_cache[$fk_product]['dataforprice']['pu_ht']) ? 0 : $product_data_cache[$fk_product]['dataforprice']['pu_ht'];
$txtvaline = $product_data_cache[$fk_product]['dataforprice']['tva_tx'];
$localtax1line = $product_data_cache[$fk_product]['dataforprice']['localtax1'];
$localtax2line = $product_data_cache[$fk_product]['dataforprice']['localtax2'];
}
$idprodline=$fk_product;
}
if ($idprodline > 0) {
// If a product is defined, we msut use the $prodDurationHours and $pu_ht of product (already set previously). // If a product is defined, we msut use the $prodDurationHours and $pu_ht of product (already set previously).
$pu_ht_for_task = $pu_ht; $pu_ht_for_task = $pu_htline;
// If we want to reuse the value of timespent (so use same price than cost price) // If we want to reuse the value of timespent (so use same price than cost price)
if (!empty($conf->global->PROJECT_TIME_SPENT_INTO_INVOICE_USE_VALUE)) { if (!empty($conf->global->PROJECT_TIME_SPENT_INTO_INVOICE_USE_VALUE)) {
$pu_ht_for_task = price2num($value['totalvaluetodivideby3600'] / $value['timespent'], 'MU') * $prodDurationHours; $pu_ht_for_task = price2num($timespent_data['totalvaluetodivideby3600'] / $timespent_data['timespent'], 'MU') * $prodDurationHours;
} }
$pa_ht = price2num($value['totalvaluetodivideby3600'] / $value['timespent'], 'MU') * $prodDurationHours; $pa_ht = price2num($timespent_data['totalvaluetodivideby3600'] / $timespent_data['timespent'], 'MU') * $prodDurationHours;
} else { } else {
// If not product used, we use the hour unit for duration and unit price. // If not product used, we use the hour unit for duration and unit price.
$pu_ht_for_task = 0; $pu_ht_for_task = 0;
// If we want to reuse the value of timespent (so use same price than cost price) // If we want to reuse the value of timespent (so use same price than cost price)
if (!empty($conf->global->PROJECT_TIME_SPENT_INTO_INVOICE_USE_VALUE)) { if (!empty($conf->global->PROJECT_TIME_SPENT_INTO_INVOICE_USE_VALUE)) {
$pu_ht_for_task = price2num($value['totalvaluetodivideby3600'] / $value['timespent'], 'MU'); $pu_ht_for_task = price2num($timespent_data['totalvaluetodivideby3600'] / $timespent_data['timespent'], 'MU');
} }
$pa_ht = price2num($value['totalvaluetodivideby3600'] / $value['timespent'], 'MU'); $pa_ht = price2num($timespent_data['totalvaluetodivideby3600'] / $timespent_data['timespent'], 'MU');
} }
// Add lines // Add lines
$date_start = ''; $date_start = '';
$date_end = ''; $date_end = '';
$lineName = $ftask->ref.' - '.$ftask->label; $lineName = $ftask->ref . ' - ' . $ftask->label;
$lineid = $tmpinvoice->addline($lineName, $pu_ht_for_task, price2num($qtyhour / $prodDurationHours, 'MS'), $txtva, $localtax1, $localtax2, ($idprod > 0 ? $idprod : 0), 0, $date_start, $date_end, 0, 0, '', 'HT', 0, 1, -1, 0, '', 0, 0, null, $pa_ht); $lineid = $tmpinvoice->addline($lineName, $pu_ht_for_task, price2num($qtyhour / $prodDurationHours, 'MS'), $txtvaline, $localtax1line, $localtax2line, ($idprodline > 0 ? $idprodline : 0), 0, $date_start, $date_end, 0, 0, '', 'HT', 0, 1, -1, 0, '', 0, 0, null, $pa_ht);
if ($lineid < 0) { if ($lineid < 0) {
$error++; $error++;
setEventMessages($tmpinvoice->error, $tmpinvoice->errors, 'errors'); setEventMessages($tmpinvoice->error, $tmpinvoice->errors, 'errors');
@ -594,8 +715,8 @@ if ($action == 'confirm_generateinvoice') {
if (!$error) { if (!$error) {
// Update lineid into line of timespent // Update lineid into line of timespent
$sql = 'UPDATE '.MAIN_DB_PREFIX.'projet_task_time SET invoice_line_id = '.((int) $lineid).', invoice_id = '.((int) $tmpinvoice->id); $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'projet_task_time SET invoice_line_id = ' . ((int) $lineid) . ', invoice_id = ' . ((int) $tmpinvoice->id);
$sql .= ' WHERE rowid IN ('.$db->sanitize(join(',', $toselect)).')'; $sql .= ' WHERE rowid IN (' . $db->sanitize(join(',', $toselect)) . ')';
$result = $db->query($sql); $result = $db->query($sql);
if (!$result) { if (!$result) {
$error++; $error++;
@ -606,6 +727,7 @@ if ($action == 'confirm_generateinvoice') {
} }
} }
} }
}
if (!$error) { if (!$error) {
$urltoinvoice = $tmpinvoice->getNomUrl(0); $urltoinvoice = $tmpinvoice->getNomUrl(0);
@ -1085,6 +1207,9 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
} }
$arrayfields['author'] = array('label'=>$langs->trans("By"), 'checked'=>1); $arrayfields['author'] = array('label'=>$langs->trans("By"), 'checked'=>1);
$arrayfields['t.note'] = array('label'=>$langs->trans("Note"), 'checked'=>1); $arrayfields['t.note'] = array('label'=>$langs->trans("Note"), 'checked'=>1);
if ($conf->service->enabled && $projectstatic->thirdparty->id > 0 && $projectstatic->usage_bill_time) {
$arrayfields['t.fk_product'] = array('label' => $langs->trans("Product"), 'checked' => 1);
}
$arrayfields['t.task_duration'] = array('label'=>$langs->trans("Duration"), 'checked'=>1); $arrayfields['t.task_duration'] = array('label'=>$langs->trans("Duration"), 'checked'=>1);
$arrayfields['value'] = array('label'=>$langs->trans("Value"), 'checked'=>1, 'enabled'=>(empty($conf->salaries->enabled) ? 0 : 1)); $arrayfields['value'] = array('label'=>$langs->trans("Value"), 'checked'=>1, 'enabled'=>(empty($conf->salaries->enabled) ? 0 : 1));
$arrayfields['valuebilled'] = array('label'=>$langs->trans("Billed"), 'checked'=>1, 'enabled'=>(((!empty($conf->global->PROJECT_HIDE_TASKS) || empty($conf->global->PROJECT_BILL_TIME_SPENT)) ? 0 : 1) && $projectstatic->usage_bill_time)); $arrayfields['valuebilled'] = array('label'=>$langs->trans("Billed"), 'checked'=>1, 'enabled'=>(((!empty($conf->global->PROJECT_HIDE_TASKS) || empty($conf->global->PROJECT_BILL_TIME_SPENT)) ? 0 : 1) && $projectstatic->usage_bill_time));
@ -1312,6 +1437,7 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
$tasks = array(); $tasks = array();
$sql = "SELECT t.rowid, t.fk_task, t.task_date, t.task_datehour, t.task_date_withhour, t.task_duration, t.fk_user, t.note, t.thm,"; $sql = "SELECT t.rowid, t.fk_task, t.task_date, t.task_datehour, t.task_date_withhour, t.task_duration, t.fk_user, t.note, t.thm,";
$sql .= " t.fk_product,";
$sql .= " pt.ref, pt.label, pt.fk_projet,"; $sql .= " pt.ref, pt.label, pt.fk_projet,";
$sql .= " u.lastname, u.firstname, u.login, u.photo, u.statut as user_status,"; $sql .= " u.lastname, u.firstname, u.login, u.photo, u.statut as user_status,";
$sql .= " il.fk_facture as invoice_id, inv.fk_statut,"; $sql .= " il.fk_facture as invoice_id, inv.fk_statut,";
@ -1324,9 +1450,10 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
$sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as t"; $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as t";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facturedet as il ON il.rowid = t.invoice_line_id"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facturedet as il ON il.rowid = t.invoice_line_id";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as inv ON inv.rowid = il.fk_facture"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as inv ON inv.rowid = il.fk_facture";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as prod ON prod.rowid = t.fk_product";
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."projet_task as pt ON pt.rowid = t.fk_task"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."projet_task as pt ON pt.rowid = t.fk_task";
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."user as u ON t.fk_user = u.rowid";
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."projet as p ON p.rowid = pt.fk_projet"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."projet as p ON p.rowid = pt.fk_projet";
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."user as u ON t.fk_user = u.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = p.fk_soc"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = p.fk_soc";
// Add table from hooks // Add table from hooks
@ -1363,6 +1490,9 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
if ($search_user > 0) { if ($search_user > 0) {
$sql .= natural_search('t.fk_user', $search_user, 2); $sql .= natural_search('t.fk_user', $search_user, 2);
} }
if (!empty($search_product_ref)) {
$sql .= natural_search('prod.ref', $search_product_ref);
}
if ($search_valuebilled == '1') { if ($search_valuebilled == '1') {
$sql .= ' AND t.invoice_id > 0'; $sql .= ' AND t.invoice_id > 0';
} }
@ -1461,6 +1591,10 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
print '<td>'.$langs->trans("ProgressDeclared").'</td>'; print '<td>'.$langs->trans("ProgressDeclared").'</td>';
if (empty($conf->global->PROJECT_HIDE_TASKS) && !empty($conf->global->PROJECT_BILL_TIME_SPENT)) { if (empty($conf->global->PROJECT_HIDE_TASKS) && !empty($conf->global->PROJECT_BILL_TIME_SPENT)) {
print '<td></td>'; print '<td></td>';
if ($conf->service->enabled && $projectstatic->thirdparty->id > 0 && $projectstatic->usage_bill_time) {
print '<td>'.$langs->trans("Product").'</td>';
}
} }
// Hook fields // Hook fields
$parameters = array('mode' => 'create'); $parameters = array('mode' => 'create');
@ -1536,6 +1670,12 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
if (empty($conf->global->PROJECT_HIDE_TASKS) && !empty($conf->global->PROJECT_BILL_TIME_SPENT)) { if (empty($conf->global->PROJECT_HIDE_TASKS) && !empty($conf->global->PROJECT_BILL_TIME_SPENT)) {
print '<td>'; print '<td>';
print '</td>'; print '</td>';
if ($conf->service->enabled && $projectstatic->thirdparty->id > 0 && $projectstatic->usage_bill_time) {
print '<td class="nowrap">';
print $form->select_produits('', 'fk_product', '1', 0, $projectstatic->thirdparty->price_level, 1, 2, '', 0, array(), $projectstatic->thirdparty->id, 'None', 0, 'maxwidth500');
print '</td>';
}
} }
// Fields from hook // Fields from hook
@ -1619,6 +1759,10 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
if (!empty($arrayfields['t.task_duration']['checked'])) { if (!empty($arrayfields['t.task_duration']['checked'])) {
print '<td class="liste_titre right"></td>'; print '<td class="liste_titre right"></td>';
} }
// Product
if (!empty($arrayfields['t.fk_product']['checked'])) {
print '<td class="liste_titre right"></td>';
}
// Value in main currency // Value in main currency
if (!empty($arrayfields['value']['checked'])) { if (!empty($arrayfields['value']['checked'])) {
print '<td class="liste_titre"></td>'; print '<td class="liste_titre"></td>';
@ -1670,6 +1814,10 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
if (!empty($arrayfields['t.task_duration']['checked'])) { if (!empty($arrayfields['t.task_duration']['checked'])) {
print_liste_field_titre($arrayfields['t.task_duration']['label'], $_SERVER['PHP_SELF'], 't.task_duration', '', $param, '', $sortfield, $sortorder, 'right '); print_liste_field_titre($arrayfields['t.task_duration']['label'], $_SERVER['PHP_SELF'], 't.task_duration', '', $param, '', $sortfield, $sortorder, 'right ');
} }
if (!empty($arrayfields['t.fk_product']['checked'])) {
print_liste_field_titre($arrayfields['t.fk_product']['label'], $_SERVER['PHP_SELF'], 't.fk_product', '', $param, '', $sortfield, $sortorder);
}
if (!empty($arrayfields['value']['checked'])) { if (!empty($arrayfields['value']['checked'])) {
print_liste_field_titre($arrayfields['value']['label'], $_SERVER['PHP_SELF'], '', '', $param, '', $sortfield, $sortorder, 'right '); print_liste_field_titre($arrayfields['value']['label'], $_SERVER['PHP_SELF'], '', '', $param, '', $sortfield, $sortorder, 'right ');
} }
@ -1860,6 +2008,23 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
$totalarray['totalduration'] += $task_time->task_duration; $totalarray['totalduration'] += $task_time->task_duration;
} }
//Product
if (!empty($arrayfields['t.fk_product']['checked'])) {
print '<td class="nowraponall">';
if ($action == 'editline' && $_GET['lineid'] == $task_time->rowid) {
$form->select_produits($task_time->fk_product, 'fk_product', '1', 0, $projectstatic->thirdparty->price_level, 1, 2, '', 0, array(), $projectstatic->thirdparty->id, 'None', 0, 'maxwidth500');
} elseif (!empty($task_time->fk_product)) {
$product = new Product($db);
$resultFetch = $product->fetch($task_time->fk_product);
if ($resultFetch < 0) {
setEventMessages($product->error, $product->errors, 'errors');
} else {
print $product->getNomUrl(1);
}
}
print '</td>';
}
// Value spent // Value spent
if (!empty($arrayfields['value']['checked'])) { if (!empty($arrayfields['value']['checked'])) {
$langs->load("salaries"); $langs->load("salaries");