From 0cb87e39edba64af46015cb27c12acd34e9e7619 Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Mon, 21 Oct 2019 15:38:50 +0200 Subject: [PATCH] Further time recording implementation --- Admin/Install/Navigation.install.json | 2 +- Admin/Routes/Web/Api.php | 31 +++ Admin/Routes/Web/Backend.php | 11 + Controller/ApiController.php | 309 ++++++++++++++++++++++ Controller/BackendController.php | 73 ++++- Controller/TimerecordingController.php | 3 +- Models/PermissionState.php | 8 +- Models/Session.php | 46 +++- Models/SessionElement.php | 4 +- Models/SessionMapper.php | 69 ++++- Theme/Backend/Lang/Navigation.en.lang.php | 2 + Theme/Backend/Lang/en.lang.php | 19 +- Theme/Backend/hr-stats.tpl.php | 10 + Theme/Backend/private-dashboard.tpl.php | 133 +++++++--- Theme/Backend/private-session.tpl.php | 43 +++ 15 files changed, 703 insertions(+), 60 deletions(-) create mode 100644 Admin/Routes/Web/Api.php create mode 100644 Controller/ApiController.php create mode 100644 Theme/Backend/hr-stats.tpl.php create mode 100644 Theme/Backend/private-session.tpl.php diff --git a/Admin/Install/Navigation.install.json b/Admin/Install/Navigation.install.json index 00913bb..de0284e 100644 --- a/Admin/Install/Navigation.install.json +++ b/Admin/Install/Navigation.install.json @@ -34,7 +34,7 @@ "type": 3, "subtype": 1, "name": "Stats", - "uri": "{/prefix}humanresource/timerecording/dashboard?{?}", + "uri": "{/prefix}humanresource/timerecording/stats?{?}", "target": "self", "icon": null, "order": 1, diff --git a/Admin/Routes/Web/Api.php b/Admin/Routes/Web/Api.php new file mode 100644 index 0000000..3146ea0 --- /dev/null +++ b/Admin/Routes/Web/Api.php @@ -0,0 +1,31 @@ + [ + [ + 'dest' => '\Modules\HumanResourceTimeRecording\Controller\ApiController:apiSessionCreate', + 'verb' => RouteVerb::PUT, + 'permission' => [ + 'module' => ApiController::MODULE_NAME, + 'type' => PermissionType::CREATE, + 'state' => PermissionState::SESSION, + ], + ], + ], + '^.*/humanresource/timerecording/element.*$' => [ + [ + 'dest' => '\Modules\HumanResourceTimeRecording\Controller\ApiController:apiSessionElementCreate', + 'verb' => RouteVerb::PUT, + 'permission' => [ + 'module' => ApiController::MODULE_NAME, + 'type' => PermissionType::CREATE, + 'state' => PermissionState::SESSION_ELEMENT, + ], + ], + ], +]; \ No newline at end of file diff --git a/Admin/Routes/Web/Backend.php b/Admin/Routes/Web/Backend.php index 0439129..b4da764 100644 --- a/Admin/Routes/Web/Backend.php +++ b/Admin/Routes/Web/Backend.php @@ -28,4 +28,15 @@ return [ ], ], ], + '^.*/private/timerecording/session.*$' => [ + [ + 'dest' => '\Modules\HumanResourceTimeRecording\Controller\BackendController:viewPrivateSession', + 'verb' => RouteVerb::GET, + 'permission' => [ + 'module' => BackendController::MODULE_NAME, + 'type' => PermissionType::READ, + 'state' => PermissionState::PRIVATE_DASHBOARD, + ], + ], + ], ]; diff --git a/Controller/ApiController.php b/Controller/ApiController.php new file mode 100644 index 0000000..177a218 --- /dev/null +++ b/Controller/ApiController.php @@ -0,0 +1,309 @@ +createSessionFromRequest($request); + + if ($session === null) { + $this->fillJsonResponse($request, $response, NotificationLevel::ERROR, 'Session', 'Session couldn\'t be created.', $session); + + return; + } + + $this->createModel($request->getHeader()->getAccount(), $session, SessionMapper::class, 'session'); + $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Session', 'Session successfully created', $session); + } + + /** + * Method to create a session from request. + * + * @param RequestAbstract $request Request + * + * @return null|Session + * + * @since 1.0.0 + */ + private function createSessionFromRequest(RequestAbstract $request) : ?Session + { + $account = (int) ($request->getData('account') ?? $request->getHeader()->getAccount()); + + if ($request->getData('account') !== null) { + if (!$this->app->accountManager->get($request->getHeader()->getAccount())->hasPermission( + PermissionType::CREATE, $this->app->orgId, $this->app->appName, self::MODULE_NAME, PermissionState::SESSION_FOREIGN + )) { + return null; + } + } + + $employee = EmployeeMapper::getFromAccount($account); + $type = (int) $request->getData('type'); + $status = (int) $request->getData('status'); + + if ($employee instanceof NullEmployee) { + return null; + } + + $session = new Session($employee); + $session->setType(ClockingType::isValidValue($type) ? $type : ClockingType::OFFICE); + + if (ClockingStatus::isValidValue($status)) { + // a custom datetime can only be set if the user is allowed to create a session for a foreign account or if the session is a vacation + $dt = $request->getData('datetime') !== null + && ($request->getData('account') !== null + || $type === ClockingType::VACATION + ) ? new \DateTime($request->getData('datetime')) : new \DateTime('now'); + + $element = new SessionElement($session, $dt); + $element->setStatus($status); + + $session->addSessionElement($element); + } + + return $session; + } + + /** + * Api method to create a session element + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiSessionElementCreate(RequestAbstract $request, ResponseAbstract $response, $data = null) : void + { + if ((int) $request->getData('status') === ClockingStatus::START) { + $this->apiSessionCreate($request, $response); + + return; + } + + if (!empty($val = $this->validateSessionElementCreate($request))) { + $response->set($request->getUri()->__toString(), new FormValidation($val)); + + return; + } + + $element = $this->createSessionElementFromRequest($request); + + if ($element === null) { + $this->fillJsonResponse($request, $response, NotificationLevel::ERROR, 'Session Element', 'You cannot create a session element for another person!', $element); + } + + if ($element->getStatus() === ClockingStatus::END) { + $session = SessionMapper::get($element->getSession()->getId()); + $session->addSessionElement($element); + SessionMapper::update($session); + } + + $this->createModel($request->getHeader()->getAccount(), $element, SessionElementMapper::class, 'element'); + + $this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Session Element', 'Session Element successfully created', $element); + } + + /** + * Validate session element create request + * + * @param RequestAbstract $request Request + * + * @return array Returns the validation array of the request + * + * @since 1.0.0 + */ + private function validateSessionElementCreate(RequestAbstract $request) : array + { + $val = []; + if (($val['session'] = empty($request->getData('session')) || !\is_numeric($request->getData('session')))) { + return $val; + } + + return []; + } + + /** + * Method to create session element from request. + * + * @param RequestAbstract $request Request + * + * @return SessionElement Returns the created session element from the request + * + * @since 1.0.0 + */ + private function createSessionElementFromRequest(RequestAbstract $request) : ?SessionElement + { + $account = (int) ($request->getData('account') ?? $request->getHeader()->getAccount()); + + /** @var Session $session */ + $session = SessionMapper::get((int) $request->getData('session'), RelationType::ALL, null, 6); + + // cannot create session element for none existing session + if ($session === null || $session instanceof NullSession) { + return null; + } + + // account and owner of the session don't match = exception! + if ($session->getEmployee()->getProfile()->getAccount()->getId() !== $account) { + return null; + } + + // check permissions to edit session and create session element of a foreign account + if ($request->getData('account') !== null + || $session->getEmployee()->getProfile()->getAccount()->getId() !== $request->getHeader()->getAccount() + ) { + if (!$this->app->accountManager->get($request->getHeader()->getAccount())->hasPermission( + PermissionType::CREATE, $this->app->orgId, $this->app->appName, self::MODULE_NAME, PermissionState::SESSION_ELEMENT_FOREIGN + )) { + return null; + } + } + + $status = (int) $request->getData('status'); + + // a custom datetime can only be set if the user is allowed to create a session for a foreign account or if the session is a vacation + $dt = $request->getData('datetime') !== null + && ($request->getData('account') !== null + || $session->getType() === ClockingType::VACATION + ) ? new \DateTime($request->getData('datetime')) : new \DateTime('now'); + + $element = new SessionElement($session, $dt); + $element->setStatus(ClockingStatus::isValidValue($status) ? $status : ClockingStatus::END); + + return $element; + } + + /** + * Api method to update a session + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiSessionUpdate(RequestAbstract $request, ResponseAbstract $response, $data = null) : void + { + // todo: allow to change employee and type. + // employee can only change start/end if vacation + } + + /** + * Api method to update a session element + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiSessionElementUpdate(RequestAbstract $request, ResponseAbstract $response, $data = null) : void + { + // todo: allow to change session, status, and datetime + // employee can only change start/end if vacation + // any change in start end will also change the session because it's not 100% normalized! + } + + /** + * Api method to update a session + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiSessionDelete(RequestAbstract $request, ResponseAbstract $response, $data = null) : void + { + // employee can only delete if vacation + } + + /** + * Api method to update a session element + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiSessionElementDelete(RequestAbstract $request, ResponseAbstract $response, $data = null) : void + { + // employee can only delete if vacation + } +} diff --git a/Controller/BackendController.php b/Controller/BackendController.php index 0e95c2d..c08e77b 100644 --- a/Controller/BackendController.php +++ b/Controller/BackendController.php @@ -14,11 +14,14 @@ declare(strict_types=1); namespace Modules\HumanResourceTimeRecording\Controller; +use Modules\HumanResourceTimeRecording\Models\SessionMapper; + use phpOMS\Contract\RenderableInterface; use phpOMS\Message\RequestAbstract; use phpOMS\Message\ResponseAbstract; use phpOMS\Views\View; -use Modules\HumanResourceTimeRecording\Models\SessionMapper; +use Modules\HumanResourceManagement\Models\EmployeeMapper; +use phpOMS\Stdlib\Base\SmartDateTime; /** * TimeRecording controller class. @@ -49,7 +52,7 @@ final class BackendController extends Controller $view->setTemplate('/Modules/HumanResourceTimeRecording/Theme/Backend/dashboard'); $view->addData('nav', $this->app->moduleManager->get('Navigation')->createNavigationMid(1006301001, $request, $response)); - $list = SessionMapper::getLastSessionsForDate(new \DateTime('now')); + $list = SessionMapper::getLastSessionsFromAllEmployees(new \DateTime('now')); $view->addData('sessions', $list); return $view; @@ -73,7 +76,71 @@ final class BackendController extends Controller $view->setTemplate('/Modules/HumanResourceTimeRecording/Theme/Backend/private-dashboard'); $view->addData('nav', $this->app->moduleManager->get('Navigation')->createNavigationMid(1006303001, $request, $response)); - $list = SessionMapper::getNewest(50); + $employee = EmployeeMapper::getFromAccount($request->getHeader()->getAccount())->getId(); + $lastOpenSession = SessionMapper::getMostPlausibleOpenSessionForEmployee($employee); + + $limit = new SmartDateTime('now'); + $limit = $limit->getEndOfMonth(); + $limit->smartModify(0, -2, 0); + + $list = SessionMapper::getNewestForEmployee($employee, $limit); + + $view->addData('sessions', $list); + $view->addData('lastSession', $lastOpenSession); + $view->addData('date', $limit); + + return $view; + } + + /** + * Routing end-point for application behaviour. + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return RenderableInterface + * + * @since 1.0.0 + * @codeCoverageIgnore + */ + public function viewPrivateSession(RequestAbstract $request, ResponseAbstract $response, $data = null) : RenderableInterface + { + $view = new View($this->app, $request, $response); + $view->setTemplate('/Modules/HumanResourceTimeRecording/Theme/Backend/private-session'); + $view->addData('nav', $this->app->moduleManager->get('Navigation')->createNavigationMid(1006303001, $request, $response)); + + $session = SessionMapper::get((int) $request->getData('id')); + $employee = EmployeeMapper::getFromAccount($request->getHeader()->getAccount())->getId(); + + if ($session->getEmployee()->getId() !== $employee) { + $view->addData('session', new NullSession()); + } else { + $view->addData('session', $session); + } + + return $view; + } + + /** + * Routing end-point for application behaviour. + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param mixed $data Generic data + * + * @return RenderableInterface + * + * @since 1.0.0 + * @codeCoverageIgnore + */ + public function viewHRStats(RequestAbstract $request, ResponseAbstract $response, $data = null) : RenderableInterface + { + $view = new View($this->app, $request, $response); + $view->setTemplate('/Modules/HumanResourceTimeRecording/Theme/Backend/hr-stats'); + $view->addData('nav', $this->app->moduleManager->get('Navigation')->createNavigationMid(1006301001, $request, $response)); + + $list = SessionMapper::getLastSessionsFromAllEmployees(new \DateTime('now')); $view->addData('sessions', $list); return $view; diff --git a/Controller/TimerecordingController.php b/Controller/TimerecordingController.php index 9b3ac0e..49651c7 100644 --- a/Controller/TimerecordingController.php +++ b/Controller/TimerecordingController.php @@ -14,11 +14,12 @@ declare(strict_types=1); namespace Modules\HumanResourceTimeRecording\Controller; +use Modules\HumanResourceTimeRecording\Models\SessionMapper; + use phpOMS\Contract\RenderableInterface; use phpOMS\Message\RequestAbstract; use phpOMS\Message\ResponseAbstract; use phpOMS\Views\View; -use Modules\HumanResourceTimeRecording\Models\SessionMapper; /** * TimeRecording controller class. diff --git a/Models/PermissionState.php b/Models/PermissionState.php index 5e031ea..7140dbf 100644 --- a/Models/PermissionState.php +++ b/Models/PermissionState.php @@ -26,6 +26,10 @@ use phpOMS\Stdlib\Base\Enum; */ abstract class PermissionState extends Enum { - public const DASHBOARD = 1; - public const PRIVATE_DASHBOARD = 2; + public const DASHBOARD = 1; + public const PRIVATE_DASHBOARD = 2; + public const SESSION = 3; + public const SESSION_FOREIGN = 4; + public const SESSION_ELEMENT = 5; + public const SESSION_ELEMENT_FOREIGN = 6; } diff --git a/Models/Session.php b/Models/Session.php index ad9ee6f..0fdcc73 100644 --- a/Models/Session.php +++ b/Models/Session.php @@ -131,13 +131,19 @@ class Session implements ArrayableInterface, \JsonSerializable public function addSessionElement($element) : void { if ($element->getStatus() === ClockingStatus::START) { - // todo: prevent multiple starts and ends per session? + foreach ($this->sessionElements as $e) { + if ($e->getStatus === ClockingStatus::START) { + return; + } + } $this->start = $element->getDatetime(); } if ($element->getStatus() === ClockingStatus::END) { - // todo: prevent multiple starts and ends per session? + if ($this->end !== null) { + return; + } $this->end = $element->getDatetime(); } @@ -168,6 +174,42 @@ class Session implements ArrayableInterface, \JsonSerializable $this->busy = $busyTime; } + /** + * Get all session elements + * + * @return SessionElement[] + * + * @since 1.0.0 + */ + public function getSessionElements() : array + { + return $this->sessionElements; + } + + /** + * Get the status of the last session element + * + * @return int + * + * @since 1.0.0 + */ + public function getStatus() : int + { + if (\count($this->sessionElements) === 0) { + return ClockingStatus::START; + } + + \usort($this->sessionElements, function($a, $b) { + return $a->getDatetime()->getTimestamp() <=> $b->getDatetime()->getTimestamp(); + }); + + $last = \end($this->sessionElements); + + \reset($this->sessionElements); + + return $last->getStatus(); + } + /** * Get the total break time of a session * diff --git a/Models/SessionElement.php b/Models/SessionElement.php index e7a4739..a7679fe 100644 --- a/Models/SessionElement.php +++ b/Models/SessionElement.php @@ -141,9 +141,9 @@ class SessionElement implements ArrayableInterface, \JsonSerializable { return [ 'id' => $this->id, - 'status' => $this->status, + 'status' => $this->status, 'dt' => $this->dt->format('Y-m-d H:i:s'), - 'sesseion' => $this->session, + 'session' => \is_int($this->session) ? $this->session : $this->session->getId(), ]; } diff --git a/Models/SessionMapper.php b/Models/SessionMapper.php index 71f309e..387d165 100644 --- a/Models/SessionMapper.php +++ b/Models/SessionMapper.php @@ -18,6 +18,7 @@ use Modules\HumanResourceManagement\Models\EmployeeMapper; use phpOMS\DataStorage\Database\DataMapperAbstract; use phpOMS\DataStorage\Database\Query\Builder; use phpOMS\DataStorage\Database\RelationType; +use phpOMS\Stdlib\Base\SmartDateTime; /** * Mapper class. @@ -100,14 +101,14 @@ final class SessionMapper extends DataMapperAbstract /** * Get last sessions from all employees * - * @return array + * @return Session[] * * @todo: consider selecting only active employees * @todo: consider using a datetime to limit the results to look for * * @since 1.0.0 */ - public static function getLastSessionsForDate(\DateTime $dt = null) : array + public static function getLastSessionsFromAllEmployees(\DateTime $dt = null) : array { $join = new Builder(self::$db); $join->prefix(self::$db->getPrefix()) @@ -125,4 +126,68 @@ final class SessionMapper extends DataMapperAbstract return self::getAllByQuery($query, RelationType::ALL, 6); } + + /** + * Get the most plausible open session for an employee. + * + * This searches for an open session that could be ongoing. This is required to automatically select + * the current session for breaks, work continuation and ending work sessions without manually selecting + * the current session. + * + * @param int $employee Employee id + * + * @return null|Session + * + * @since 1.0.0 + */ + public static function getMostPlausibleOpenSessionForEmployee(int $employee) : ?Session + { + $dt = new SmartDateTime('now'); + $dt->smartModify(0, 0, -32); + + $query = self::getQuery(); + $query->where(self::$table.'.hr_timerecording_session_employee', '=', $employee) + ->andWhere(self::$table.'.hr_timerecording_session_start', '>', $dt) + ->orderBy(self::$table.'.hr_timerecording_session_start', 'DESC') + ->limit(1); + + /** @var Session[] $session */ + $sessions = self::getAllByQuery($query, RelationType::ALL, 6); + + if (empty($sessions)) { + return null; + } + + if (\end($sessions)->getEnd() === null) { + return \end($sessions); + } + + return null; + } + + /** + * Get newest sessions for employee + * + * @param int $employee Employee to get the sessions for + * @param \DateTime $start Sessions after this date + */ + public static function getNewestForEmployee(int $employee, \DateTime $start) : array + { + $query = new Builder(self::$db); + $query = self::getQuery($query) + ->where(self::$table . '.hr_timerecording_session_employee', '=', $employee) + ->andWhere(self::$table . '.' . self::$createdAt, '>', $start->format('Y-m-d')) + ->orderBy(self::$table . '.' . self::$createdAt, 'DESC'); + + $sth = self::$db->con->prepare($query->toSql()); + $sth->execute(); + + $results = $sth->fetchAll(\PDO::FETCH_ASSOC); + $obj = self::populateIterable($results === false ? [] : $results); + + self::fillRelations($obj, RelationType::ALL, 6); + self::clear(); + + return $obj; + } } diff --git a/Theme/Backend/Lang/Navigation.en.lang.php b/Theme/Backend/Lang/Navigation.en.lang.php index 662b952..ac48e00 100644 --- a/Theme/Backend/Lang/Navigation.en.lang.php +++ b/Theme/Backend/Lang/Navigation.en.lang.php @@ -15,5 +15,7 @@ declare(strict_types=1); return ['Navigation' => [ 'Create' => 'Create', 'List' => 'List', + 'Stats' => 'Stats', 'TimeRecording' => 'Time Recording', + 'Today' => 'Today', ]]; diff --git a/Theme/Backend/Lang/en.lang.php b/Theme/Backend/Lang/en.lang.php index fb00b86..030d966 100644 --- a/Theme/Backend/Lang/en.lang.php +++ b/Theme/Backend/Lang/en.lang.php @@ -14,16 +14,16 @@ declare(strict_types=1); return ['HumanResourceTimeRecording' => [ 'Break' => 'Break', - 'CS0' => 'Start', - 'CS1' => 'Pause', - 'CS2' => 'Continue', - 'CS3' => 'End', - 'CT0' => 'Office', - 'CT1' => 'Remote', + 'CS1' => 'Start', + 'CS2' => 'Pause', + 'CS3' => 'Continue', + 'CS4' => 'End', + 'CT1' => 'Office', 'CT2' => 'Home', - 'CT3' => 'Vacation', - 'CT4' => 'Sick', - 'CT5' => 'On the move', + 'CT3' => 'Remote', + 'CT4' => 'Vacation', + 'CT5' => 'Sick', + 'CT6' => 'On the move', 'D0' => 'Sunday', 'D1' => 'Monday', 'D2' => 'Tuesday', @@ -36,6 +36,7 @@ return ['HumanResourceTimeRecording' => [ 'Recordings' => 'Recordings', 'Start' => 'Start', 'Status' => 'Status', + 'Time' => 'Time', 'Total' => 'Total', 'Type' => 'Type', ]]; diff --git a/Theme/Backend/hr-stats.tpl.php b/Theme/Backend/hr-stats.tpl.php new file mode 100644 index 0000000..84dc45c --- /dev/null +++ b/Theme/Backend/hr-stats.tpl.php @@ -0,0 +1,10 @@ +list all employees here with strip_tags + +sickness +overtime +vacation ++ total + +filter for time line +filter for active/inactive +checkbox in front of name for filtering manually \ No newline at end of file diff --git a/Theme/Backend/private-dashboard.tpl.php b/Theme/Backend/private-dashboard.tpl.php index 54bbc22..c3fd024 100644 --- a/Theme/Backend/private-dashboard.tpl.php +++ b/Theme/Backend/private-dashboard.tpl.php @@ -12,39 +12,74 @@ */ declare(strict_types=1); - use \Modules\HumanResourceTimeRecording\Models\ClockingType; use \Modules\HumanResourceTimeRecording\Models\ClockingStatus; +use \phpOMS\Stdlib\Base\SmartDateTime; -$sessions = $this->getData('sessions'); +/** @var Session[] $sessions */ +$sessions = $this->getData('sessions'); +$sessionCount = \count($sessions); + +/** @var Session $lastOpenSession */ +$lastOpenSession = $this->getData('lastSession'); + +$type = $lastOpenSession !== null ? $lastOpenSession->getType() : ClockingType::OFFICE; +$status = $lastOpenSession !== null ? $lastOpenSession->getStatus() : ClockingStatus::END; + +// @todo: users may have a different definition of week start! +/** @var \phpOMS\Stdlib\Base\SmartDateTime $startWeek */ +$startWeek = new SmartDateTime('now'); +$startWeek = $startWeek->getStartOfWeek(); +$endWeek = $startWeek->createModify(0, 0, 6); + +$startMonth = new SmartDateTime('now'); +$startMonth = $startMonth->getStartOfMonth(); +$endMonth = $startMonth->createModify(0, 1, -1); + +$busy = [ + 'total' => 0, + 'month' => 0, + 'week' => 0, +]; echo $this->getData('nav')->render(); ?> +on response successfull reload!!! or! +on response successfull change ui/change ui and on response not successfull undo changed ui both result in the same + +list for month show total of total +list contains segments for week show total of total +every week contains every day show total of total +if you click on a day you get detailed information of that day + +show additional section with vacation days +
-
+
- +
- +
+
@@ -87,34 +122,56 @@ echo $this->getData('nav')->render(); ?> getHtml('Date'); ?> - Status - getHtml('Start') ?> - getHtml('Break') ?> - getHtml('End') ?> - getHtml('Total') ?> + getHtml('Type'); ?> + getHtml('Status'); ?> + getHtml('Start'); ?> + getHtml('Break'); ?> + getHtml('End'); ?> + getHtml('Total'); ?> - + getId()); + ?> + + + getStart()->format('Y-m-d') === $lastOpenSession->getStart()->format('Y-m-d')) : ?> + Today + + getStart()->format('Y-m-d'); ?> - getHtml('D' . $session->getStart()->format('w')); ?> + + getHtml('CT' . $session->getType()) ?> + getHtml('CS' . $session->getStatus()) ?> + getStart()->format('H:i'); ?> + getBreak() / 3600); ?>h getBreak() / 60) % 60); ?>m + getEnd() !== null ? $session->getEnd()->format('H:i') : ''; ?> + getBusy() / 3600); ?>h getBusy() / 60) % 60); ?>m + getStart()->getTimestamp() < $startWeek->getTimestamp() || $count === $sessionCount) : ?> - getStart()->format('Y-m-d'); ?> - getHtml('D' . $session->getStart()->format('w')); ?> - Status Here - getStart()->format('H:i'); ?> - getBreak() / 3600); ?>h getBreak() / 60) % 60); ?>m - getEnd() !== null ? $session->getEnd()->format('H:i') : ''; ?> - getBusy() / 3600); ?>h getBusy() / 60) % 60); ?>m + format('Y/m/d'); ?> - format('Y/m/d'); ?> + h m + createModify(0, 0, -7); + $busy['week'] = 0; + endif; + $busy['week'] += $session->getBusy(); + ?> + getStart()->getTimestamp() < $startMonth->getTimestamp() || $count === $sessionCount) : ?> + + format('Y/m/d'); ?> - format('Y/m/d'); ?> + h m + createModify(0, -1, 0); + $busy['month'] = 0; + endif; + $busy['month'] += $session->getBusy(); + ?>
-
- -on response successfull reload!!! or! -on response successfull change ui/change ui and on response not successfull undo changed ui both result in the same - -list for month show total of total -list contains segments for week show total of total -every week contains every day show total of total -if you click on a day you get detailed information of that day - -show additional section with vacation days \ No newline at end of file + \ No newline at end of file diff --git a/Theme/Backend/private-session.tpl.php b/Theme/Backend/private-session.tpl.php new file mode 100644 index 0000000..be96def --- /dev/null +++ b/Theme/Backend/private-session.tpl.php @@ -0,0 +1,43 @@ +getData('session'); +$elements = $session->getSessionElements(); + +echo $this->getData('nav')->render(); ?> + +
+
+
+ + + + + + + + +
getStart()->format('Y-m-d'); ?>
getHtml('Status'); ?> + getHtml('Time'); ?> + getHtml('Date'); ?> +
+
getHtml('CS' . $element->getStatus()); ?> + getDatetime()->format('H:i:s'); ?> + getDatetime()->format('Y-m-d'); ?> + +
+
+
+
\ No newline at end of file