Skip to content

Commit

Permalink
NEW Implementing Billable tasks on projects using new attribute "bill…
Browse files Browse the repository at this point in the history
…able" (#30092)

* Implementing Billable task function on projects using new attribute in #30014

* added billable to Task->initAsSpecimen()

* default billable to 1 in initAsSpecimen

* set billable to 1 in ProjectTest::testTaskCreate

* Moving attribut usage in usage_bill_time condition of project

* PPDoc

* fusion

* pre-commit + typo

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
  • Loading branch information
vdegrandpre and eldy authored Jul 3, 2024
1 parent 3ddd6cc commit b0cd32b
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 12 deletions.
15 changes: 14 additions & 1 deletion htdocs/core/lib/project.lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* Copyright (C) 2018-2024 Frédéric France <frederic.france@netlogic.fr>
* Copyright (C) 2022 Charlene Benke <charlene@patas-monkey.com>
* Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024 Vincent de Grandpré <vincent@de-grandpre.quebec>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -691,6 +692,7 @@ function projectLinesa(&$inc, $parent, &$lines, &$level, $var, $showproject, &$t
$taskstatic->planned_workload = $lines[$i]->planned_workload;
$taskstatic->duration_effective = $lines[$i]->duration_effective;
$taskstatic->budget_amount = $lines[$i]->budget_amount;
$taskstatic->billable = $lines[$i]->billable;

// Action column
if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
Expand Down Expand Up @@ -932,6 +934,17 @@ function projectLinesa(&$inc, $parent, &$lines, &$level, $var, $showproject, &$t
print '</td>';
}

// Billable
if (count($arrayfields) > 0 && !empty($arrayfields['t.billable']['checked'])) {
print '<td class="center">';
if ($lines[$i]->billable) {
print '<span>'.$langs->trans('Yes').'</span>';
} else {
print '<span>'.$langs->trans('No').'</span>';
}
print '</td>';
}

// Extra fields
$extrafieldsobjectkey = $taskstatic->table_element;
$extrafieldsobjectprefix = 'efpt.';
Expand Down
2 changes: 1 addition & 1 deletion htdocs/core/modules/modProjet.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ public function __construct($db)
$keyforaliasextra = 'extra';
include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php';
// Add fields for tasks
$this->export_fields_array[$r] = array_merge($this->export_fields_array[$r], array('pt.rowid'=>'TaskId', 'pt.ref'=>'RefTask', 'pt.label'=>'LabelTask', 'pt.dateo'=>"TaskDateStart", 'pt.datee'=>"TaskDateEnd", 'pt.duration_effective'=>"DurationEffective", 'pt.planned_workload'=>"PlannedWorkload", 'pt.progress'=>"Progress", 'pt.description'=>"TaskDescription"));
$this->export_fields_array[$r] = array_merge($this->export_fields_array[$r], array('pt.rowid'=>'TaskId', 'pt.ref'=>'RefTask', 'pt.label'=>'LabelTask', 'pt.dateo'=>"TaskDateStart", 'pt.datee'=>"TaskDateEnd", 'pt.duration_effective'=>"DurationEffective", 'pt.planned_workload'=>"PlannedWorkload", 'pt.progress'=>"Progress", 'pt.description'=>"TaskDescription", 'pt.billable'=>"Billable"));
$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('pt.rowid'=>'projecttask', 'pt.ref'=>'projecttask', 'pt.label'=>'projecttask', 'pt.dateo'=>"projecttask", 'pt.datee'=>"projecttask", 'pt.duration_effective'=>"projecttask", 'pt.planned_workload'=>"projecttask", 'pt.progress'=>"projecttask", 'pt.description'=>"projecttask"));
// Add extra fields for task
$keyforselect = 'projet_task';
Expand Down
1 change: 1 addition & 0 deletions htdocs/langs/de_DE/projects.lang
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,4 @@ NewLeadbyWeb=Ihre Nachricht bzw. Anfrage wurde erfasst. Wir werden uns so bald w
NewLeadForm=Neues Kontaktformular
LeadFromPublicForm=Online-Interessent per öffentlichem Formular
ExportAccountingReportButtonLabel=Bericht erhalten
Billable = Verrechenbar
1 change: 1 addition & 0 deletions htdocs/langs/en_US/projects.lang
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,4 @@ MergeTasks=Merge tasks
TaskMergeSuccess=Tasks have been merged
ErrorTaskIdIsMandatory=Error: Task id is mandatory
ErrorsTaskMerge=An error occurred while merging tasks
Billable = Billable
1 change: 1 addition & 0 deletions htdocs/langs/es_ES/projects.lang
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,4 @@ NewLeadbyWeb=Su mensaje o solicitud ha sido grabada. Le responderemos o contacta
NewLeadForm=Nuevo formulario de contacto
LeadFromPublicForm=Cliente potencial en línea desde un formulario público
ExportAccountingReportButtonLabel=Obtener informe
Billable = Billable
1 change: 1 addition & 0 deletions htdocs/langs/fr_FR/projects.lang
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,4 @@ MergeTasks=Fusionner tâches
TaskMergeSuccess=Les tâches ont été fusionnées
ErrorTaskIdIsMandatory=Erreur : l'ID de tâche est obligatoire
ErrorsTaskMerge=Une erreur s'est produite lors de la fusion des tâches
Billable = Facturable
1 change: 1 addition & 0 deletions htdocs/langs/it_IT/projects.lang
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,4 @@ NewLeadbyWeb=Il tuo messaggio o richiesta è stato registrato. Ti risponderemo o
NewLeadForm=Nuovo modulo di contatto
LeadFromPublicForm=Lead online da modulo pubblico
ExportAccountingReportButtonLabel=Get report
Billable = Fatturabile
25 changes: 20 additions & 5 deletions htdocs/projet/class/task.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* Copyright (C) 2020 Juanjo Menent <jmenent@2byte.es>
* Copyright (C) 2022 Charlene Benke <charlene@patas-monkey.com>
* Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024 Vincent de Grandpré <vincent@de-grandpre.quebec>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -270,6 +271,12 @@ class Task extends CommonObjectLine
*/
public $task_parent_position;

/**
* Status indicate whether the task is billable (time is meant to be added to invoice) '1' or not '0'
* @var int billable
*/
public $billable = 1;

/**
* @var float budget_amount
*/
Expand Down Expand Up @@ -362,6 +369,7 @@ public function create($user, $notrigger = 0)
$sql .= ", progress";
$sql .= ", budget_amount";
$sql .= ", priority";
$sql .= ", billable";
$sql .= ") VALUES (";
$sql .= (!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
$sql .= ", ".((int) $this->fk_project);
Expand All @@ -379,6 +387,7 @@ public function create($user, $notrigger = 0)
$sql .= ", ".(($this->progress != '' && $this->progress >= 0) ? ((int) $this->progress) : 'null');
$sql .= ", ".(($this->budget_amount != '' && $this->budget_amount >= 0) ? ((int) $this->budget_amount) : 'null');
$sql .= ", ".(($this->priority != '' && $this->priority >= 0) ? (int) $this->priority : 'null');
$sql .= ", ".((int) $this->billable);
$sql .= ")";

$this->db->begin();
Expand Down Expand Up @@ -456,7 +465,8 @@ public function fetch($id, $ref = '', $loadparentdata = 0)
$sql .= " t.priority,";
$sql .= " t.note_private,";
$sql .= " t.note_public,";
$sql .= " t.rang";
$sql .= " t.rang,";
$sql .= " t.billable";
if (!empty($loadparentdata)) {
$sql .= ", t2.ref as task_parent_ref";
$sql .= ", t2.rang as task_parent_position";
Expand Down Expand Up @@ -508,6 +518,7 @@ public function fetch($id, $ref = '', $loadparentdata = 0)
$this->task_parent_ref = $obj->task_parent_ref;
$this->task_parent_position = $obj->task_parent_position;
}
$this->billable = $obj->billable;

// Retrieve all extrafield
$this->fetch_optionals();
Expand Down Expand Up @@ -595,7 +606,8 @@ public function update($user = null, $notrigger = 0)
$sql .= " progress=".(($this->progress != '' && $this->progress >= 0) ? $this->progress : 'null').",";
$sql .= " budget_amount=".(($this->budget_amount != '' && $this->budget_amount >= 0) ? $this->budget_amount : 'null').",";
$sql .= " rang=".((!empty($this->rang)) ? ((int) $this->rang) : "0").",";
$sql .= " priority=".((!empty($this->priority)) ? ((int) $this->priority) : "0");
$sql .= " priority=".((!empty($this->priority)) ? ((int) $this->priority) : "0").",";
$sql .= " billable=".((int) $this->billable);
$sql .= " WHERE rowid=".((int) $this->id);

$this->db->begin();
Expand Down Expand Up @@ -1020,6 +1032,7 @@ public function initAsSpecimen()
$this->priority = 0;
$this->note_private = 'This is a specimen private note';
$this->note_public = 'This is a specimen public note';
$this->billable = 1;

return 1;
}
Expand Down Expand Up @@ -1063,7 +1076,7 @@ public function getTasksArray($usert = null, $userp = null, $projectid = 0, $soc
$sql .= " p.rowid as projectid, p.ref, p.title as plabel, p.public, p.fk_statut as projectstatus, p.usage_bill_time,";
$sql .= " t.rowid as taskid, t.ref as taskref, t.label, t.description, t.fk_task_parent, t.duration_effective, t.progress, t.fk_statut as status,";
$sql .= " t.dateo as date_start, t.datee as date_end, t.planned_workload, t.rang, t.priority,";
$sql .= " t.budget_amount,";
$sql .= " t.budget_amount, t.billable,";
$sql .= " t.note_public, t.note_private,";
$sql .= " s.rowid as thirdparty_id, s.nom as thirdparty_name, s.email as thirdparty_email,";
$sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount as project_budget_amount";
Expand Down Expand Up @@ -1178,7 +1191,7 @@ public function getTasksArray($usert = null, $userp = null, $projectid = 0, $soc
$sql .= " t.datec, t.dateo, t.datee, t.tms,";
$sql .= " t.rowid, t.ref, t.label, t.description, t.fk_task_parent, t.duration_effective, t.progress, t.fk_statut,";
$sql .= " t.dateo, t.datee, t.planned_workload, t.rang, t.priority,";
$sql .= " t.budget_amount,";
$sql .= " t.budget_amount, t.billable,";
$sql .= " t.note_public, t.note_private,";
$sql .= " s.rowid, s.nom, s.email,";
$sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount";
Expand Down Expand Up @@ -1273,6 +1286,8 @@ public function getTasksArray($usert = null, $userp = null, $projectid = 0, $soc
$tasks[$i]->thirdparty_name = $obj->thirdparty_name;
$tasks[$i]->thirdparty_email = $obj->thirdparty_email;

$tasks[$i]->billable = $obj->billable;

if ($loadextras) {
if (!empty($extrafields->attributes['projet']['label'])) {
foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
Expand Down
26 changes: 26 additions & 0 deletions htdocs/projet/tasks.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
$search_progresscalc = GETPOST('search_progresscalc');
$search_progressdeclare = GETPOST('search_progressdeclare');
$search_task_budget_amount = GETPOST('search_task_budget_amount');
$search_task_billable = GETPOST('search_task_billable');

$search_date_start_startmonth = GETPOSTINT('search_date_start_startmonth');
$search_date_start_startyear = GETPOSTINT('search_date_start_startyear');
Expand Down Expand Up @@ -148,6 +149,7 @@

$progress = GETPOSTINT('progress');
$budget_amount = GETPOSTFLOAT('budget_amount');
$billable = (GETPOST('billable', 'aZ') == 'yes'? 1 : 0);
$label = GETPOST('label', 'alpha');
$description = GETPOST('description', 'restricthtml');
$planned_workloadhour = (GETPOSTISSET('planned_workloadhour') ? GETPOSTINT('planned_workloadhour') : '');
Expand Down Expand Up @@ -176,6 +178,7 @@
if ($object->usage_bill_time) {
$arrayfields['t.tobill'] = array('label' => $langs->trans("TimeToBill"), 'checked' => 0, 'position' => 11);
$arrayfields['t.billed'] = array('label' => $langs->trans("TimeBilled"), 'checked' => 0, 'position' => 12);
$arrayfields['t.billable'] = array('label' => $langs->trans("Billable"), 'checked' => 1, 'position' => 13);
}

// Extra fields
Expand Down Expand Up @@ -234,6 +237,7 @@
$search_progresscalc = '';
$search_progressdeclare = '';
$search_task_budget_amount = '';
$search_task_billable = '';
$toselect = array();
$search_array_options = array();
$search_date_start_startmonth = "";
Expand Down Expand Up @@ -315,6 +319,9 @@
if ($search_task_budget_amount) {
$morewherefilterarray[] = natural_search('t.budget_amount', $search_task_budget_amount, 1, 1);
}
if ($search_task_billable) {
$morewherefilterarray[] = " t.billable = ".($search_task_billable == "yes" ? 1 : 0);
}
//var_dump($morewherefilterarray);

$morewherefilter = '';
Expand Down Expand Up @@ -364,6 +371,7 @@
$task->date_end = $date_end;
$task->progress = $progress;
$task->budget_amount = $budget_amount;
$task->billable = $billable;

// Fill array 'array_options' with data from add form
$ret = $extrafields->setOptionalsFromPost(null, $task);
Expand Down Expand Up @@ -557,6 +565,9 @@
if ($search_task_budget_amount) {
$param .= '&search_task_budget_amount='.urlencode($search_task_budget_amount);
}
if ($search_task_billable) {
$param .= '&search_task_billable='.urlencode($search_task_billable);
}
if ($optioncss != '') {
$param .= '&optioncss='.urlencode($optioncss);
}
Expand Down Expand Up @@ -796,6 +807,11 @@
}
print '</td></tr>';

// Billable
print '<tr><td>'.$langs->trans("Billable").'</td><td>';
print $form->selectyesno('billable');
print '</td></tr>';

// Date start task
print '<tr><td>'.$langs->trans("DateStart").'</td><td>';
print img_picto('', 'action', 'class="pictofixedwidth"');
Expand Down Expand Up @@ -1054,6 +1070,12 @@
print '</td>';
}

if (!empty($arrayfields['t.billable']['checked'])) {
print '<td class="liste_titre center">';
print $form->selectyesno('search_task_billable', $search_task_billable, 0, false, 1);
print '</td>';
}

include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_input.tpl.php';

print '<td class="liste_titre maxwidthsearch">&nbsp;</td>';
Expand Down Expand Up @@ -1126,6 +1148,10 @@
if (!empty($arrayfields['c.assigned']['checked'])) {
print_liste_field_titre($arrayfields['c.assigned']['label'], $_SERVER["PHP_SELF"], "", '', $param, '', $sortfield, $sortorder, 'center ', '');
}

if (!empty($arrayfields['t.billable']['checked'])) {
print_liste_field_titre($arrayfields['t.billable']['label'], $_SERVER["PHP_SELF"], "", '', $param, '', $sortfield, $sortorder, 'center ', '');
}
// Extra fields
$disablesortlink = 1;
include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_title.tpl.php';
Expand Down
14 changes: 13 additions & 1 deletion htdocs/projet/tasks/task.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
* Copyright (C) 2006-2017 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2010-2012 Regis Houssin <regis.houssin@inodbox.com>
* Copyright (C) 2018 Frédéric France <frederic.france@netlogic.fr>
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024 Vincent de Grandpré <vincent@de-grandpre.quebec>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -119,6 +120,7 @@
$object->date_end = dol_mktime(GETPOSTINT('date_endhour'), GETPOSTINT('date_endmin'), 0, GETPOSTINT('date_endmonth'), GETPOSTINT('date_endday'), GETPOSTINT('date_endyear'));
$object->progress = price2num(GETPOST('progress', 'alphanohtml'));
$object->budget_amount = GETPOSTFLOAT('budget_amount');
$object->billable = (GETPOST('billable', 'aZ') == 'yes' ? 1 : 0);

// Fill array 'array_options' with data from add form
$ret = $extrafields->setOptionalsFromPost(null, $object, '@GETPOSTISSET');
Expand Down Expand Up @@ -509,6 +511,11 @@
print $formother->select_percent($object->progress, 'progress', 0, 5, 0, 100, 1);
print '</td></tr>';

// Billable
print '<tr><td>'.$langs->trans("Billable").'</td><td>';
print $form->selectyesno('billable', $object->billable);
print '</td></tr>';

// Description

print '<tr><td class="tdtop">'.$langs->trans("Description").'</td>';
Expand Down Expand Up @@ -682,6 +689,11 @@
}
print '</td></tr>';

// Billable
print '<tr><td>'.$langs->trans("Billable").'</td><td>';
print '<span>'.($object->billable ? $langs->trans('Yes') : $langs->trans('No')).'</span>';
print '</td></tr>';

// Other attributes
$cols = 3;
$parameters = array('socid' => $socid);
Expand Down
26 changes: 22 additions & 4 deletions htdocs/projet/tasks/time.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
* Copyright (C) 2018 Frédéric France <frederic.france@netlogic.fr>
* Copyright (C) 2019-2021 Christophe Battarel <christophe@altairis.fr>
* Copyright (C) 2023 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024 Vincent de Grandpré <vincent@de-grandpre.quebec>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -1573,7 +1574,8 @@ function setDetailVisibility() {
$sql .= " u.lastname, u.firstname, u.login, u.photo, u.gender, u.statut as user_status,";
$sql .= " il.fk_facture as invoice_id, inv.fk_statut,";
$sql .= " p.fk_soc,s.name_alias,";
$sql .= " t.invoice_line_id";
$sql .= " t.invoice_line_id,";
$sql .= " pt.billable";
// Add fields from hooks
$parameters = array();
$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object); // Note that $action and $object may have been modified by hook
Expand Down Expand Up @@ -2411,6 +2413,7 @@ function setDetailVisibility() {
}

// Invoiced
$invoiced = false;
if (!empty($arrayfields['valuebilled']['checked'])) {
print '<td class="center">'; // invoice_id and invoice_line_id
if (!getDolGlobalString('PROJECT_HIDE_TASKS') && getDolGlobalString('PROJECT_BILL_TIME_SPENT')) {
Expand All @@ -2432,8 +2435,13 @@ function setDetailVisibility() {
}
}
}
$invoiced = true;
} else {
print $langs->trans("No");
if ( intval($task_time->billable) == 1) {
print $langs->trans("No");
} else {
print $langs->trans("Disabled");
}
}
} else {
print '<span class="opacitymedium">' . $langs->trans("NA") . '</span>';
Expand Down Expand Up @@ -2485,7 +2493,17 @@ function setDetailVisibility() {
$selected = 1;
}
print '&nbsp;';
print '<input id="cb' . $task_time->rowid . '" class="flat checkforselect marginleftonly" type="checkbox" name="toselect[]" value="' . $task_time->rowid . '"' . ($selected ? ' checked="checked"' : '') . '>';
// Disable select if task not billable or already invoiced
$disabled = (intval($task_time->billable) !=1 || $invoiced);
$ctrl = '<input '.($disabled?'disabled':'').' id="cb' . $task_time->rowid . '" class="flat checkforselect marginleftonly" type="checkbox" name="toselect[]" value="' . $task_time->rowid . '"' . ($selected ? ' checked="checked"' : '') . '>';
if ($disabled) {
// If disabled, a dbl-click very close outside the control
// will re-enable it, so that user is not blocked if needed.
print '<span id="cbsp'. $task_time->rowid . '">'.$ctrl.'</span>';
print '<script>$("#cbsp' . $task_time->rowid . '").dblclick(()=>{ $("#cb' . $task_time->rowid . '").removeAttr("disabled") })</script>';
} else {
print $ctrl;
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions test/phpunit/ProjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ public function testTaskCreate($idproject)
$localobject = new Task($db);
$localobject->initAsSpecimen();
$localobject->fk_project = $idproject;
$localobject->billable = 1;
$result = $localobject->create($user);

$this->assertLessThan($result, 0);
Expand Down

0 comments on commit b0cd32b

Please sign in to comment.