浏览代码

FIX #18091

Laurent Destailleur 4 年之前
父节点
当前提交
202ee8340a

+ 1 - 0
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.

+ 1 - 1
htdocs/product/card.php

@@ -1234,7 +1234,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 		if ($type == 1) {
 			print '<tr><td>'.$langs->trans("Duration").'</td><td>';
 			print '<input name="duration_value" size="4" value="'.GETPOST('duration_value', 'int').'">';
-			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 '</td></tr>';
 		}
 

+ 3 - 1
htdocs/product/class/html.formproduct.class.php

@@ -414,7 +414,7 @@ class FormProduct
 			dol_print_error($db);
 			return -1;
 		} else {
-			$return .= '<select class="flat'.($morecss ? ' '.$morecss : '').'" name="'.$name.'">';
+			$return .= '<select class="flat'.($morecss ? ' '.$morecss : '').'" name="'.$name.'" id="'.$name.'">';
 			if ($adddefault || $adddefault === '') {
 				$return .= '<option value="0">'.($adddefault ? $langs->trans("Default") : '').'</option>';
 			}
@@ -447,6 +447,8 @@ class FormProduct
 			$return .= '</select>';
 		}
 
+		$return .= ajax_combobox($name);
+
 		return $return;
 	}
 

+ 21 - 3
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) {

+ 55 - 20
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 '<input type="hidden" name="massaction" value="confirm_createbills">';
 
 			if ($projectstatic->thirdparty->id > 0) {
-				print '<table class="noborder" width="100%" >';
+				print '<table class="noborder centerpercent">';
 				print '<tr>';
 				print '<td class="titlefield">';
 				print $langs->trans('DateInvoice');
@@ -1352,11 +1383,11 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0) {
 				break;
 			}
 
-			print '<tr class="oddeven">';
-
 			$date1 = $db->jdate($task_time->task_date);
 			$date2 = $db->jdate($task_time->task_datehour);
 
+			print '<tr class="oddeven">';
+
 			// Date
 			if (!empty($arrayfields['t.task_date']['checked'])) {
 				print '<td class="nowrap">';
@@ -1480,9 +1511,13 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0) {
 
 			// Value spent
 			if (!empty($arrayfields['value']['checked'])) {
+				$langs->load("salaries");
+
 				print '<td class="nowraponall right">';
 				$value = price2num($task_time->thm * $task_time->task_duration / 3600, 'MT', 1);
+				print '<span class="amount" title="'.$langs->trans("THM").': '.price($task_time->thm).'">';
 				print price($value, 1, $langs, 1, -1, -1, $conf->currency);
+				print '</span>';
 				print '</td>';
 				if (!$i) {
 					$totalarray['nbfield']++;