diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/Controller/Base.php | 1 | ||||
-rw-r--r-- | app/Controller/Config.php | 6 | ||||
-rw-r--r-- | app/Model/Base.php | 2 | ||||
-rw-r--r-- | app/Model/DateParser.php | 41 | ||||
-rw-r--r-- | app/Model/SubtaskTimeTracking.php | 89 | ||||
-rw-r--r-- | app/Model/Timetable.php | 88 | ||||
-rw-r--r-- | app/Schema/Mysql.php | 7 | ||||
-rw-r--r-- | app/Schema/Postgres.php | 7 | ||||
-rw-r--r-- | app/Schema/Sqlite.php | 7 | ||||
-rw-r--r-- | app/ServiceProvider/ClassProvider.php | 1 | ||||
-rw-r--r-- | app/Template/task/time_tracking.php | 10 | ||||
-rw-r--r-- | app/Template/user/timesheet.php | 10 |
12 files changed, 225 insertions, 44 deletions
diff --git a/app/Controller/Base.php b/app/Controller/Base.php index ec202c06..a4e94343 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -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 diff --git a/app/Controller/Config.php b/app/Controller/Config.php index 01c7ad53..bee897be 100644 --- a/app/Controller/Config.php +++ b/app/Controller/Config.php @@ -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(); diff --git a/app/Model/Base.php b/app/Model/Base.php index 8a90e286..0217aae3 100644 --- a/app/Model/Base.php +++ b/app/Model/Base.php @@ -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 diff --git a/app/Model/DateParser.php b/app/Model/DateParser.php index 8a4d3edd..a0d10a36 100644 --- a/app/Model/DateParser.php +++ b/app/Model/DateParser.php @@ -13,6 +13,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 * * @access public diff --git a/app/Model/SubtaskTimeTracking.php b/app/Model/SubtaskTimeTracking.php index 8b197c46..ab0d7c54 100644 --- a/app/Model/SubtaskTimeTracking.php +++ b/app/Model/SubtaskTimeTracking.php @@ -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,11 +252,60 @@ 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 * * @access public @@ -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'], - )); - } } diff --git a/app/Model/Timetable.php b/app/Model/Timetable.php index eb37cefd..205c2c20 100644 --- a/app/Model/Timetable.php +++ b/app/Model/Timetable.php @@ -25,6 +25,94 @@ class Timetable extends Base 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 * * @access public diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 03868748..b4db10a4 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -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) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index 124aec76..331205fa 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -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) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index 818ed78d..551e186e 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -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) { diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 6f597a5c..fc71ebf9 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -55,7 +55,6 @@ class ClassProvider implements ServiceProviderInterface 'TimetableWeek', 'TimetableOff', 'TimetableExtra', - 'TimeTracking', 'User', 'UserSession', 'Webhook', diff --git a/app/Template/task/time_tracking.php b/app/Template/task/time_tracking.php index 1dea0f0b..55d33e5e 100644 --- a/app/Template/task/time_tracking.php +++ b/app/Template/task/time_tracking.php @@ -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> diff --git a/app/Template/user/timesheet.php b/app/Template/user/timesheet.php index 4f052006..3ae84df0 100644 --- a/app/Template/user/timesheet.php +++ b/app/Template/user/timesheet.php @@ -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> |