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

Feature/display billable hours #347

Open
wants to merge 22 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
693adc1
Displaying billable and non-billable hours above timelog list
timl07 Apr 23, 2024
4cccf8c
Displaying billable hours above timelog list
timl07 Apr 24, 2024
8ee0b9a
Displaying billable hours above timelog list
timl07 Apr 24, 2024
40b80ea
Writing methods to get time for a task by time type and whether billable
timl07 Apr 24, 2024
ae05565
Moving functions to TaskService
timl07 Apr 24, 2024
41f02f0
Moving functions to TaskService
timl07 Apr 24, 2024
07f9d43
Getting instance of TaskService instead of TaskTime
timl07 Apr 24, 2024
5d295e9
Make sure that billable doesn't show if there aren't time types
timl07 May 1, 2024
dff97d7
fix: add timelog.utils.ts change for later CRM test of this PR's func…
dragonflyfree May 3, 2024
c8513c1
fix: cut off seconds in billable/unbillable dropdown
dragonflyfree May 5, 2024
c6a4e07
fix: moving billable/unbillable seconds removal into getTotalTimeByBi…
dragonflyfree May 5, 2024
c34d52d
fix: timelog tests typo when adding Admin to task group
dragonflyfree May 5, 2024
f74184a
Merge branch 'develop' into fix/display_billable_hours
dragonflyfree May 5, 2024
6761754
fix: undo timelog test typo change because it breaks CI (???)
dragonflyfree May 5, 2024
134163a
Merge branch 'develop' into fix/display_billable_hours
dragonflyfree May 9, 2024
027940b
Edit treatment of config variable, so that no assumptions about it ar…
timl07 Aug 2, 2024
42fbda1
Compacting code in listtimelog.php
timl07 Aug 3, 2024
3429c97
Cleanup so file isn't shown in diff
timl07 Aug 6, 2024
18e88c3
Add hook to timelog.hooks
timl07 Aug 29, 2024
5aaeb92
Call hook in listtimelog
timl07 Aug 29, 2024
8546622
Merge branch 'feature/display_billable_hours' into fix/display_billab…
timl07 Sep 8, 2024
17dfae8
Merge pull request #346 from 2pisoftware/fix/display_billable_hours
timl07 Sep 8, 2024
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
42 changes: 42 additions & 0 deletions system/modules/task/models/TaskService.php
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be possible to have getTotalTimeByTimeType return a map of timetypes to total minutes, as opposed to formatted hour/minute strings? this would make the maths of adding things up a lot easier in getTotalTimeByBillable, make things a bit more readable?

Original file line number Diff line number Diff line change
Expand Up @@ -1116,4 +1116,46 @@ public function navList(): array
new MenuLinkStruct("Task Groups", "task-group/viewtaskgrouptypes"),
];
}

public function getTotalTimeByTimeType($task_id) {
$all_tasks = TaskService::getInstance($this->w)->getTasks();
foreach ($all_tasks as $task) {
if ($task->id == $task_id) {
$current_task = $task;
}
}
$all_timelogs_for_task = TimelogService::getInstance($this->w)->getTimelogsForObject($current_task);

$config_var = Config::get('task.TaskType_' . $current_task->task_type);
if (!array_key_exists('time-types', $config_var)) {
return;
}
$timelog_types = $config_var['time-types'];

$time_totals_for_time_types = [];
foreach ($timelog_types as $timelog_type) {
$total_time_for_type = 0;
foreach ($all_timelogs_for_task as $timelog) {
if ($timelog->time_type == $timelog_type) {
$total_time_for_type += $timelog->getDuration();
}
$total_time_fmtd = floor($total_time_for_type / 3600) . gmdate(":i:s", $total_time_for_type) . ' ';
}
$time_totals_for_time_types[$timelog_type] = $total_time_fmtd;
}
return $time_totals_for_time_types;
}

public function getTotalTimeByBillable($task_id) {
$time_totals = $this->getTotalTimeByTimeType($task_id);
$time_totals_in_seconds = [];
foreach ($time_totals as $time_total) {
$time_totals_in_seconds[] = strtotime("1970-01-01 $time_total UTC");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any other way to do this? DateTime perhaps? https://www.php.net/manual/en/class.datetime.php

}
$billable_time_in_seconds = array_sum(array_merge(array_slice($time_totals_in_seconds, 0, 3), [$time_totals_in_seconds[4]]));
$billable_time = floor($billable_time_in_seconds / 3600) . gmdate(":i:s", $billable_time_in_seconds);
$nonbillable_time = floor($time_totals_in_seconds[3] / 3600) . gmdate(":i:s", $time_totals_in_seconds[3]);
return ['Billable' => substr($billable_time, 0, -3), 'Non-Billable' => substr($nonbillable_time, 0, -3)];
}

}
1 change: 0 additions & 1 deletion system/modules/task/models/TaskTime.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,4 @@ public function getComment() {
public function getTask() {
return $this->getObject("Task", $this->task_id);
}

}
13 changes: 12 additions & 1 deletion system/modules/timelog/partials/actions/listtimelog.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,21 @@ function listtimelog(\Web $w, $params) {
return $carry += $timelog->getDuration();
});
}

$all_tasks = \TaskService::getInstance($w)->getTasks();
foreach ($all_tasks as $task) {
if ($task->id == $params['object_id']) {
$current_task = $task;
}
}
$task_types_with_time_types = get_task_types_with_time_types($w);
if (in_array($current_task->task_type, $task_types_with_time_types) && sizeof($timelogs) > 0) {
$w->ctx("billable_hours", \TaskService::getInstance($w)->getTotalTimeByBillable($params['object_id']));
}

$w->ctx("total", !empty($total) ? $total : 0);
$w->ctx("class", $params['object_class']);
$w->ctx("id", $params['object_id']);
$w->ctx("redirect", !empty($params['redirect']) ? $params['redirect'] : "");
$w->ctx("timelogs", $timelogs);
}
}
29 changes: 26 additions & 3 deletions system/modules/timelog/partials/templates/listtimelog.tpl.php
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move all the style="..."s and the <style> block into SCSS, for consistency?

Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
<?php echo Html::box("/timelog/edit?class={$class}&id={$id}" . (!empty($redirect) ? "&redirect=$redirect" : ''), "Add new timelog", true); ?>
<h4 style="display: inline; padding: 0px 5px;" class="right">
<?php echo TaskService::getInstance($w)->getFormatPeriod($total); ?>
</h4>
<?php if (!empty($billable_hours)) : ?>
<details style="user-select: none;" class="right">
<summary style="display: flex; cursor: pointer; justify-content: right;">
<h4 class="right" style="margin-bottom: 5px;">
<?php echo TaskService::getInstance($w)->getFormatPeriod($total); ?>
</h4>
<span class="icon" style="padding: 6px;">&dtrif;</span>
</summary>
<style>
details[open] summary span {
transform: rotate(180deg) translate(0px, 5px)
}
</style>
<p style="font-size: 11px; text-align: right; margin-bottom: 10px;">
<?php
if ($billable_hours) {
echo 'Billable: ' . $billable_hours['Billable'] . '<br>Non-Billable: ' . $billable_hours['Non-Billable'];
}
?>
</p>
</details>
<?php else: ?>
<h4 class="right" style="margin-bottom: 5px">
<?php echo TaskService::getInstance($w)->getFormatPeriod($total); ?>
</h4>
<?php endif; ?>

<?php if (!empty($timelogs)) : ?>
<table class='tablesorter small-12'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class TimelogHelper {
await expect(page.getByText(timelog)).toBeVisible();
}

static async createTimelog(page: Page, isMobile: boolean, timelog: string, taskName: string, taskID: string, date: DateTime, start_time: string, end_time: string, check_duplicate: boolean = false)
static async createTimelog(page: Page, isMobile: boolean, timelog: string, taskName: string, taskID: string, date: DateTime, start_time: string, end_time: string, check_duplicate: boolean = false, time_type?: string)
{
if(page.url() != HOST + "/task/edit/" + taskID + "#details") {
if(page.url() != HOST + "/task/tasklist")
Expand Down Expand Up @@ -58,6 +58,9 @@ export class TimelogHelper {
await page.locator("#time_start").fill(start_time);
await page.locator("#time_end").fill(end_time);
await page.getByLabel("Description", {exact: true}).fill(timelog);
if (time_type) {
await page.getByRole("combobox", {name: 'Task time' }).selectOption({ value: time_type });
}
await page.locator("#timelog_edit_form").getByRole("button", { name: "Save" }).click();

// if(await page.$("#saved_record_id") != null)
Expand Down
29 changes: 29 additions & 0 deletions system/modules/timelog/timelog.hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,32 @@ function timelog_core_dbobject_after_delete($w, $obj)
}
}
}

// find the task types which have time types
function get_task_types_with_time_types($w)
{
$taskgroup_types = TaskService::getInstance($w)->getAllTaskGroupTypes();

$task_types = [];
foreach ($taskgroup_types as $taskgroup_type) {
$config_var = Config::get("task." . $taskgroup_type[1]);
if (!$config_var || !array_key_exists('tasktypes', $config_var)) {
continue;
}

$task_types_for_taskgroup_type = $config_var['tasktypes'];
foreach (array_keys($task_types_for_taskgroup_type) as $task_type_for_taskgroup_type) {
$task_types[] = 'TaskType_' . $task_type_for_taskgroup_type;
}
}

$task_types_with_time_types = [];
foreach ($task_types as $task_type) {
$config_var = Config::get("task." . $task_type);
if ($config_var && array_key_exists('time-types', $config_var)) {
$task_types_with_time_types[] = substr($task_type, 9);
}
}

return $task_types_with_time_types;
}
Loading