This commit is contained in:
Dennis Eichhorn 2024-01-02 23:34:17 +00:00
parent 5afd8bfdd7
commit f0e2ebe314
11 changed files with 365 additions and 56 deletions

View File

@ -60,6 +60,32 @@
}
}
},
"hr_staff_note": {
"name": "hr_staff_note",
"fields": {
"hr_staff_note_id": {
"name": "hr_staff_note_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"hr_staff_note_staff": {
"name": "hr_staff_note_staff",
"type": "INT",
"null": false,
"foreignTable": "hr_staff",
"foreignKey": "hr_staff_id"
},
"hr_staff_note_doc": {
"name": "hr_staff_note_doc",
"type": "INT",
"null": false,
"foreignTable": "editor_doc",
"foreignKey": "editor_doc_id"
}
}
},
"hr_staff_history": {
"name": "hr_staff_history",
"fields": {

View File

@ -23,12 +23,18 @@ use Modules\HumanResourceManagement\Models\EmployeeHistoryMapper;
use Modules\HumanResourceManagement\Models\EmployeeMapper;
use Modules\HumanResourceManagement\Models\EmployeeWorkHistory;
use Modules\HumanResourceManagement\Models\EmployeeWorkHistoryMapper;
use Modules\HumanResourceManagement\Models\PermissionCategory;
use Modules\Media\Models\CollectionMapper;
use Modules\Media\Models\MediaMapper;
use Modules\Media\Models\PathSettings;
use Modules\Organization\Models\NullDepartment;
use Modules\Organization\Models\NullPosition;
use Modules\Organization\Models\NullUnit;
use Modules\Profile\Models\Profile;
use Modules\Profile\Models\ProfileMapper;
use phpOMS\Account\PermissionType;
use phpOMS\Message\Http\RequestStatusCode;
use phpOMS\Message\NotificationLevel;
use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use phpOMS\Stdlib\Base\AddressType;
@ -432,4 +438,264 @@ final class ApiController extends Controller
return $history;
}
/**
* Api method to create a bill
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param array $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiMediaAddToEmployee(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
{
if (!empty($val = $this->validateMediaAddToEmployee($request))) {
$response->header->status = RequestStatusCode::R_400;
$this->createInvalidAddResponse($request, $response, $val);
return;
}
/** @var \Modules\HumanResourceManagement\Models\Employee $employee */
$employee = EmployeeMapper::get()->where('id', (int) $request->getData('employee'))->execute();
$path = $this->createEmployeeDir($employee);
$uploaded = [];
if (!empty($uploadedFiles = $request->files)) {
$uploaded = $this->app->moduleManager->get('Media')->uploadFiles(
names: [],
fileNames: [],
files: $uploadedFiles,
account: $request->header->account,
basePath: __DIR__ . '/../../../Modules/Media/Files' . $path,
virtualPath: $path,
pathSettings: PathSettings::FILE_PATH,
hasAccountRelation: false,
readContent: $request->getDataBool('parse_content') ?? false
);
$collection = null;
foreach ($uploaded as $media) {
$this->createModelRelation(
$request->header->account,
$employee->id,
$media->id,
EmployeeMapper::class,
'files',
'',
$request->getOrigin()
);
if ($request->hasData('type')) {
$this->createModelRelation(
$request->header->account,
$media->id,
$request->getDataInt('type'),
MediaMapper::class,
'types',
'',
$request->getOrigin()
);
}
if ($collection === null) {
/** @var \Modules\Media\Models\Collection $collection */
$collection = MediaMapper::getParentCollection($path)->limit(1)->execute();
if ($collection->id === 0) {
$collection = $this->app->moduleManager->get('Media')->createRecursiveMediaCollection(
$path,
$request->header->account,
__DIR__ . '/../../../Modules/Media/Files' . $path,
);
}
}
$this->createModelRelation(
$request->header->account,
$collection->id,
$media->id,
CollectionMapper::class,
'sources',
'',
$request->getOrigin()
);
}
}
if (!empty($mediaFiles = $request->getDataJson('media'))) {
foreach ($mediaFiles as $media) {
$this->createModelRelation(
$request->header->account,
$employee->id,
(int) $media,
EmployeeMapper::class,
'files',
'',
$request->getOrigin()
);
}
}
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Media', 'Media added to employee.', [
'upload' => $uploaded,
'media' => $mediaFiles,
]);
}
/**
* Create media directory path
*
* @param Employee $employee Employee
*
* @return string
*
* @since 1.0.0
*/
private function createEmployeeDir(Employee $employee) : string
{
return '/Modules/HumanResourceManagement/Employee/'
. $this->app->unitId . '/'
. $employee->id;
}
/**
* Method to validate bill creation from request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateMediaAddToEmployee(RequestAbstract $request) : array
{
$val = [];
if (($val['media'] = (!$request->hasData('media') && empty($request->files)))
|| ($val['employee'] = !$request->hasData('employee'))
) {
return $val;
}
return [];
}
/**
* Api method to create notes
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param array $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiNoteCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
{
if (!empty($val = $this->validateNoteCreate($request))) {
$response->header->status = RequestStatusCode::R_400;
$this->createInvalidCreateResponse($request, $response, $val);
return;
}
$request->setData('virtualpath', '/Modules/HumanResourceManagement/Employee/' . $request->getData('id'), true);
$this->app->moduleManager->get('Editor', 'Api')->apiEditorCreate($request, $response, $data);
if ($response->header->status !== RequestStatusCode::R_200) {
return;
}
$responseData = $response->getDataArray($request->uri->__toString());
if (!\is_array($responseData)) {
return;
}
$model = $responseData['response'];
$this->createModelRelation($request->header->account, (int) $request->getData('id'), $model->id, EmployeeMapper::class, 'notes', '', $request->getOrigin());
}
/**
* Validate item note create request
*
* @param RequestAbstract $request Request
*
* @return array<string, bool>
*
* @since 1.0.0
*/
private function validateNoteCreate(RequestAbstract $request) : array
{
$val = [];
if (($val['id'] = !$request->hasData('id'))
) {
return $val;
}
return [];
}
/**
* Api method to update Note
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param array $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiNoteUpdate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
{
$accountId = $request->header->account;
if (!$this->app->accountManager->get($accountId)->hasPermission(
PermissionType::MODIFY, $this->app->unitId, $this->app->appId, self::NAME, PermissionCategory::EMPLOYEE_NOTE, $request->getDataInt('id'))
) {
$this->fillJsonResponse($request, $response, NotificationLevel::HIDDEN, '', '', []);
$response->header->status = RequestStatusCode::R_403;
return;
}
$this->app->moduleManager->get('Editor', 'Api')->apiEditorUpdate($request, $response, $data);
}
/**
* Api method to delete Note
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param array $data Generic data
*
* @return void
*
* @api
*
* @since 1.0.0
*/
public function apiNoteDelete(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
{
$accountId = $request->header->account;
if (!$this->app->accountManager->get($accountId)->hasPermission(
PermissionType::DELETE, $this->app->unitId, $this->app->appId, self::NAME, PermissionCategory::EMPLOYEE_NOTE, $request->getDataInt('id'))
) {
$this->fillJsonResponse($request, $response, NotificationLevel::HIDDEN, '', '', []);
$response->header->status = RequestStatusCode::R_403;
return;
}
$this->app->moduleManager->get('Editor', 'Api')->apiEditorDelete($request, $response, $data);
}
}

View File

@ -15,11 +15,13 @@ declare(strict_types=1);
namespace Modules\HumanResourceManagement\Controller;
use Modules\HumanResourceManagement\Models\EmployeeMapper;
use Modules\Media\Models\MediaMapper;
use Modules\Organization\Models\DepartmentMapper;
use phpOMS\Contract\RenderableInterface;
use phpOMS\DataStorage\Database\Query\OrderType;
use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use Modules\Profile\Models\SettingsEnum;
use phpOMS\Views\View;
/**
@ -34,7 +36,7 @@ use phpOMS\Views\View;
final class BackendController extends Controller
{
/**
* Routing end-point for application behaviour.
* Routing end-point for application behavior.
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
@ -54,15 +56,27 @@ final class BackendController extends Controller
$view->data['employees'] = EmployeeMapper::getAll()
->with('profile')
->with('profile/account')
->with('image')
->with('profile/image')
->with('companyHistory')
->with('companyHistory/unit')
->execute();
/** @var \Model\Setting $profileImage */
$profileImage = $this->app->appSettings->get(names: SettingsEnum::DEFAULT_PROFILE_IMAGE, module: 'Profile');
/** @var \Modules\Media\Models\Media $image */
$image = MediaMapper::get()
->where('id', (int) $profileImage->content)
->execute();
$view->data['defaultImage'] = $image;
return $view;
}
/**
* Routing end-point for application behaviour.
* Routing end-point for application behavior.
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
@ -86,7 +100,7 @@ final class BackendController extends Controller
}
/**
* Routing end-point for application behaviour.
* Routing end-point for application behavior.
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
@ -106,6 +120,10 @@ final class BackendController extends Controller
$employee = EmployeeMapper::get()
->with('profile')
->with('profile/account')
->with('image')
->with('notes')
->with('files')
->with('profile/image')
->with('companyHistory')
->with('companyHistory/unit')
->with('companyHistory/department')
@ -126,11 +144,14 @@ final class BackendController extends Controller
$view->data['employee'] = $employee;
$view->data['media-upload'] = new \Modules\Media\Theme\Backend\Components\Upload\BaseView($this->app->l11nManager, $request, $response);
$view->data['vehicle-notes'] = new \Modules\Editor\Theme\Backend\Components\Compound\BaseView($this->app->l11nManager, $request, $response);
return $view;
}
/**
* Routing end-point for application behaviour.
* Routing end-point for application behavior.
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
@ -153,7 +174,7 @@ final class BackendController extends Controller
}
/**
* Routing end-point for application behaviour.
* Routing end-point for application behavior.
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
@ -176,7 +197,7 @@ final class BackendController extends Controller
}
/**
* Routing end-point for application behaviour.
* Routing end-point for application behavior.
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response

View File

@ -332,4 +332,5 @@ class Employee implements \JsonSerializable
}
use \Modules\Media\Models\MediaListTrait;
use \Modules\Editor\Models\EditorDocListTrait;
}

View File

@ -14,6 +14,7 @@ declare(strict_types=1);
namespace Modules\HumanResourceManagement\Models;
use Modules\Editor\Models\EditorDocMapper;
use Modules\Media\Models\MediaMapper;
use Modules\Profile\Models\ProfileMapper;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
@ -101,6 +102,12 @@ final class EmployeeMapper extends DataMapperFactory
'self' => 'hr_staff_education_history_staff',
'external' => null,
],
'notes' => [
'mapper' => EditorDocMapper::class, /* mapper of the related object */
'table' => 'hr_staff_note', /* table of the related object, null if no relation table is used (many->1) */
'external' => 'hr_staff_note_doc',
'self' => 'hr_staff_note_staff',
],
];
/**

View File

@ -17,7 +17,7 @@ namespace Modules\HumanResourceManagement\Models;
use phpOMS\Stdlib\Base\Enum;
/**
* Permision state enum.
* Permission category enum.
*
* @package Modules\HumanResourceManagement\Models
* @license OMS License 2.0
@ -31,4 +31,6 @@ abstract class PermissionCategory extends Enum
public const DEPARTMENT = 2;
public const POSITION = 3;
public const EMPLOYEE_NOTE = 4;
}

View File

@ -15,7 +15,7 @@ declare(strict_types=1);
return ['HumanResourceManagement' => [
'Account' => 'Konto',
'Active' => 'Aktiv',
'Address' => '',
'Address' => 'Addresse',
'Birthday' => 'Fødselsdag',
'Clocking' => 'Clocking.',
'Contracts' => 'Kontrakter',
@ -40,12 +40,13 @@ return ['HumanResourceManagement' => [
'Position' => 'Position',
'ProfileImage' => 'Profilbillede.',
'Remarks' => 'Bemærkninger.',
'Salary' => '',
'Salary' => 'Geahlt',
'Notes' => 'Notizen',
'Shifts' => 'Skift',
'Staff' => 'Personale',
'Start' => 'Start',
'Status' => 'Status.',
'Title' => '',
'Title' => 'Titel',
'Unit' => 'Enhed',
'Vacation' => 'Ferie',
'Work' => 'Arbejde',

View File

@ -40,7 +40,8 @@ return ['HumanResourceManagement' => [
'Position' => 'Position',
'ProfileImage' => 'Profile image',
'Remarks' => 'Remarks',
'Salary' => '',
'Salary' => 'Salary',
'Notes' => 'Notes',
'Shifts' => 'Shifts',
'Staff' => 'Staff',
'Start' => 'Start',

View File

@ -35,11 +35,11 @@ echo $this->data['nav']->render();
<option value="<?= $this->printHtml(EmployeeActivityStatus::INACTIVE); ?>"><?= $this->getHtml('Inactive'); ?>
</select>
<tr><td><label for="iName1"><?= $this->getHtml('Name1', 'Admin'); ?></label>
<tr><td><input id="iName1" name="name1" type="text" placeholder="&#xf007; Donald" required>
<tr><td><input id="iName1" name="name1" type="text" placeholder="Donald" required>
<tr><td><label for="iName2"><?= $this->getHtml('Name2', 'Admin'); ?></label>
<tr><td><input id="iName2" name="name2" type="text" placeholder="&#xf007; Fauntleroy">
<tr><td><input id="iName2" name="name2" type="text" placeholder="Fauntleroy">
<tr><td><label for="iName3"><?= $this->getHtml('Name3', 'Admin'); ?></label>
<tr><td><input id="iName3" name="name3" type="text" placeholder="&#xf007; Duck">
<tr><td><input id="iName3" name="name3" type="text" placeholder="Duck">
<tr><td><label for="iAddress"><?= $this->getHtml('Address', 'Profile'); ?></label>
<tr><td><input type="text" id="iAddress" name="address">
<tr><td><label for="iZip"><?= $this->getHtml('Zip', 'Profile'); ?></label>
@ -53,7 +53,7 @@ echo $this->data['nav']->render();
<tr><td><label for="iPhone"><?= $this->getHtml('Phone', 'Profile'); ?></label>
<tr><td><input id="iPhone" name="phone" type="text">
<tr><td><label for="iEmail"><?= $this->getHtml('Email', 'Admin'); ?></label>
<tr><td><input id="iEmail" name="email" type="email" placeholder="&#xf0e0; d.duck@duckburg.com">
<tr><td><input id="iEmail" name="email" type="email" placeholder="d.duck@duckburg.com">
<tr><td><label for="iUnit"><?= $this->getHtml('Unit', 'Organization'); ?></label>
<tr><td><input id="iUnit" name="unit" type="text">
<tr><td><label for="iPosition"><?= $this->getHtml('Position', 'Organization'); ?></label>

View File

@ -42,10 +42,12 @@ echo $this->data['nav']->render(); ?>
<?php $c = 0; foreach ($employees as $key => $value) : ++$c;
$url = UriFactory::build('{/base}/humanresource/staff/profile?{?}&id=' . $value->id); ?>
<tr tabindex="0" data-href="<?= $url; ?>">
<td><a href="<?= $url; ?>"><img alt="<?= $this->getHtml('IMG_alt_staff'); ?>" width="30" loading="lazy" class="profile-image"
src="<?= $value->profile->image->id === 0 ?
UriFactory::build('Web/Backend/img/user_default_' . \mt_rand(1, 6) .'.png') :
UriFactory::build('{/base}/' . $value->profile->image->getPath()); ?>"></a>
<td><a href="<?= $url; ?>"><img alt="<?= $this->getHtml('IMG_alt_user'); ?>" width="30" loading="lazy" class="profile-image"
src="<?= $value->image->id === 0
? ($value->profile->image->id === 0
? UriFactory::build($this->getData('defaultImage')->getPath())
: UriFactory::build($value->profile->image->getPath()))
: UriFactory::build($value->image->getPath()); ?>"></a>
<td data-label="<?= $this->getHtml('ID', '0', '0'); ?>"><a href="<?= $url; ?>"><?= $value->id; ?></a>
<td data-label="<?= $this->getHtml('Name'); ?>"><a href="<?= $url; ?>"><?= $this->printHtml(
\sprintf('%3$s %2$s %1$s', $value->profile->account->name1, $value->profile->account->name2, $value->profile->account->name3)

View File

@ -25,15 +25,15 @@ echo $this->data['nav']->render(); ?>
<div class="tabview tab-2">
<div class="box">
<ul class="tab-links">
<li><label for="c-tab-1"><?= $this->getHtml('General'); ?></label></li>
<li><label for="c-tab-2"><?= $this->getHtml('Clocking'); ?></label></li>
<li><label for="c-tab-3"><?= $this->getHtml('Documents'); ?></label></li>
<li><label for="c-tab-4"><?= $this->getHtml('Contracts'); ?></label></li>
<li><label for="c-tab-5"><?= $this->getHtml('Remarks'); ?></label></li>
<li><label for="c-tab-6"><?= $this->getHtml('Evaluations'); ?></label></li>
<li><label for="c-tab-7"><?= $this->getHtml('Salary'); ?></label></li>
<li><label for="c-tab-8"><?= $this->getHtml('Education'); ?></label></li>
<li><label for="c-tab-9"><?= $this->getHtml('Work'); ?></label></li>
<li><label for="c-tab-1"><?= $this->getHtml('General'); ?></label>
<li><label for="c-tab-2"><?= $this->getHtml('Clocking'); ?></label>
<li><label for="c-tab-3"><?= $this->getHtml('Documents'); ?></label>
<li><label for="c-tab-4"><?= $this->getHtml('Contracts'); ?></label>
<li><label for="c-tab-5"><?= $this->getHtml('Notes'); ?></label>
<li><label for="c-tab-6"><?= $this->getHtml('Evaluations'); ?></label>
<li><label for="c-tab-7"><?= $this->getHtml('Salary'); ?></label>
<li><label for="c-tab-8"><?= $this->getHtml('Education'); ?></label>
<li><label for="c-tab-9"><?= $this->getHtml('Work'); ?></label>
</ul>
</div>
<div class="tab-content">
@ -44,17 +44,16 @@ echo $this->data['nav']->render(); ?>
<section itemscope itemtype="http://schema.org/Person" class="portlet">
<div class="portlet-head"><span itemprop="familyName"><?= $this->printHtml($employee->profile->account->name2); ?></span>, <span itemprop="givenName"><?= $this->printHtml($employee->profile->account->name1); ?></span></div>
<div class="portlet-body">
<!-- @formatter:off -->
<span class="rf">
<img class="m-profile rf"
alt="<?= $this->getHtml('ProfileImage'); ?>"
itemprop="logo" loading="lazy"
src="<?=
$employee->image->id === 0 ?
($employee->profile->image->id === 0 ?
UriFactory::build('Web/Backend/img/user_default_' . \mt_rand(1, 6) .'.png') :
UriFactory::build('{/base}/' . $employee->profile->image->getPath())) :
UriFactory::build('{/base}/' . $employee->image->getPath()); ?>"
$employee->image->id === 0
? ($employee->profile->image->id === 0
? UriFactory::build($this->getData('defaultImage')->getPath())
: UriFactory::build($employee->profile->image->getPath()))
: UriFactory::build($employee->image->getPath()); ?>"
>
</span>
<table class="list">
@ -98,7 +97,6 @@ echo $this->data['nav']->render(); ?>
<th><?= $this->getHtml('Status'); ?>
<td><span class="tag green"><?= $recentHistory->id > 0 ? $this->getHtml('Active') : $this->getHtml('Inactive'); ?></span>
</table>
<!-- @formatter:on -->
</div>
</section>
</div>
@ -108,7 +106,7 @@ echo $this->data['nav']->render(); ?>
<div class="col-xs-12">
<div class="portlet x-overflow">
<div class="portlet-head"><?= $this->getHtml('History'); ?><i class="g-icon download btn end-xs">download</i></div>
<table id="historyList" class="default">
<table id="historyList" class="default sticky">
<thead>
<td><?= $this->getHtml('Start'); ?><i class="sort-asc g-icon">expand_less</i><i class="sort-desc g-icon">expand_more</i>
<td><?= $this->getHtml('End'); ?><i class="sort-asc g-icon">expand_less</i><i class="sort-desc g-icon">expand_more</i>
@ -142,15 +140,7 @@ echo $this->data['nav']->render(); ?>
</div>
<input type="radio" id="c-tab-3" name="tabular-2"<?= $this->request->uri->fragment === 'c-tab-3' ? ' checked' : ''; ?>>
<div class="tab">
<div class="row">
<div class="col-xs-12">
<section class="portlet">
<div class="portlet-head"><?= $this->getHtml('Documents'); ?></div>
<div class="portlet-body">
</div>
</section>
</div>
</div>
<?= $this->data['media-upload']->render('employee-file', 'files', '', $employee->files); ?>
</div>
<input type="radio" id="c-tab-4" name="tabular-2"<?= $this->request->uri->fragment === 'c-tab-4' ? ' checked' : ''; ?>>
<div class="tab">
@ -166,15 +156,7 @@ echo $this->data['nav']->render(); ?>
</div>
<input type="radio" id="c-tab-5" name="tabular-2"<?= $this->request->uri->fragment === 'c-tab-5' ? ' checked' : ''; ?>>
<div class="tab">
<div class="row">
<div class="col-xs-12">
<section class="portlet">
<div class="portlet-head"><?= $this->getHtml('Remarks'); ?></div>
<div class="portlet-body">
</div>
</section>
</div>
</div>
<?= $this->data['employee-notes']->render('employee-notes', '', $employee->notes); ?>
</div>
<input type="radio" id="c-tab-6" name="tabular-2"<?= $this->request->uri->fragment === 'c-tab-6' ? ' checked' : ''; ?>>
<div class="tab">
@ -199,7 +181,7 @@ echo $this->data['nav']->render(); ?>
<div class="col-xs-12">
<div class="portlet x-overflow">
<div class="portlet-head"><?= $this->getHtml('Education'); ?><i class="g-icon download btn end-xs">download</i></div>
<table id="historyList" class="default">
<table id="historyList" class="default sticky">
<thead>
<td><?= $this->getHtml('Start'); ?><i class="sort-asc g-icon">expand_less</i><i class="sort-desc g-icon">expand_more</i>
<td><?= $this->getHtml('End'); ?><i class="sort-asc g-icon">expand_less</i><i class="sort-desc g-icon">expand_more</i>
@ -223,7 +205,7 @@ echo $this->data['nav']->render(); ?>
<div class="col-xs-12">
<div class="portlet x-overflow">
<div class="portlet-head"><?= $this->getHtml('Work'); ?><i class="g-icon download btn end-xs">download</i></div>
<table id="historyList" class="default">
<table id="historyList" class="default sticky">
<thead>
<td><?= $this->getHtml('Start'); ?><i class="sort-asc g-icon">expand_less</i><i class="sort-desc g-icon">expand_more</i>
<td><?= $this->getHtml('End'); ?><i class="sort-asc g-icon">expand_less</i><i class="sort-desc g-icon">expand_more</i>