From 202ee8340a4dafd6a17a066781bffdff729f0d7b Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 12 Jul 2021 23:01:34 +0200 Subject: [PATCH] FIX #18091 --- htdocs/langs/en_US/errors.lang | 1 + htdocs/product/card.php | 2 +- .../product/class/html.formproduct.class.php | 4 +- htdocs/projet/class/task.class.php | 24 +++++- htdocs/projet/tasks/time.php | 75 ++++++++++++++----- 5 files changed, 81 insertions(+), 25 deletions(-) diff --git a/htdocs/langs/en_US/errors.lang b/htdocs/langs/en_US/errors.lang index 3ed7441afa5..cb97af5c183 100644 --- a/htdocs/langs/en_US/errors.lang +++ b/htdocs/langs/en_US/errors.lang @@ -264,6 +264,7 @@ ErrorAnAmountWithoutTaxIsRequired=Error, amount is mandatory ErrorAPercentIsRequired=Error, please fill in the percentage correctly ErrorYouMustFirstSetupYourChartOfAccount=You must first setup your chart of account ErrorFailedToFindEmailTemplate=Failed to find template with code name %s +ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice=Duration not defined on service. No way to calculate the hourly price. # Warnings WarningParamUploadMaxFileSizeHigherThanPostMaxSize=Your PHP parameter upload_max_filesize (%s) is higher than PHP parameter post_max_size (%s). This is not a consistent setup. diff --git a/htdocs/product/card.php b/htdocs/product/card.php index 01a2d9acfdd..2472c764d7e 100644 --- a/htdocs/product/card.php +++ b/htdocs/product/card.php @@ -1234,7 +1234,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { if ($type == 1) { print ''.$langs->trans("Duration").''; print ''; - print $formproduct->selectMeasuringUnits("duration_unit", "time", GETPOST('duration_value', 'alpha'), 0, 1); + print $formproduct->selectMeasuringUnits("duration_unit", "time", (GETPOSTISSET('duration_value') ? GETPOSTISSET('duration_value', 'alpha') : 'h'), 0, 1); print ''; } diff --git a/htdocs/product/class/html.formproduct.class.php b/htdocs/product/class/html.formproduct.class.php index 5be93a9b9d1..555bc08d03e 100644 --- a/htdocs/product/class/html.formproduct.class.php +++ b/htdocs/product/class/html.formproduct.class.php @@ -414,7 +414,7 @@ class FormProduct dol_print_error($db); return -1; } else { - $return .= ''; if ($adddefault || $adddefault === '') { $return .= ''; } @@ -447,6 +447,8 @@ class FormProduct $return .= ''; } + $return .= ajax_combobox($name); + return $return; } diff --git a/htdocs/projet/class/task.class.php b/htdocs/projet/class/task.class.php index 5e17c92c562..02c045e2ce1 100644 --- a/htdocs/projet/class/task.class.php +++ b/htdocs/projet/class/task.class.php @@ -116,6 +116,7 @@ class Task extends CommonObject public $timespent_datehour; // More accurate start date (same than timespent_date but includes hours, minutes and seconds) public $timespent_withhour; // 1 = we entered also start hours for timesheet line public $timespent_fk_user; + public $timespent_thm; public $timespent_note; public $comments = array(); @@ -1212,6 +1213,7 @@ class Task extends CommonObject $ret = -2; } + // Update hourly rate of this time spent entry $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task_time"; $sql .= " SET thm = (SELECT thm FROM ".MAIN_DB_PREFIX."user WHERE rowid = ".((int) $this->timespent_fk_user).")"; // set average hour rate of user $sql .= " WHERE rowid = ".((int) $tasktime_id); @@ -1425,6 +1427,7 @@ class Task extends CommonObject $sql .= " ptt.task_duration,"; $sql .= " ptt.fk_user,"; $sql .= " ptt.note,"; + $sql .= " ptt.thm,"; $sql .= " pt.rowid as task_id,"; $sql .= " pt.ref as task_ref,"; $sql .= " pt.label as task_label,"; @@ -1435,7 +1438,7 @@ class Task extends CommonObject $sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as ptt, ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."projet as p"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid"; $sql .= " WHERE ptt.fk_task = pt.rowid AND pt.fk_projet = p.rowid"; - $sql .= " AND ptt.fk_user = ".$userobj->id; + $sql .= " AND ptt.fk_user = ".((int) $userobj->id); $sql .= " AND pt.entity IN (".getEntity('project').")"; if ($morewherefilter) { $sql .= $morewherefilter; @@ -1471,6 +1474,7 @@ class Task extends CommonObject $newobj->timespent_withhour = $obj->task_date_withhour; $newobj->timespent_duration = $obj->task_duration; $newobj->timespent_fk_user = $obj->fk_user; + $newobj->timespent_thm = $obj->thm; // hourly rate $newobj->timespent_note = $obj->note; $arrayres[] = $newobj; @@ -1552,10 +1556,12 @@ class Task extends CommonObject } if ($ret == 1 && ($this->timespent_old_duration != $this->timespent_duration)) { - $newDuration = $this->timespent_duration - $this->timespent_old_duration; - + // Recalculate amount of time spent for task and update denormalized field $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task"; $sql .= " SET duration_effective = (SELECT SUM(task_duration) FROM ".MAIN_DB_PREFIX."projet_task_time as ptt where ptt.fk_task = ".((int) $this->id).")"; + if (isset($this->progress)) { + $sql .= ", progress = ".((float) $this->progress); // Do not overwrite value if not provided + } $sql .= " WHERE rowid = ".((int) $this->id); dol_syslog(get_class($this)."::updateTimeSpent", LOG_DEBUG); @@ -1564,6 +1570,18 @@ class Task extends CommonObject $this->db->rollback(); $ret = -2; } + + // Update hourly rate of this time spent entry, but only if it was not set initialy + $sql = "UPDATE ".MAIN_DB_PREFIX."projet_task_time"; + $sql .= " SET thm = (SELECT thm FROM ".MAIN_DB_PREFIX."user WHERE rowid = ".((int) $this->timespent_fk_user).")"; // set average hour rate of user + $sql .= " WHERE (thm IS NULL OR thm = 0) AND rowid = ".((int) $this->timespent_id); + + dol_syslog(get_class($this)."::addTimeSpent", LOG_DEBUG); + if (!$this->db->query($sql)) { + $this->error = $this->db->lasterror(); + $ret = -2; + } + } if ($ret >= 0) { diff --git a/htdocs/projet/tasks/time.php b/htdocs/projet/tasks/time.php index bbe9671faf3..f04b53c3d1e 100644 --- a/htdocs/projet/tasks/time.php +++ b/htdocs/projet/tasks/time.php @@ -261,10 +261,10 @@ if (($action == 'updateline' || $action == 'updatesplitline') && !$cancel && $us $object->timespent_duration = GETPOSTINT("new_durationhour") * 60 * 60; // We store duration in seconds $object->timespent_duration += (GETPOSTINT("new_durationmin") ? GETPOSTINT('new_durationmin') : 0) * 60; // We store duration in seconds if (GETPOST("timelinehour") != '' && GETPOST("timelinehour") >= 0) { // If hour was entered - $object->timespent_date = dol_mktime(GETPOST("timelinehour"), GETPOST("timelinemin"), 0, GETPOST("timelinemonth"), GETPOST("timelineday"), GETPOST("timelineyear")); + $object->timespent_date = dol_mktime(GETPOST("timelinehour", 'int'), GETPOST("timelinemin", 'int'), 0, GETPOST("timelinemonth", 'int'), GETPOST("timelineday", 'int'), GETPOST("timelineyear", 'int')); $object->timespent_withhour = 1; } else { - $object->timespent_date = dol_mktime(12, 0, 0, GETPOST("timelinemonth"), GETPOST("timelineday"), GETPOST("timelineyear")); + $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'); @@ -353,6 +353,13 @@ if ($action == 'confirm_generateinvoice') { $prodDurationHours = 1.0; if ($idprod > 0) { $tmpproduct->fetch($idprod); + + if (empty($tmpproduct->duration_value)) { + $error++; + $langs->load("errors"); + setEventMessages($langs->trans("ErrorDurationForServiceNotDefinedCantCalculateHourlyPrice"), null, 'errors'); + } + if ($tmpproduct->duration_unit == 'i') { $prodDurationHours = 1. / 60; } @@ -380,6 +387,8 @@ if ($action == 'confirm_generateinvoice') { $localtax1 = $dataforprice['localtax1']; $localtax2 = $dataforprice['localtax2']; } else { + $prodDurationHours = 1; + $pu_ht = 0; $txtva = get_default_tva($mysoc, $projectstatic->thirdparty); $localtax1 = get_default_localtax($mysoc, $projectstatic->thirdparty, 1); @@ -482,14 +491,14 @@ if ($action == 'confirm_generateinvoice') { break; } } - } elseif ($generateinvoicemode == 'onelinepertask') { + } elseif ($generateinvoicemode == 'onelinepertask') { // One line for each different task $arrayoftasks = array(); foreach ($toselect as $key => $value) { // Get userid, timepent - $object->fetchTimeSpent($value); - // $object->id is the task id + $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 $arrayoftasks[$object->id]['timespent'] += $object->timespent_duration; - $arrayoftasks[$object->id]['totalvaluetodivideby3600'] += $object->timespent_duration * $object->timespent_thm; + $arrayoftasks[$object->id]['totalvaluetodivideby3600'] += ($object->timespent_duration * $object->timespent_thm); } foreach ($arrayoftasks as $task_id => $value) { @@ -499,24 +508,46 @@ if ($action == 'confirm_generateinvoice') { $qtyhour = $value['timespent'] / 3600; $qtyhourtext = convertSecondToTime($value['timespent'], 'all', $conf->global->MAIN_DURATION_OF_WORKDAY); - // If no unit price known - if (empty($pu_ht)) { - $pu_ht = price2num($value['totalvaluetodivideby3600'] / 3600, 'MU'); + if ($idprod > 0) { + // If a product is defined, we msut use the $prodDurationHours and $pu_ht of product (already set previously). + $pu_ht_for_task = $pu_ht; + // 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)) { + $pu_ht_for_task = price2num($value['totalvaluetodivideby3600'] / $value['timespent'], 'MU') * $prodDurationHours; + } + $pa_ht = price2num($value['totalvaluetodivideby3600'] / $value['timespent'], 'MU') * $prodDurationHours; + } else { + // If not product used, we use the hour unit for duration and unit price. + $pu_ht_for_task = 0; + // 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)) { + $pu_ht_for_task = price2num($value['totalvaluetodivideby3600'] / $value['timespent'], 'MU'); + } + $pa_ht = price2num($value['totalvaluetodivideby3600'] / $value['timespent'], 'MU'); } // Add lines + $date_start = ''; + $date_end = ''; $lineName = $ftask->ref.' - '.$ftask->label; - $lineid = $tmpinvoice->addline($lineName, $pu_ht, round($qtyhour / $prodDurationHours, 2), $txtva, $localtax1, $localtax2, ($idprod > 0 ? $idprod : 0)); - - // 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 .= ' WHERE rowid IN ('.$db->sanitize(join(',', $toselect)).')'; - $result = $db->query($sql); - if (!$result) { + $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); + if ($lineid < 0) { $error++; - setEventMessages($db->lasterror(), null, 'errors'); + setEventMessages($tmpinvoice->error, $tmpinvoice->errors, 'errors'); break; } + + if (!$error) { + // 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 .= ' WHERE rowid IN ('.$db->sanitize(join(',', $toselect)).')'; + $result = $db->query($sql); + if (!$result) { + $error++; + setEventMessages($db->lasterror(), null, 'errors'); + break; + } + } } } } @@ -960,7 +991,7 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0) { print ''; if ($projectstatic->thirdparty->id > 0) { - print ''; + print '
'; print ''; print ''; - $date1 = $db->jdate($task_time->task_date); $date2 = $db->jdate($task_time->task_datehour); + print ''; + // Date if (!empty($arrayfields['t.task_date']['checked'])) { print ''; if (!$i) { $totalarray['nbfield']++;
'; print $langs->trans('DateInvoice'); @@ -1352,11 +1383,11 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0) { break; } - print '
'; @@ -1480,9 +1511,13 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0) { // Value spent if (!empty($arrayfields['value']['checked'])) { + $langs->load("salaries"); + print ''; $value = price2num($task_time->thm * $task_time->task_duration / 3600, 'MT', 1); + print 'thm).'">'; print price($value, 1, $langs, 1, -1, -1, $conf->currency); + print ''; print '