Further time recording implementation

This commit is contained in:
Dennis Eichhorn 2019-10-21 15:38:50 +02:00
parent d0ec492329
commit 0cb87e39ed
15 changed files with 703 additions and 60 deletions

View File

@ -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,

31
Admin/Routes/Web/Api.php Normal file
View File

@ -0,0 +1,31 @@
<?php declare(strict_types=1);
use Modules\HumanResourceTimeRecording\Controller\ApiController;
use Modules\HumanResourceTimeRecording\Models\PermissionState;
use phpOMS\Account\PermissionType;
use phpOMS\Router\RouteVerb;
return [
'^.*/humanresource/timerecording/session.*$' => [
[
'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,
],
],
],
];

View File

@ -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,
],
],
],
];

View File

@ -0,0 +1,309 @@
<?php
/**
* Orange Management
*
* PHP Version 7.4
*
* @package Modules\HumanResourceTimeRecording
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://orange-management.org
*/
declare(strict_types=1);
namespace Modules\HumanResourceTimeRecording\Controller;
use Modules\HumanResourceTimeRecording\Models\ClockingStatus;
use Modules\HumanResourceTimeRecording\Models\ClockingType;
use Modules\HumanResourceTimeRecording\Models\NullSession;
use Modules\HumanResourceTimeRecording\Models\PermissionState;
use Modules\HumanResourceTimeRecording\Models\Session;
use Modules\HumanResourceTimeRecording\Models\SessionElement;
use Modules\HumanResourceTimeRecording\Models\SessionElementMapper;
use Modules\HumanResourceTimeRecording\Models\SessionMapper;
use phpOMS\Account\PermissionType;
use phpOMS\Message\NotificationLevel;
use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use phpOMS\Model\Message\FormValidation;
use phpOMS\Utils\Parser\Markdown\Markdown;
use Modules\HumanResourceManagement\Models\EmployeeMapper;
use Modules\HumanResourceManagement\Models\NullEmployee;
use phpOMS\DataStorage\Database\RelationType;
/**
* HumanResourceTimeRecording controller class.
*
* @package Modules\HumanResourceTimeRecording
* @license OMS License 1.0
* @link https://orange-management.org
* @since 1.0.0
*/
final class ApiController extends Controller
{
/**
* Api method to create a session
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiSessionCreate(RequestAbstract $request, ResponseAbstract $response, $data = null) : void
{
$session = $this->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<string, bool> 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
}
}

View File

@ -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;

View File

@ -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.

View File

@ -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;
}

View File

@ -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
*

View File

@ -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(),
];
}

View File

@ -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;
}
}

View File

@ -15,5 +15,7 @@ declare(strict_types=1);
return ['Navigation' => [
'Create' => 'Create',
'List' => 'List',
'Stats' => 'Stats',
'TimeRecording' => 'Time Recording',
'Today' => 'Today',
]];

View File

@ -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',
]];

View File

@ -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

View File

@ -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
<div class="row">
<div class="col-md-4 col-xs-12">
<section class="box wf-100">
<div class="inner">
<form id="clocking" method="PUT" action="<?= \phpOMS\Uri\UriFactory::build('{/api}task/element?{?}&csrf={$CSRF}'); ?>">
<form id="iClocking" method="PUT" action="<?= \phpOMS\Uri\UriFactory::build('{/api}humanresource/timerecording/element?{?}&csrf={$CSRF}'); ?>">
<table class="layout wf-100" style="table-layout: fixed">
<tr><td><label for="iType"><?= $this->getHtml('Type') ?></label>
<tr><td>
<select id="iType" name="Type">
<option value="<?= ClockingType::OFFICE; ?>"><?= $this->getHtml('CT0') ?>
<option value="<?= ClockingType::REMOTE; ?>"><?= $this->getHtml('CT1') ?>
<option value="<?= ClockingType::HOME; ?>"><?= $this->getHtml('CT2') ?>
<option value="<?= ClockingType::VACATION; ?>"><?= $this->getHtml('CT3') ?>
<option value="<?= ClockingType::SICK; ?>"><?= $this->getHtml('CT4') ?>
<option value="<?= ClockingType::ON_THE_MOVE; ?>"><?= $this->getHtml('CT5') ?>
<select id="iType" name="type">
<option value="<?= ClockingType::OFFICE; ?>"<?= $type === ClockingType::OFFICE ? ' selected': ''; ?>><?= $this->getHtml('CT1') ?>
<option value="<?= ClockingType::REMOTE; ?>"<?= $type === ClockingType::REMOTE ? ' selected': ''; ?>><?= $this->getHtml('CT3') ?>
<option value="<?= ClockingType::HOME; ?>"<?= $type === ClockingType::HOME ? ' selected': ''; ?>><?= $this->getHtml('CT2') ?>
<option value="<?= ClockingType::VACATION; ?>"<?= $type === ClockingType::VACATION ? ' selected': ''; ?>><?= $this->getHtml('CT4') ?>
<option value="<?= ClockingType::SICK; ?>"<?= $type === ClockingType::SICK ? ' selected': ''; ?>><?= $this->getHtml('CT5') ?>
<option value="<?= ClockingType::ON_THE_MOVE; ?>"<?= $type === ClockingType::ON_THE_MOVE ? ' selected': ''; ?>><?= $this->getHtml('CT6') ?>
</select>
<tr><td><label for="iStatus"><?= $this->getHtml('Status') ?></label>
<tr><td>
<select id="iStatus" name="Status">
<option value="<?= ClockingStatus::START; ?>"><?= $this->getHtml('CS0') ?>
<option value="<?= ClockingStatus::PAUSE; ?>"><?= $this->getHtml('CS1') ?>
<option value="<?= ClockingStatus::CONTINUE; ?>"><?= $this->getHtml('CS2') ?>
<option value="<?= ClockingStatus::END; ?>"><?= $this->getHtml('CS3') ?>
<select id="iStatus" name="status">
<option value="<?= ClockingStatus::START; ?>"<?= $status === ClockingStatus::END ? ' selected' : ''; ?>><?= $this->getHtml('CS1') ?>
<option value="<?= ClockingStatus::PAUSE; ?>"<?= $status === ClockingStatus::START ? ' selected' : ''; ?>><?= $this->getHtml('CS2') ?>
<option value="<?= ClockingStatus::CONTINUE; ?>"<?= $status === ClockingStatus::PAUSE ? ' selected' : ''; ?>><?= $this->getHtml('CS3') ?>
<option value="<?= ClockingStatus::END; ?>"<?= $status === ClockingStatus::CONTINUE ? ' selected' : ''; ?>><?= $this->getHtml('CS4') ?>
</select>
<tr><td>
<input type="hidden" name="session" value="<?= $lastOpenSession !== null ? $lastOpenSession->getId() : ''; ?>">
<input type="submit" id="iclockingButton" name="clockingButton" value="<?= $this->getHtml('Submit', '0', '0'); ?>">
</table>
</form>
@ -87,34 +122,56 @@ echo $this->getData('nav')->render(); ?>
<thead>
<tr>
<td><?= $this->getHtml('Date'); ?>
<td>Status
<td><?= $this->getHtml('Start') ?>
<td><?= $this->getHtml('Break') ?>
<td><?= $this->getHtml('End') ?>
<td><?= $this->getHtml('Total') ?>
<td><?= $this->getHtml('Type'); ?>
<td><?= $this->getHtml('Status'); ?>
<td><?= $this->getHtml('Start'); ?>
<td><?= $this->getHtml('Break'); ?>
<td><?= $this->getHtml('End'); ?>
<td><?= $this->getHtml('Total'); ?>
<tfoot>
<tr><td colspan="5">
<tbody>
<?php foreach ($sessions as $session) : ?>
<?php
$count = 0; foreach ($sessions as $session) : ++$count;
$url = \phpOMS\Uri\UriFactory::build('{/prefix}private/timerecording/session?{?}&id=' . $session->getId());
?>
<tr data-href="<?= $url; ?>">
<td><a href="<?= $url; ?>">
<?php if ($lastOpenSession !== null && $session->getStart()->format('Y-m-d') === $lastOpenSession->getStart()->format('Y-m-d')) : ?>
<span class="tag">Today</span>
<?php else : ?>
<?= $session->getStart()->format('Y-m-d'); ?> - <?= $this->getHtml('D' . $session->getStart()->format('w')); ?>
<?php endif; ?></a>
<td><a href="<?= $url; ?>"><span class="tag"><?= $this->getHtml('CT' . $session->getType()) ?></span></a>
<td><a href="<?= $url; ?>"><span class="tag"><?= $this->getHtml('CS' . $session->getStatus()) ?></span></a>
<td><a href="<?= $url; ?>"><?= $session->getStart()->format('H:i'); ?></a>
<td><a href="<?= $url; ?>"><?= (int) ($session->getBreak() / 3600); ?>h <?= ((int) ($session->getBreak() / 60) % 60); ?>m</a>
<td><a href="<?= $url; ?>"><?= $session->getEnd() !== null ? $session->getEnd()->format('H:i') : ''; ?></a>
<td><a href="<?= $url; ?>"><?= (int) ($session->getBusy() / 3600); ?>h <?= ((int) ($session->getBusy() / 60) % 60); ?>m</a>
<?php if ($session->getStart()->getTimestamp() < $startWeek->getTimestamp() || $count === $sessionCount) : ?>
<tr>
<td><?= $session->getStart()->format('Y-m-d'); ?> - <?= $this->getHtml('D' . $session->getStart()->format('w')); ?>
<td><span class="tag">Status Here</span>
<td><?= $session->getStart()->format('H:i'); ?>
<td><?= (int) ($session->getBreak() / 3600); ?>h <?= ((int) ($session->getBreak() / 60) % 60); ?>m
<td><?= $session->getEnd() !== null ? $session->getEnd()->format('H:i') : ''; ?>
<td><?= (int) ($session->getBusy() / 3600); ?>h <?= ((int) ($session->getBusy() / 60) % 60); ?>m
<th colspan="6"> <?= $startWeek->format('Y/m/d'); ?> - <?= $endWeek->format('Y/m/d'); ?>
<th><?= (int) ($busy['week'] / 3600); ?>h <?= ((int) ($busy['week'] / 60) % 60); ?>m
<?php
$endWeek = $startWeek;
$startWeek = $startWeek->createModify(0, 0, -7);
$busy['week'] = 0;
endif;
$busy['week'] += $session->getBusy();
?>
<?php if ($session->getStart()->getTimestamp() < $startMonth->getTimestamp() || $count === $sessionCount) : ?>
<tr>
<th colspan="6"> <?= $startMonth->format('Y/m/d'); ?> - <?= $endMonth->format('Y/m/d'); ?>
<th><?= (int) ($busy['month'] / 3600); ?>h <?= ((int) ($busy['month'] / 60) % 60); ?>m
<?php
$endMonth = $startMonth;
$startMonth = $startMonth->createModify(0, -1, 0);
$busy['month'] = 0;
endif;
$busy['month'] += $session->getBusy();
?>
<?php endforeach; ?>
</table>
</div>
</div>
</div>
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
</div>

View File

@ -0,0 +1,43 @@
<?php
/**
* Orange Management
*
* PHP Version 7.4
*
* @package HumanResourceTimeRecording
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://orange-management.org
*/
declare(strict_types=1);
/** @var \Modules\HumanResourceTimeRecording\Models\Session $session */
$session = $this->getData('session');
$elements = $session->getSessionElements();
echo $this->getData('nav')->render(); ?>
<div class="row">
<div class="col-xs-12">
<div class="box wf-100">
<table id="accountList" class="default">
<caption><?= $session->getStart()->format('Y-m-d'); ?><i class="fa fa-download floatRight download btn"></i></caption>
<thead>
<tr>
<td><?= $this->getHtml('Status'); ?>
<td class="wf-100"><?= $this->getHtml('Time'); ?>
<td class="wf-100"><?= $this->getHtml('Date'); ?>
<tfoot>
<tr><td colspan="2">
<tbody>
<?php foreach ($elements as $element) : ?>
<tr>
<td><?= $this->getHtml('CS' . $element->getStatus()); ?>
<td><?= $element->getDatetime()->format('H:i:s'); ?>
<td><?= $element->getDatetime()->format('Y-m-d'); ?>
<?php endforeach; ?>
</table>
</div>
</div>
</div>