diff options
author | Frederic Guillot <fred@kanboard.net> | 2015-03-14 20:53:33 -0400 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2015-03-14 20:53:33 -0400 |
commit | 253996901a10918e3207d46839cdfdc90d200e72 (patch) | |
tree | 5e359471d8e7de963277c37c6ff9611181505975 | |
parent | 4700139a86d1ef44eabe43edb5a4abff9c645811 (diff) |
Calculate the time spent based on the timetable
-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 | ||||
-rw-r--r-- | composer.lock | 20 | ||||
-rw-r--r-- | tests/units/DateParserTest.php | 28 | ||||
-rw-r--r-- | tests/units/SubtaskTimeTrackingTest.php | 25 | ||||
-rw-r--r-- | tests/units/TimetableTest.php | 123 |
16 files changed, 397 insertions, 68 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> diff --git a/composer.lock b/composer.lock index ef61d8cc..ebaa06fc 100644 --- a/composer.lock +++ b/composer.lock @@ -88,12 +88,12 @@ "source": { "type": "git", "url": "https://github.com/fguillot/picoDb.git", - "reference": "3bc7a6ccdaaa675bc90610f7fe8c1dd6044d2a9e" + "reference": "da0380575afdfd35a1ce1fa8dc36f366ef577172" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fguillot/picoDb/zipball/3bc7a6ccdaaa675bc90610f7fe8c1dd6044d2a9e", - "reference": "3bc7a6ccdaaa675bc90610f7fe8c1dd6044d2a9e", + "url": "https://api.github.com/repos/fguillot/picoDb/zipball/da0380575afdfd35a1ce1fa8dc36f366ef577172", + "reference": "da0380575afdfd35a1ce1fa8dc36f366ef577172", "shasum": "" }, "require": { @@ -117,7 +117,7 @@ ], "description": "Minimalist database query builder", "homepage": "https://github.com/fguillot/picoDb", - "time": "2015-03-06 02:33:25" + "time": "2015-03-14 23:30:27" }, { "name": "fguillot/simple-validator", @@ -341,16 +341,16 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "c5f963e7f9d6f6438fda4f22d5cc2db296ec621a" + "reference": "31454f258f10329ae7c48763eb898a75c39e0a9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/c5f963e7f9d6f6438fda4f22d5cc2db296ec621a", - "reference": "c5f963e7f9d6f6438fda4f22d5cc2db296ec621a", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/31454f258f10329ae7c48763eb898a75c39e0a9f", + "reference": "31454f258f10329ae7c48763eb898a75c39e0a9f", "shasum": "" }, "require": { @@ -362,7 +362,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3-dev" + "dev-master": "5.4-dev" } }, "autoload": { @@ -389,7 +389,7 @@ "mail", "mailer" ], - "time": "2014-12-05 14:17:14" + "time": "2015-03-14 06:06:39" }, { "name": "symfony/console", diff --git a/tests/units/DateParserTest.php b/tests/units/DateParserTest.php index 5828fc48..9403063b 100644 --- a/tests/units/DateParserTest.php +++ b/tests/units/DateParserTest.php @@ -6,6 +6,34 @@ use Model\DateParser; class DateParserTest extends Base { + public function testDateRange() + { + $d = new DateParser($this->container); + + $this->assertTrue($d->withinDateRange(new DateTime('2015-03-14 15:30:00'), new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 16:00:00'))); + $this->assertFalse($d->withinDateRange(new DateTime('2015-03-14 15:30:00'), new DateTime('2015-03-14 16:00:00'), new DateTime('2015-03-14 17:00:00'))); + } + + public function testRoundSeconds() + { + $d = new DateParser($this->container); + $this->assertEquals('16:30', date('H:i', $d->getRoundedSeconds(strtotime('16:28')))); + $this->assertEquals('16:00', date('H:i', $d->getRoundedSeconds(strtotime('16:02')))); + $this->assertEquals('16:15', date('H:i', $d->getRoundedSeconds(strtotime('16:14')))); + $this->assertEquals('17:00', date('H:i', $d->getRoundedSeconds(strtotime('16:58')))); + } + + public function testGetHours() + { + $d = new DateParser($this->container); + + $this->assertEquals(1, $d->getHours(new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 16:00:00'))); + $this->assertEquals(2.5, $d->getHours(new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 17:30:00'))); + $this->assertEquals(2.75, $d->getHours(new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 17:45:00'))); + $this->assertEquals(3, $d->getHours(new DateTime('2015-03-14 14:57:00'), new DateTime('2015-03-14 17:58:00'))); + $this->assertEquals(3, $d->getHours(new DateTime('2015-03-14 14:57:00'), new DateTime('2015-03-14 11:58:00'))); + } + public function testValidDate() { $d = new DateParser($this->container); diff --git a/tests/units/SubtaskTimeTrackingTest.php b/tests/units/SubtaskTimeTrackingTest.php index 90650e42..e15e60da 100644 --- a/tests/units/SubtaskTimeTrackingTest.php +++ b/tests/units/SubtaskTimeTrackingTest.php @@ -176,38 +176,35 @@ class SubtaskTimeTrackingTest extends Base $this->assertEquals(7, $s->create(array('title' => 'subtask #7', 'task_id' => 2))); $this->assertEquals(8, $s->create(array('title' => 'subtask #8', 'task_id' => 2))); - // Create a couple of time slots - $now = time(); - // Slot start before and finish inside the calendar time range - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 1, 'start' => $now - 86400, 'end' => $now + 3600)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 1, 'start' => strtotime('-1 day'), 'end' => strtotime('+1 hour'))); // Slot start inside time range and finish after the time range - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 2, 'start' => $now + 3600, 'end' => $now + 2*86400)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 2, 'start' => strtotime('+1 hour'), 'end' => strtotime('+2 days'))); // Start before time range and finish inside time range - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 3, 'start' => $now - 86400, 'end' => $now + 1.5*86400)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 3, 'start' => strtotime('-1 day'), 'end' => strtotime('+1.5 days'))); // Start and finish inside time range - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 4, 'start' => $now + 3600, 'end' => $now + 2*3600)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 4, 'start' => strtotime('+1 hour'), 'end' => strtotime('+2 hours'))); // Start and finish after the time range - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 5, 'start' => $now + 2*86400, 'end' => $now + 3*86400)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 5, 'start' => strtotime('+2 days'), 'end' => strtotime('+3 days'))); // Start and finish before the time range - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 6, 'start' => $now - 2*86400, 'end' => $now - 86400)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 6, 'start' => strtotime('-2 days'), 'end' => strtotime('-1 day'))); // Start before time range and not finished - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 7, 'start' => $now - 86400)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 7, 'start' => strtotime('-1 day'))); // Start inside time range and not finish - $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 8, 'start' => $now + 3200)); + $this->container['db']->table(SubtaskTimeTracking::TABLE)->insert(array('user_id' => 1, 'subtask_id' => 8, 'start' => strtotime('+3200 seconds'))); $timesheet = $st->getUserTimesheet(1); $this->assertNotEmpty($timesheet); $this->assertCount(8, $timesheet); - $events = $st->getUserCalendarEvents(1, date('Y-m-d', $now), date('Y-m-d', $now + 86400)); + $events = $st->getUserCalendarEvents(1, date('Y-m-d'), date('Y-m-d', strtotime('+2 day'))); $this->assertNotEmpty($events); $this->assertCount(6, $events); $this->assertEquals(1, $events[0]['subtask_id']); @@ -217,14 +214,14 @@ class SubtaskTimeTrackingTest extends Base $this->assertEquals(7, $events[4]['subtask_id']); $this->assertEquals(8, $events[5]['subtask_id']); - $events = $st->getProjectCalendarEvents(1, date('Y-m-d', $now), date('Y-m-d', $now + 86400)); + $events = $st->getProjectCalendarEvents(1, date('Y-m-d'), date('Y-m-d', strtotime('+2 days'))); $this->assertNotEmpty($events); $this->assertCount(3, $events); $this->assertEquals(1, $events[0]['subtask_id']); $this->assertEquals(2, $events[1]['subtask_id']); $this->assertEquals(3, $events[2]['subtask_id']); - $events = $st->getProjectCalendarEvents(2, date('Y-m-d', $now), date('Y-m-d', $now + 86400)); + $events = $st->getProjectCalendarEvents(2, date('Y-m-d'), date('Y-m-d', strtotime('+2 days'))); $this->assertNotEmpty($events); $this->assertCount(3, $events); $this->assertEquals(4, $events[0]['subtask_id']); diff --git a/tests/units/TimetableTest.php b/tests/units/TimetableTest.php index 4b4dcc83..c88371f9 100644 --- a/tests/units/TimetableTest.php +++ b/tests/units/TimetableTest.php @@ -127,4 +127,127 @@ class TimetableTest extends Base $this->assertEquals($friday->format('Y-m-d').' 13:00', $timetable[4][0]->format('Y-m-d H:i')); $this->assertEquals($friday->format('Y-m-d').' 17:00', $timetable[4][1]->format('Y-m-d H:i')); } + + public function testClosestTimeSlot() + { + $w = new TimetableWeek($this->container); + $t = new Timetable($this->container); + + $this->assertNotFalse($w->create(1, 1, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 1, '13:00', '17:00')); + $this->assertNotFalse($w->create(1, 2, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 2, '13:00', '17:00')); + + $monday = new DateTime('next Monday'); + $tuesday = new DateTime('next Tuesday'); + + $timetable = $t->calculate(1, new DateTime('next Monday'), new DateTime('next Monday + 6 days')); + $this->assertNotEmpty($timetable); + $this->assertCount(4, $timetable); + + // Start to work before timetable + $date = new DateTime('next Monday'); + $date->setTime(5, 02); + + $slot = $t->findClosestTimeSlot($date, $timetable); + $this->assertNotEmpty($slot); + $this->assertEquals($monday->format('Y-m-d').' 09:30', $slot[0]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 12:00', $slot[1]->format('Y-m-d H:i')); + + // Start to work at the end of the timeslot + $date = new DateTime('next Monday'); + $date->setTime(12, 02); + + $slot = $t->findClosestTimeSlot($date, $timetable); + $this->assertNotEmpty($slot); + $this->assertEquals($monday->format('Y-m-d').' 09:30', $slot[0]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 12:00', $slot[1]->format('Y-m-d H:i')); + + // Start to work at lunch time + $date = new DateTime('next Monday'); + $date->setTime(12, 32); + + $slot = $t->findClosestTimeSlot($date, $timetable); + $this->assertNotEmpty($slot); + $this->assertEquals($monday->format('Y-m-d').' 13:00', $slot[0]->format('Y-m-d H:i')); + $this->assertEquals($monday->format('Y-m-d').' 17:00', $slot[1]->format('Y-m-d H:i')); + + // Start to work early in the morning + $date = new DateTime('next Tuesday'); + $date->setTime(8, 02); + + $slot = $t->findClosestTimeSlot($date, $timetable); + $this->assertNotEmpty($slot); + $this->assertEquals($tuesday->format('Y-m-d').' 09:30', $slot[0]->format('Y-m-d H:i')); + $this->assertEquals($tuesday->format('Y-m-d').' 12:00', $slot[1]->format('Y-m-d H:i')); + } + + public function testCalculateDuration() + { + $w = new TimetableWeek($this->container); + $t = new Timetable($this->container); + + $this->assertNotFalse($w->create(1, 1, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 1, '13:00', '17:00')); + $this->assertNotFalse($w->create(1, 2, '09:30', '12:00')); + $this->assertNotFalse($w->create(1, 2, '13:00', '17:00')); + + // Different day + $start = new DateTime('next Monday'); + $start->setTime(16, 02); + + $end = new DateTime('next Tuesday'); + $end->setTime(10, 03); + + $this->assertEquals(1.5, $t->calculateEffectiveDuration(1, $start, $end)); + + // Same time slot + $start = new DateTime('next Monday'); + $start->setTime(16, 02); + + $end = new DateTime('next Monday'); + $end->setTime(17, 03); + + $this->assertEquals(1, $t->calculateEffectiveDuration(1, $start, $end)); + + // Intermediate time slot + $start = new DateTime('next Monday'); + $start->setTime(10, 02); + + $end = new DateTime('next Tuesday'); + $end->setTime(16, 03); + + $this->assertEquals(11.5, $t->calculateEffectiveDuration(1, $start, $end)); + + // Different day + $start = new DateTime('next Monday'); + $start->setTime(9, 02); + + $end = new DateTime('next Tuesday'); + $end->setTime(10, 03); + + $this->assertEquals(7, $t->calculateEffectiveDuration(1, $start, $end)); + + // Start before first time slot + $start = new DateTime('next Monday'); + $start->setTime(5, 32); + + $end = new DateTime('next Tuesday'); + $end->setTime(11, 17); + + $this->assertEquals(8.25, $t->calculateEffectiveDuration(1, $start, $end)); + } + + public function testCalculateDurationWithEmptyTimetable() + { + $t = new Timetable($this->container); + + $start = new DateTime('next Monday'); + $start->setTime(16, 02); + + $end = new DateTime('next Monday'); + $end->setTime(17, 03); + + $this->assertEquals(1, $t->calculateEffectiveDuration(1, $start, $end)); + } } |