Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW Implementing Billable tasks on projects using new attribute "billable" #30092

Merged
merged 14 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
vdegrandpre marked this conversation as resolved.
Show resolved Hide resolved
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;
vdegrandpre marked this conversation as resolved.
Show resolved Hide resolved

/**
* @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
Loading