Calculate the time spent based on the timetable

This commit is contained in:
Frederic Guillot
2015-03-14 20:53:33 -04:00
parent 4700139a86
commit 253996901a
16 changed files with 397 additions and 68 deletions

View File

@@ -67,7 +67,6 @@ use Symfony\Component\EventDispatcher\Event;
* @property \Model\CommentHistory $commentHistory
* @property \Model\SubtaskHistory $subtaskHistory
* @property \Model\SubtaskTimeTracking $subtaskTimeTracking
* @property \Model\TimeTracking $timeTracking
* @property \Model\User $user
* @property \Model\UserSession $userSession
* @property \Model\Webhook $webhook

View File

@@ -38,7 +38,11 @@ class Config extends Base
{
if ($this->request->isPost()) {
$values = $this->request->getValues() + array('subtask_restriction' => 0, 'subtask_time_tracking' => 0);
$values = $this->request->getValues();
if ($redirect === 'board') {
$values += array('subtask_restriction' => 0, 'subtask_time_tracking' => 0);
}
if ($this->config->save($values)) {
$this->config->reload();

View File

@@ -43,7 +43,7 @@ use Pimple\Container;
* @property \Model\TaskLink $taskLink
* @property \Model\TaskPosition $taskPosition
* @property \Model\TaskValidator $taskValidator
* @property \Model\TimeTracking $timeTracking
* @property \Model\Timetable $timetable
* @property \Model\SubtaskTimeTracking $subtaskTimeTracking
* @property \Model\User $user
* @property \Model\UserSession $userSession

View File

@@ -12,6 +12,47 @@ use DateTime;
*/
class DateParser extends Base
{
/**
* Return true if the date is within the date range
*
* @access public
* @param DateTime $date
* @param DateTime $start
* @param DateTime $end
* @return boolean
*/
public function withinDateRange(DateTime $date, DateTime $start, DateTime $end)
{
return $date >= $start && $date <= $end;
}
/**
* Get the total number of hours between 2 datetime objects
* Minutes are rounded to the nearest quarter
*
* @access public
* @param DateTime $d1
* @param DateTime $d2
* @return float
*/
public function getHours(DateTime $d1, DateTime $d2)
{
$seconds = $this->getRoundedSeconds(abs($d1->getTimestamp() - $d2->getTimestamp()));
return round($seconds / 3600, 2);
}
/**
* Round the timestamp to the nearest quarter
*
* @access public
* @param integer $seconds Timestamp
* @return integer
*/
public function getRoundedSeconds($seconds)
{
return (int) round($seconds / (15 * 60)) * (15 * 60);
}
/**
* Return a timestamp if the given date format is correct otherwise return 0
*

View File

@@ -2,6 +2,8 @@
namespace Model;
use DateTime;
/**
* Subtask timesheet
*
@@ -33,6 +35,7 @@ class SubtaskTimeTracking extends Base
self::TABLE.'.subtask_id',
self::TABLE.'.end',
self::TABLE.'.start',
self::TABLE.'.time_spent',
Subtask::TABLE.'.task_id',
Subtask::TABLE.'.title AS subtask_title',
Task::TABLE.'.title AS task_title',
@@ -60,6 +63,7 @@ class SubtaskTimeTracking extends Base
self::TABLE.'.subtask_id',
self::TABLE.'.end',
self::TABLE.'.start',
self::TABLE.'.time_spent',
self::TABLE.'.user_id',
Subtask::TABLE.'.task_id',
Subtask::TABLE.'.title AS subtask_title',
@@ -89,6 +93,7 @@ class SubtaskTimeTracking extends Base
self::TABLE.'.subtask_id',
self::TABLE.'.end',
self::TABLE.'.start',
self::TABLE.'.time_spent',
self::TABLE.'.user_id',
Subtask::TABLE.'.task_id',
Subtask::TABLE.'.title AS subtask_title',
@@ -235,7 +240,11 @@ class SubtaskTimeTracking extends Base
*/
public function logEndTime($subtask_id, $user_id)
{
$this->updateSubtaskTimeSpent($subtask_id, $user_id);
$time_spent = $this->getTimeSpent($subtask_id, $user_id);
if ($time_spent > 0) {
$this->updateSubtaskTimeSpent($subtask_id, $time_spent);
}
return $this->db
->table(self::TABLE)
@@ -243,10 +252,59 @@ class SubtaskTimeTracking extends Base
->eq('user_id', $user_id)
->eq('end', 0)
->update(array(
'end' => time()
'end' => time(),
'time_spent' => $time_spent,
));
}
/**
* Calculate the time spent when the clock is stopped
*
* @access public
* @param integer $subtask_id
* @param integer $user_id
* @return float
*/
public function getTimeSpent($subtask_id, $user_id)
{
$start_time = $this->db
->table(self::TABLE)
->eq('subtask_id', $subtask_id)
->eq('user_id', $user_id)
->eq('end', 0)
->findOneColumn('start');
if ($start_time) {
$start = new DateTime;
$start->setTimestamp($start_time);
return $this->timetable->calculateEffectiveDuration($user_id, $start, new DateTime);
}
return 0;
}
/**
* Update subtask time spent
*
* @access public
* @param integer $subtask_id
* @param float $time_spent
* @return bool
*/
public function updateSubtaskTimeSpent($subtask_id, $time_spent)
{
$subtask = $this->subtask->getById($subtask_id);
// Fire the event subtask.update
return $this->subtask->update(array(
'id' => $subtask['id'],
'time_spent' => $subtask['time_spent'] + $time_spent,
'task_id' => $subtask['task_id'],
));
}
/**
* Update task time tracking based on subtasks time tracking
*
@@ -289,31 +347,4 @@ class SubtaskTimeTracking extends Base
)
->findOne();
}
/**
* Update subtask time spent based on the punch clock table
*
* @access public
* @param integer $subtask_id
* @param integer $user_id
* @return bool
*/
public function updateSubtaskTimeSpent($subtask_id, $user_id)
{
$start_time = $this->db
->table(self::TABLE)
->eq('subtask_id', $subtask_id)
->eq('user_id', $user_id)
->eq('end', 0)
->findOneColumn('start');
$subtask = $this->subtask->getById($subtask_id);
return $start_time &&
$this->subtask->update(array( // Fire the event subtask.update
'id' => $subtask['id'],
'time_spent' => $subtask['time_spent'] + round((time() - $start_time) / 3600, 1),
'task_id' => $subtask['task_id'],
));
}
}

View File

@@ -24,6 +24,94 @@ class Timetable extends Base
private $overtime;
private $timeoff;
/**
* Calculate effective worked hours by taking into consideration the timetable
*
* @access public
* @param integer $user_id
* @param \DateTime $start
* @param \DateTime $end
* @return float
*/
public function calculateEffectiveDuration($user_id, DateTime $start, DateTime $end)
{
$end_timetable = clone($end);
$end_timetable->setTime(23, 59);
$timetable = $this->calculate($user_id, $start, $end_timetable);
$found_start = false;
$hours = 0;
// The user has no timetable
if (empty($this->week)) {
return $this->dateParser->getHours($start, $end);
}
foreach ($timetable as $slot) {
$isStartSlot = $this->dateParser->withinDateRange($start, $slot[0], $slot[1]);
$isEndSlot = $this->dateParser->withinDateRange($end, $slot[0], $slot[1]);
// Start and end are within the same time slot
if ($isStartSlot && $isEndSlot) {
return $this->dateParser->getHours($start, $end);
}
// We found the start slot
if (! $found_start && $isStartSlot) {
$found_start = true;
$hours = $this->dateParser->getHours($start, $slot[1]);
}
else if ($found_start) {
// We found the end slot
if ($isEndSlot) {
$hours += $this->dateParser->getHours($slot[0], $end);
break;
}
else {
// Sum hours of the intermediate time slots
$hours += $this->dateParser->getHours($slot[0], $slot[1]);
}
}
}
// The start date was not found in regular hours so we get the nearest time slot
if (! empty($timetable) && ! $found_start) {
$slot = $this->findClosestTimeSlot($start, $timetable);
if ($start < $slot[0]) {
return $this->calculateEffectiveDuration($user_id, $slot[0], $end);
}
}
return $hours;
}
/**
* Find the nearest time slot
*
* @access public
* @param DateTime $date
* @param array $timetable
* @return array
*/
public function findClosestTimeSlot(DateTime $date, array $timetable)
{
$values = array();
foreach ($timetable as $slot) {
$t1 = abs($slot[0]->getTimestamp() - $date->getTimestamp());
$t2 = abs($slot[1]->getTimestamp() - $date->getTimestamp());
$values[] = min($t1, $t2);
}
asort($values);
return $timetable[key($values)];
}
/**
* Get the timetable for a user for a given date range
*

View File

@@ -6,7 +6,12 @@ use PDO;
use Core\Security;
use Model\Link;
const VERSION = 52;
const VERSION = 53;
function version_53($pdo)
{
$pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent FLOAT DEFAULT 0");
}
function version_52($pdo)
{

View File

@@ -6,7 +6,12 @@ use PDO;
use Core\Security;
use Model\Link;
const VERSION = 33;
const VERSION = 34;
function version_34($pdo)
{
$pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent REAL DEFAULT 0");
}
function version_33($pdo)
{

View File

@@ -6,7 +6,12 @@ use Core\Security;
use PDO;
use Model\Link;
const VERSION = 51;
const VERSION = 52;
function version_52($pdo)
{
$pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent REAL DEFAULT 0");
}
function version_51($pdo)
{

View File

@@ -55,7 +55,6 @@ class ClassProvider implements ServiceProviderInterface
'TimetableWeek',
'TimetableOff',
'TimetableExtra',
'TimeTracking',
'User',
'UserSession',
'Webhook',

View File

@@ -6,10 +6,11 @@
<?php else: ?>
<table class="table-fixed">
<tr>
<th class="column-20"><?= $subtask_paginator->order(t('User'), 'username') ?></th>
<th class="column-30"><?= $subtask_paginator->order(t('Subtask'), 'subtask_title') ?></th>
<th><?= $subtask_paginator->order(t('Start'), 'start') ?></th>
<th><?= $subtask_paginator->order(t('End'), 'end') ?></th>
<th class="column-15"><?= $subtask_paginator->order(t('User'), 'username') ?></th>
<th><?= $subtask_paginator->order(t('Subtask'), 'subtask_title') ?></th>
<th class="column-20"><?= $subtask_paginator->order(t('Start'), 'start') ?></th>
<th class="column-20"><?= $subtask_paginator->order(t('End'), 'end') ?></th>
<th class="column-10"><?= $subtask_paginator->order(t('Time spent'), 'time_spent') ?></th>
</tr>
<?php foreach ($subtask_paginator->getCollection() as $record): ?>
<tr>
@@ -17,6 +18,7 @@
<td><?= t($record['subtask_title']) ?></td>
<td><?= dt('%B %e, %Y at %k:%M %p', $record['start']) ?></td>
<td><?= dt('%B %e, %Y at %k:%M %p', $record['end']) ?></td>
<td><?= n($record['time_spent']).' '.t('hours') ?></td>
</tr>
<?php endforeach ?>
</table>

View File

@@ -8,10 +8,11 @@
<?php else: ?>
<table class="table-fixed">
<tr>
<th class="column-20"><?= $subtask_paginator->order(t('Task'), 'task_title') ?></th>
<th class="column-20"><?= $subtask_paginator->order(t('Subtask'), 'subtask_title') ?></th>
<th><?= $subtask_paginator->order(t('Start'), 'start') ?></th>
<th><?= $subtask_paginator->order(t('End'), 'end') ?></th>
<th class="column-25"><?= $subtask_paginator->order(t('Task'), 'task_title') ?></th>
<th class="column-25"><?= $subtask_paginator->order(t('Subtask'), 'subtask_title') ?></th>
<th class="column-20"><?= $subtask_paginator->order(t('Start'), 'start') ?></th>
<th class="column-20"><?= $subtask_paginator->order(t('End'), 'end') ?></th>
<th class="column-10"><?= $subtask_paginator->order(t('Time spent'), 'time_spent') ?></th>
</tr>
<?php foreach ($subtask_paginator->getCollection() as $record): ?>
<tr>
@@ -19,6 +20,7 @@
<td><?= $this->a($this->e($record['subtask_title']), 'task', 'show', array('project_id' => $record['project_id'], 'task_id' => $record['task_id'])) ?></td>
<td><?= dt('%B %e, %Y at %k:%M %p', $record['start']) ?></td>
<td><?= dt('%B %e, %Y at %k:%M %p', $record['end']) ?></td>
<td><?= n($record['time_spent']).' '.t('hours') ?></td>
</tr>
<?php endforeach ?>
</table>