mirror of
https://github.com/Karaka-Management/oms-HumanResourceTimeRecording.git
synced 2026-01-23 17:28:41 +00:00
Further time recording implementation
This commit is contained in:
parent
d0ec492329
commit
0cb87e39ed
|
|
@ -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
31
Admin/Routes/Web/Api.php
Normal 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,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
@ -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,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
309
Controller/ApiController.php
Normal file
309
Controller/ApiController.php
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,5 +15,7 @@ declare(strict_types=1);
|
|||
return ['Navigation' => [
|
||||
'Create' => 'Create',
|
||||
'List' => 'List',
|
||||
'Stats' => 'Stats',
|
||||
'TimeRecording' => 'Time Recording',
|
||||
'Today' => 'Today',
|
||||
]];
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]];
|
||||
|
|
|
|||
10
Theme/Backend/hr-stats.tpl.php
Normal file
10
Theme/Backend/hr-stats.tpl.php
Normal 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
|
||||
|
|
@ -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>
|
||||
43
Theme/Backend/private-session.tpl.php
Normal file
43
Theme/Backend/private-session.tpl.php
Normal 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>
|
||||
Loading…
Reference in New Issue
Block a user