diff --git a/htdocs/compta/facture/class/facture.class.php b/htdocs/compta/facture/class/facture.class.php index 74595267a7c..a69f7cd0629 100644 --- a/htdocs/compta/facture/class/facture.class.php +++ b/htdocs/compta/facture/class/facture.class.php @@ -3820,6 +3820,7 @@ class Facture extends CommonInvoice return -2; } } 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); return -3; } diff --git a/htdocs/core/class/html.formfile.class.php b/htdocs/core/class/html.formfile.class.php index 9ba4ccc7f19..69c349616f5 100644 --- a/htdocs/core/class/html.formfile.class.php +++ b/htdocs/core/class/html.formfile.class.php @@ -1291,6 +1291,7 @@ class FormFile $nboflines++; print ''."\n"; // Do we have entry into database ? + print ''."\n"; print '
| '.$langs->trans("Label").' | ||||||||||
| '.$langs->trans("Label").' | label).'" autofocus> | |||||||||
| '.$langs->trans("AddIn").' | ';
print $formecm->selectAllSections((GETPOST("catParent", 'alpha') ? GETPOST("catParent", 'alpha') : $ecmdir->fk_parent), 'catParent', $module);
diff --git a/htdocs/langs/en_US/projects.lang b/htdocs/langs/en_US/projects.lang
index b4bd6f75d9a..037ddd1c4e1 100644
--- a/htdocs/langs/en_US/projects.lang
+++ b/htdocs/langs/en_US/projects.lang
@@ -259,7 +259,7 @@ TimeSpentInvoiced=Time spent billed
TimeSpentForIntervention=Time spent
TimeSpentForInvoice=Time spent
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
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.
diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php
index b257c49a723..aec83c67b39 100644
--- a/htdocs/product/class/product.class.php
+++ b/htdocs/product/class/product.class.php
@@ -6159,6 +6159,43 @@ class Product extends CommonObject
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;
+ }
}
diff --git a/htdocs/product/inventory/list.php b/htdocs/product/inventory/list.php
index c4548b3aa66..553f4ed8baa 100644
--- a/htdocs/product/inventory/list.php
+++ b/htdocs/product/inventory/list.php
@@ -27,6 +27,9 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
require_once DOL_DOCUMENT_ROOT.'/product/inventory/class/inventory.class.php';
+if (!empty($conf->categorie->enabled)) {
+ require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
+}
// Load translation files required by the page
$langs->loadLangs(array("stocks", "other"));
@@ -88,6 +91,13 @@ foreach ($object->fields as $key => $val) {
$search[$key.'_dtend'] = dol_mktime(23, 59, 59, GETPOST('search_'.$key.'_dtendmonth', 'int'), GETPOST('search_'.$key.'_dtendday', 'int'), GETPOST('search_'.$key.'_dtendyear', 'int'));
}
}
+$searchCategoryProductOperator = 0;
+if (GETPOSTISSET('formfilteraction')) {
+ $searchCategoryProductOperator = GETPOST('search_category_product_operator', 'int');
+} elseif (!empty($conf->global->MAIN_SEARCH_CAT_OR_BY_DEFAULT)) {
+ $searchCategoryProductOperator = $conf->global->MAIN_SEARCH_CAT_OR_BY_DEFAULT;
+}
+$searchCategoryProductList = GETPOST('search_category_product_list', 'array');
// List of fields to search into when doing a "search in all"
$fieldstosearchall = array();
@@ -166,6 +176,7 @@ if (empty($reshook)) {
$search[$key.'_dtend'] = '';
}
}
+ $searchCategoryProductList = array();
$toselect = array();
$search_array_options = array();
}
@@ -259,6 +270,50 @@ foreach ($search as $key => $val) {
if ($search_all) {
$sql .= natural_search(array_keys($fieldstosearchall), $search_all);
}
+$searchCategoryProductSqlList = array();
+if ($searchCategoryProductOperator == 1) {
+ $existsCategoryProductList = array();
+ foreach ($searchCategoryProductList as $searchCategoryProduct) {
+ if (intval($searchCategoryProduct) == -2) {
+ $sqlCategoryProductNotExists = " NOT EXISTS (";
+ $sqlCategoryProductNotExists .= " SELECT cp.fk_product";
+ $sqlCategoryProductNotExists .= " FROM ".$db->prefix()."categorie_product AS cp";
+ $sqlCategoryProductNotExists .= " WHERE cp.fk_product = t.fk_product";
+ $sqlCategoryProductNotExists .= " )";
+ $searchCategoryProductSqlList[] = $sqlCategoryProductNotExists;
+ } elseif (intval($searchCategoryProduct) > 0) {
+ $existsCategoryProductList[] = $db->escape($searchCategoryProduct);
+ }
+ }
+ if (!empty($existsCategoryProductList)) {
+ $sqlCategoryProductExists = " EXISTS (";
+ $sqlCategoryProductExists .= " SELECT cp.fk_product";
+ $sqlCategoryProductExists .= " FROM ".$db->prefix()."categorie_product AS cp";
+ $sqlCategoryProductExists .= " WHERE cp.fk_product = t.fk_product";
+ $sqlCategoryProductExists .= " AND cp.fk_categorie IN (".$db->sanitize(implode(',', $existsCategoryProductList)).")";
+ $sqlCategoryProductExists .= " )";
+ $searchCategoryProductSqlList[] = $sqlCategoryProductExists;
+ }
+ if (!empty($searchCategoryProductSqlList)) {
+ $sql .= " AND (".implode(' OR ', $searchCategoryProductSqlList).")";
+ }
+} else {
+ foreach ($searchCategoryProductList as $searchCategoryProduct) {
+ if (intval($searchCategoryProduct) == -2) {
+ $sqlCategoryProductNotExists = " NOT EXISTS (";
+ $sqlCategoryProductNotExists .= " SELECT cp.fk_product";
+ $sqlCategoryProductNotExists .= " FROM ".$db->prefix()."categorie_product AS cp";
+ $sqlCategoryProductNotExists .= " WHERE cp.fk_product = t.fk_product";
+ $sqlCategoryProductNotExists .= " )";
+ $searchCategoryProductSqlList[] = $sqlCategoryProductNotExists;
+ } elseif (intval($searchCategoryProduct) > 0) {
+ $searchCategoryProductSqlList[] = "t.fk_product IN (SELECT fk_product FROM ".$db->prefix()."categorie_product WHERE fk_categorie = ".((int) $searchCategoryProduct).")";
+ }
+ }
+ if (!empty($searchCategoryProductSqlList)) {
+ $sql .= " AND (".implode(' AND ', $searchCategoryProductSqlList).")";
+ }
+}
//$sql.= dolSqlDateFilter("t.field", $search_xxxday, $search_xxxmonth, $search_xxxyear);
// Add where from extra fields
include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php';
@@ -348,6 +403,9 @@ foreach ($search as $key => $val) {
if ($optioncss != '') {
$param .= '&optioncss='.urlencode($optioncss);
}
+foreach ($searchCategoryProductList as $searchCategoryProduct) {
+ $param .= "&search_category_product_list[]=".urlencode($searchCategoryProduct);
+}
// Add $param from extra fields
include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php';
// Add $param from hooks
@@ -405,6 +463,19 @@ $moreforfilter = '';
$moreforfilter.= $langs->trans('MyFilter') . ': ';
$moreforfilter.= '';*/
+// Filter on categories
+if (!empty($conf->global->MAIN_SEARCH_CATEGORY_PRODUCT_ON_LISTS) && !empty($conf->categorie->enabled) && $user->rights->categorie->lire) {
+ $moreforfilter .= ' ';
+ $tmptitle = $langs->transnoentities('ProductsCategoriesShort');
+ $moreforfilter .= img_picto($tmptitle, 'category', 'class="pictofixedwidth"');
+ $categoriesProductArr = $form->select_all_categories(Categorie::TYPE_PRODUCT, '', '', 64, 0, 1);
+ $categoriesProductArr[-2] = '- '.$langs->trans('NotCategorized').' -';
+ $moreforfilter .= Form::multiselectarray('search_category_product_list', $categoriesProductArr, $searchCategoryProductList, 0, 0, 'minwidth300', 0, 0, '', 'category', $tmptitle);
+ $moreforfilter .= ' ';
+ $moreforfilter .= $form->textwithpicto('', $langs->trans('UseOrOperatorForCategories') . ' : ' . $tmptitle, 1, 'help', '', 0, 2, 'tooltip_cat_pro'); // Tooltip on click
+ $moreforfilter .= ' ';
+}
+
$parameters = array();
$reshook = $hookmanager->executeHooks('printFieldPreListTitle', $parameters, $object); // Note that $action and $object may have been modified by hook
if (empty($reshook)) {
diff --git a/htdocs/projet/class/task.class.php b/htdocs/projet/class/task.class.php
index ba31845592a..d9ad1111fc7 100644
--- a/htdocs/projet/class/task.class.php
+++ b/htdocs/projet/class/task.class.php
@@ -127,6 +127,7 @@ class Task extends CommonObjectLine
public $timespent_fk_user;
public $timespent_thm;
public $timespent_note;
+ public $timespent_fk_product;
public $comments = array();
@@ -1227,6 +1228,7 @@ class Task extends CommonObjectLine
$sql .= ", task_date_withhour";
$sql .= ", task_duration";
$sql .= ", fk_user";
+ $sql .= ", fk_product";
$sql .= ", note";
$sql .= ", datec";
$sql .= ") VALUES (";
@@ -1236,6 +1238,7 @@ class Task extends CommonObjectLine
$sql .= ", ".(empty($this->timespent_withhour) ? 0 : 1);
$sql .= ", ".((int) $this->timespent_duration);
$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 .= ", '".$this->db->idate($now)."'";
$sql .= ")";
@@ -1523,6 +1526,7 @@ class Task extends CommonObjectLine
$sql .= " t.task_date_withhour,";
$sql .= " t.task_duration,";
$sql .= " t.fk_user,";
+ $sql .= " t.fk_product,";
$sql .= " t.thm,";
$sql .= " t.note";
$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_duration = $obj->task_duration;
$this->timespent_fk_user = $obj->fk_user;
+ $this->timespent_fk_product = $obj->fk_product;
$this->timespent_thm = $obj->thm; // hourly rate
$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_duration = ".((int) $this->timespent_duration).",";
$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 .= " WHERE rowid = ".((int) $this->timespent_id);
diff --git a/htdocs/projet/tasks/time.php b/htdocs/projet/tasks/time.php
index f409b3cb547..0859a7d9910 100644
--- a/htdocs/projet/tasks/time.php
+++ b/htdocs/projet/tasks/time.php
@@ -73,6 +73,7 @@ $search_task_ref = GETPOST('search_task_ref', 'alpha');
$search_task_label = GETPOST('search_task_label', 'alpha');
$search_user = GETPOST('search_user', 'int');
$search_valuebilled = GETPOST('search_valuebilled', 'int');
+$search_product_ref = GETPOST('search_product_ref', 'alpha');
$search_company = GETPOST('$search_company', 'alpha');
$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_user = 0;
$search_valuebilled = '';
+ $search_product_ref = '';
$toselect = array();
$search_array_options = array();
$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_fk_user = GETPOST("userid", 'int');
+ $object->timespent_fk_product = GETPOST("fk_product", 'int');
$result = $object->addTimeSpent($user);
if ($result >= 0) {
setEventMessages($langs->trans("RecordSaved"), null, 'mesgs');
@@ -271,17 +274,17 @@ if (($action == 'updateline' || $action == 'updatesplitline') && !$cancel && $us
$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_product = GETPOST("fk_product", 'int');
$result = 0;
if (in_array($object->timespent_fk_user, $childids) || $user->rights->projet->all->creer) {
$result = $object->addTimeSpent($user);
- }
-
- if ($result >= 0) {
- setEventMessages($langs->trans("RecordSaved"), null, 'mesgs');
- } else {
- setEventMessages($langs->trans($object->error), null, 'errors');
- $error++;
+ if ($result >= 0) {
+ setEventMessages($langs->trans("RecordSaved"), null, 'mesgs');
+ } else {
+ setEventMessages($langs->trans($object->error), null, 'errors');
+ $error++;
+ }
}
} else {
$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_fk_user = GETPOST("userid_line", 'int');
+ $object->timespent_fk_product = GETPOST("fk_product", 'int');
$result = 0;
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');
$invoiceToUse = GETPOST('invoiceid', 'int');
- $prodDurationHours = 1.0;
+ $prodDurationHoursBase = 1.0;
+ $product_data_cache = array();
if ($idprod > 0) {
$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++;
$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);
$pu_ht = empty($dataforprice['pu_ht']) ? 0 : $dataforprice['pu_ht'];
@@ -428,7 +418,7 @@ if ($action == 'confirm_generateinvoice') {
$localtax1 = $dataforprice['localtax1'];
$localtax2 = $dataforprice['localtax2'];
} else {
- $prodDurationHours = 1;
+ $prodDurationHoursBase = 1;
$pu_ht = 0;
$txtva = get_default_tva($mysoc, $projectstatic->thirdparty);
@@ -456,39 +446,85 @@ if ($action == 'confirm_generateinvoice') {
foreach ($toselect as $key => $value) {
// Get userid, timepent
$object->fetchTimeSpent($value);
- $arrayoftasks[$object->timespent_fk_user]['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]['timespent'] += $object->timespent_duration;
+ $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);
//$pu_ht = $value['timespent'] * $fuser->thm;
$username = $fuser->getFullName($langs);
+ foreach ($data as $fk_product=>$timespent_data) {
+ // Define qty per hour
+ $qtyhour = $timespent_data['timespent'] / 3600;
+ $qtyhourtext = convertSecondToTime($timespent_data['timespent'], 'all', $conf->global->MAIN_DURATION_OF_WORKDAY);
- // Define qty per hour
- $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($timespent_data['totalvaluetodivideby3600'] / 3600, 'MU');
+ }
- // If no unit price known
- if (empty($pu_ht)) {
- $pu_ht = price2num($value['totalvaluetodivideby3600'] / 3600, 'MU');
- }
+ // Add lines
+ $prodDurationHours = $prodDurationHoursBase;
+ $idprodline=$idprod;
+ $pu_htline = $pu_ht;
+ $txtvaline = $txtva;
+ $localtax1line = $localtax1;
+ $localtax2line = $localtax2;
- // Add lines
- $lineid = $tmpinvoice->addline($langs->trans("TimeSpentForInvoice", $username).' : '.$qtyhourtext, $pu_ht, round($qtyhour / $prodDurationHours, 2), $txtva, $localtax1, $localtax2, ($idprod > 0 ? $idprod : 0));
+ 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');
+ }
- // 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)).') AND fk_user = '.((int) $userid);
- $result = $db->query($sql);
- if (!$result) {
- $error++;
- setEventMessages($db->lasterror(), null, 'errors');
- break;
+ $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
+ $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)).') AND fk_user = '.((int) $userid);
+ $result = $db->query($sql);
+ if (!$result) {
+ $error++;
+ setEventMessages($db->lasterror(), null, 'errors');
+ break;
+ }
}
}
} elseif ($generateinvoicemode == 'onelineperperiod') { // One line for each time spent line
$arrayoftasks = array();
+
$withdetail=GETPOST('detail_time_duration', 'alpha');
foreach ($toselect as $key => $value) {
// 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]['user'] = $object->timespent_fk_user;
+ $arrayoftasks[$object->timespent_id]['fk_product'] = $object->timespent_fk_product;
}
foreach ($arrayoftasks as $timespent_id => $value) {
@@ -533,7 +570,49 @@ if ($action == 'confirm_generateinvoice') {
}
// 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;
// Update lineid into line of timespent
@@ -552,56 +631,99 @@ if ($action == 'confirm_generateinvoice') {
// 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->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][(int) $object->timespent_fk_product]['timespent'] += $object->timespent_duration;
+ $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->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) {
- // 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;
+ 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;
}
- $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');
+
+
+ if ($idprodline > 0) {
+ // If a product is defined, we msut use the $prodDurationHours and $pu_ht of product (already set previously).
+ $pu_ht_for_task = $pu_htline;
+ // 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($timespent_data['totalvaluetodivideby3600'] / $timespent_data['timespent'], 'MU') * $prodDurationHours;
+ }
+ $pa_ht = price2num($timespent_data['totalvaluetodivideby3600'] / $timespent_data['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($timespent_data['totalvaluetodivideby3600'] / $timespent_data['timespent'], 'MU');
+ }
+ $pa_ht = price2num($timespent_data['totalvaluetodivideby3600'] / $timespent_data['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_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($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) {
+ // Add lines
+ $date_start = '';
+ $date_end = '';
+ $lineName = $ftask->ref . ' - ' . $ftask->label;
+ $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) {
$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;
+ }
+ }
}
}
}
@@ -1085,6 +1207,9 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
}
$arrayfields['author'] = array('label'=>$langs->trans("By"), '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['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));
@@ -1312,6 +1437,7 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
$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 .= " t.fk_product,";
$sql .= " pt.ref, pt.label, pt.fk_projet,";
$sql .= " u.lastname, u.firstname, u.login, u.photo, u.statut as user_status,";
$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 .= " 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."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."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."user as u ON t.fk_user = u.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = p.fk_soc";
// Add table from hooks
@@ -1363,6 +1490,9 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
if ($search_user > 0) {
$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') {
$sql .= ' AND t.invoice_id > 0';
}
@@ -1461,6 +1591,10 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser
print ' | '.$langs->trans("ProgressDeclared").' | '; if (empty($conf->global->PROJECT_HIDE_TASKS) && !empty($conf->global->PROJECT_BILL_TIME_SPENT)) { print ''; + + if ($conf->service->enabled && $projectstatic->thirdparty->id > 0 && $projectstatic->usage_bill_time) { + print ' | '.$langs->trans("Product").' | '; + } } // Hook fields $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)) { print ''; print ' | '; + + if ($conf->service->enabled && $projectstatic->thirdparty->id > 0 && $projectstatic->usage_bill_time) { + print ''; + print $form->select_produits('', 'fk_product', '1', 0, $projectstatic->thirdparty->price_level, 1, 2, '', 0, array(), $projectstatic->thirdparty->id, 'None', 0, 'maxwidth500'); + print ' | '; + } } // Fields from hook @@ -1619,6 +1759,10 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser if (!empty($arrayfields['t.task_duration']['checked'])) { print ''; } + // Product + if (!empty($arrayfields['t.fk_product']['checked'])) { + print ' | '; + } // Value in main currency if (!empty($arrayfields['value']['checked'])) { print ' | '; @@ -1670,6 +1814,10 @@ if (($id > 0 || !empty($ref)) || $projectidforalltimes > 0 || $allprojectforuser 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 '); } + 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'])) { 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; } + //Product + if (!empty($arrayfields['t.fk_product']['checked'])) { + print ' | '; + 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 ' | '; + } + // Value spent if (!empty($arrayfields['value']['checked'])) { $langs->load("salaries");