Initial xentral_oss_20.3.c9ffacf

This commit is contained in:
Alex
2021-05-21 08:49:41 +02:00
parent 5406e4a551
commit 34e5ac43d9
18884 changed files with 2109867 additions and 0 deletions
@@ -0,0 +1,151 @@
<?php
namespace Xentral\Modules\TimeManagement;
use ApplicationCore;
use Mitarbeiterzeiterfassung;
use Xentral\Core\DependencyInjection\ContainerInterface;
use Xentral\Modules\TimeManagement\Service\GroupGateway;
use Xentral\Modules\TimeManagement\Service\HolidayGateway;
use Xentral\Modules\TimeManagement\Service\TimeManagementHistoryService;
use Xentral\Modules\TimeManagement\Service\TimeManagementSettingGateway;
use Xentral\Modules\TimeManagement\Service\TimeManagementTargetHourGateway;
use Xentral\Modules\TimeManagement\Service\TimeManagementTargetHourService;
use Xentral\Modules\TimeManagement\Wrapper\TimeManagementMailerWrapper;
use Xentral\Modules\TimeManagement\Wrapper\TimeManagementTargetHourWrapper;
final class Bootstrap
{
/**
* @return array
*/
public static function registerServices(): array
{
return [
'TimeManagementModule' => 'onInitTimeManagementModule',
'TimeManagementMailer' => 'onInitTimeManagementMailer',
];
}
/**
* @param ContainerInterface $container
*
* @return TimeManagementModule
*/
public static function onInitTimeManagementModule(ContainerInterface $container): TimeManagementModule
{
return new TimeManagementModule(
self::onInitTimeManagementTargetHourGateway($container),
self::onInitTimeManagementTargetHourService($container),
self::onInitTimeManagementSettingGateway($container),
self::onInitHolidayGateway($container),
self::onInitGroupGateway($container),
self::onInitTimeManagementTargetHourWrapper($container),
self::onInitTimeManagementHistoryService($container)
);
}
/**
* @param ContainerInterface $container
*
* @return TimeManagementTargetHourService
*/
private static function onInitTimeManagementTargetHourService(ContainerInterface $container
): TimeManagementTargetHourService {
return new TimeManagementTargetHourService(
$container->get('Database')
);
}
/**
* @param ContainerInterface $container
*
* @return TimeManagementTargetHourGateway
*/
private static function onInitTimeManagementTargetHourGateway(ContainerInterface $container
): TimeManagementTargetHourGateway {
return new TimeManagementTargetHourGateway(
$container->get('Database')
);
}
/**
* @param ContainerInterface $container
*
* @return TimeManagementSettingGateway
*/
private static function onInitTimeManagementSettingGateway(ContainerInterface $container
): TimeManagementSettingGateway {
return new TimeManagementSettingGateway(
$container->get('Database')
);
}
/**
* @param ContainerInterface $container
*
* @return HolidayGateway
*/
private static function onInitHolidayGateway(ContainerInterface $container): HolidayGateway
{
return new HolidayGateway(
$container->get('Database')
);
}
/**
* @param ContainerInterface $container
*
* @return GroupGateway
*/
private static function onInitGroupGateway(ContainerInterface $container): GroupGateway
{
return new GroupGateway(
$container->get('Database')
);
}
/**
* @param ContainerInterface $container
*
* @return TimeManagementTargetHourWrapper
*/
private static function onInitTimeManagementTargetHourWrapper(ContainerInterface $container
): TimeManagementTargetHourWrapper {
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
/** @var Mitarbeiterzeiterfassung $timeRecordingModule */
$timeRecordingModule = $app->erp->LoadModul('mitarbeiterzeiterfassung');
return new TimeManagementTargetHourWrapper($timeRecordingModule);
}
/**
* @param ContainerInterface $container
*
* @return TimeManagementHistoryService
*/
private static function onInitTimeManagementHistoryService(ContainerInterface $container
): TimeManagementHistoryService {
return new TimeManagementHistoryService(
$container->get('Database')
);
}
/**
* @param ContainerInterface $container
*
* @return TimeManagementMailerWrapper
*/
public function onInitTimeManagementMailer(ContainerInterface $container): TimeManagementMailerWrapper
{
/** @var ApplicationCore $app */
$app = $container->get('LegacyApplication');
return new TimeManagementMailerWrapper(
$app->erp
);
}
}
@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Data;
use DateTimeImmutable;
use Exception;
use DateTimeInterface;
use Xentral\Modules\TimeManagement\Exception\InvalidDateFormatException;
final class CalendarData
{
/** @var int $month */
private $month = 0;
/** @var DateTimeInterface $date */
private $date;
/** @var int $addressId */
private $addressId = 0;
/** @var string $employeeName */
private $employeeName = '';
/** @var string $type */
private $type = '';
/** @var bool $isHalf */
private $isHalf = false;
private function __construct()
{
}
/**
* @param array $data
*
* @return CalendarData
*
* @throws InvalidDateFormatException
*/
public static function fromDbState(array $data): CalendarData
{
$calendarData = new CalendarData();
$calendarData->month = (int)$data['month'];
try {
$calendarData->date = new DateTimeImmutable($data['date']);
} catch (Exception $e) {
throw new InvalidDateFormatException('Could not convert date: ' . $data['date']);
}
$calendarData->addressId = (int)$data['address_id'];
$calendarData->employeeName = (string)$data['name'];
$calendarData->type = (string)$data['type'];
$calendarData->isHalf = (bool)$data['is_half'];
return $calendarData;
}
/**
* @return int
*/
public function getMonth(): int
{
return $this->month;
}
/**
* @return DateTimeInterface
*/
public function getDate(): DateTimeInterface
{
return $this->date;
}
/**
* @return int
*/
public function getAddressId(): int
{
return $this->addressId;
}
/**
* @return string
*/
public function getEmployeeName(): string
{
return $this->employeeName;
}
/**
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* @return bool
*/
public function isHalf(): bool
{
return $this->isHalf;
}
}
@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Data;
use JsonSerializable;
final class DayInfoData implements JsonSerializable
{
/** @var string $type */
private $type = '';
/** @var int $workMinutes */
private $workMinutes = 0;
/** @var int $vacationMinutes */
private $vacationMinutes = 0;
/** @var string $internalComment */
private $internalComment = '';
private function __construct()
{
}
/**
* @param $data
*
* @return DayInfoData
*/
public static function fromDbState($data): DayInfoData
{
$dayInfoData = new DayInfoData();
if (isset($data['type'])) {
$dayInfoData->type = (string)$data['type'];
}
if (isset($data['workminutes'])) {
$dayInfoData->workMinutes = (int)$data['workminutes'];
}
if (isset($data['vacationminutes'])) {
$dayInfoData->vacationMinutes = (int)$data['vacationminutes'];
}
if (isset($data['internal_comment'])) {
$dayInfoData->internalComment = (string)$data['internal_comment'];
}
return $dayInfoData;
}
/**
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* @return int
*/
public function getWorkMinutes(): int
{
return $this->workMinutes;
}
/**
* @return int
*/
public function getVacationMinutes(): int
{
return $this->vacationMinutes;
}
/**
* @return string
*/
public function getInternalComment(): string
{
return $this->internalComment;
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return [
'type' => $this->type,
'workminutes' => $this->workMinutes,
'vacationminutes' => $this->vacationMinutes,
'internal_comment' => $this->internalComment,
];
}
}
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Data;
use DateTimeImmutable;
use DateTimeInterface;
use Exception;
use Xentral\Modules\TimeManagement\Exception\InvalidDateFormatException;
final class HolidayData
{
/** @var string $name */
private $name = 'Unknown';
/** @var DateTimeInterface $date */
private $date;
private function __construct()
{
}
/**
* @param array $data
*
* @throws InvalidDateFormatException
*
* @return HolidayData
*
*/
public static function fromDbState(array $data): HolidayData
{
$holidayData = new HolidayData();
$holidayData->name = $data['name'];
try {
$holidayData->date = new DateTimeImmutable($data['date']);
} catch (Exception $e) {
throw new InvalidDateFormatException('Could not convert date: ' . $data['date']);
}
return $holidayData;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return DateTimeInterface
*/
public function getDate(): DateTimeInterface
{
return $this->date;
}
}
@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Data;
use DateTimeInterface;
use DateTimeImmutable;
use Exception;
use JsonSerializable;
use Xentral\Modules\TimeManagement\Exception\InvalidDateFormatException;
final class RequestInfoData implements JsonSerializable
{
/** @var int $employeeId */
private $employeeId = 0;
/** @var string $employeeNumber */
private $employeeNumber = '';
/** @var string $employeeName */
private $employeeName = '';
/** @var DateTimeInterface $minDate */
private $minDate;
/** @var DateTimeInterface $maxDate */
private $maxDate;
/** @var int $amount */
private $amount = 0;
/** @var string $comment */
private $comment = '';
/** @var string $type */
private $type = '';
/** @var string $internalComment */
private $internalComment = '';
private function __construct()
{
}
/**
* @return int
*/
public function getEmployeeId(): int
{
return $this->employeeId;
}
/**
* @return string
*/
public function getEmployeeNumber(): string
{
return $this->employeeNumber;
}
/**
* @return string
*/
public function getEmployeeName(): string
{
return $this->employeeName;
}
/**
* @return DateTimeInterface
*/
public function getMinDate(): DateTimeInterface
{
return $this->minDate;
}
/**
* @return DateTimeInterface
*/
public function getMaxDate(): DateTimeInterface
{
return $this->maxDate;
}
/**
* @return int
*/
public function getAmount(): int
{
return $this->amount;
}
/**
* @return string
*/
public function getComment(): string
{
return $this->comment;
}
/**
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* @return string
*/
public function getInternalComment(): string
{
return $this->internalComment;
}
/**
* @param array $data
*
* @throws InvalidDateFormatException
*
* @return RequestInfoData
*/
public static function fromDbState(array $data): RequestInfoData
{
$requestInfoData = new RequestInfoData();
$requestInfoData->employeeId = (int)$data['employee_id'];
$requestInfoData->employeeNumber = (string)$data['employee_number'];
$requestInfoData->employeeName = (string)$data['employee_name'];
try {
$requestInfoData->minDate = new DateTimeImmutable($data['min_date']);
} catch (Exception $e) {
throw new InvalidDateFormatException('Could not convert date: ' . $data['date']);
}
try {
$requestInfoData->maxDate = new DateTimeImmutable($data['max_date']);
} catch (Exception $e) {
throw new InvalidDateFormatException('Could not convert date: ' . $data['date']);
}
$requestInfoData->amount = (int)$data['amount'];
$requestInfoData->comment = (string)$data['comment'];
$requestInfoData->type = (string)$data['type'];
$requestInfoData->internalComment = (string)$data['internal_comment'];
return $requestInfoData;
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return [
'employee_id' => $this->employeeId,
'employee_number' => $this->employeeNumber,
'employee_name' => $this->employeeName,
'min_date' => empty($this->minDate) ? '0000-00-00' : $this->minDate->format('Y-m-d'),
'max_date' => empty($this->maxDate) ? '0000-00-00' : $this->maxDate->format('Y-m-d'),
'amount' => $this->amount,
'comment' => $this->comment,
'type' => $this->type,
'internal_comment' => $this->internalComment,
];
}
}
@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Data;
final class WorkDayData
{
/** @var bool $isMondayWorkDay */
private $isMondayWorkDay = false;
/** @var bool $isTuesdayWorkDay */
private $isTuesdayWorkDay = false;
/** @var bool $isWednesdayWorkDay */
private $isWednesdayWorkDay = false;
/** @var bool $isThursdayWorkDay */
private $isThursdayWorkDay = false;
/** @var bool $isFridayWorkDay */
private $isFridayWorkDay = false;
/** @var bool $isSaturdayWorkDay */
private $isSaturdayWorkDay = false;
/** @var bool $isSundayWorkDay */
private $isSundayWorkDay = false;
private function __construct()
{
}
public static function fromDbState(array $data): WorkDayData
{
$workDayData = new WorkDayData();
$workDayData->isMondayWorkDay = !empty($data['is_monday_workday']);
$workDayData->isTuesdayWorkDay = !empty($data['is_tuesday_workday']);
$workDayData->isWednesdayWorkDay = !empty($data['is_wednesday_workday']);
$workDayData->isThursdayWorkDay = !empty($data['is_thursday_workday']);
$workDayData->isFridayWorkDay = !empty($data['is_friday_workday']);
$workDayData->isSaturdayWorkDay = !empty($data['is_saturday_workday']);
$workDayData->isSundayWorkDay = !empty($data['is_sunday_workday']);
return $workDayData;
}
/**
* @return bool
*/
public function isMondayWorkDay(): bool
{
return $this->isMondayWorkDay;
}
/**
* @return bool
*/
public function isTuesdayWorkDay(): bool
{
return $this->isTuesdayWorkDay;
}
/**
* @return bool
*/
public function isWednesdayWorkDay(): bool
{
return $this->isWednesdayWorkDay;
}
/**
* @return bool
*/
public function isThursdayWorkDay(): bool
{
return $this->isThursdayWorkDay;
}
/**
* @return bool
*/
public function isFridayWorkDay(): bool
{
return $this->isFridayWorkDay;
}
/**
* @return bool
*/
public function isSaturdayWorkDay(): bool
{
return $this->isSaturdayWorkDay;
}
/**
* @return bool
*/
public function isSundayWorkDay(): bool
{
return $this->isSundayWorkDay;
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Exception;
use RuntimeException as SplRuntimeException;
final class EmailAccountNotFoundException extends SplRuntimeException implements TimeManagementExceptionInterface
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Exception;
use RuntimeException as SplRuntimeException;
final class EmailNotFoundException extends SplRuntimeException implements TimeManagementExceptionInterface
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Exception;
use RuntimeException as SplRuntimeException;
final class EmailNotSentException extends SplRuntimeException implements TimeManagementExceptionInterface
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Exception;
use InvalidArgumentException as SplInvalidArgumentException;
final class InvalidArgumentException extends SplInvalidArgumentException implements TimeManagementExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Exception;
final class InvalidDateFormatException extends \InvalidArgumentException implements TimeManagementExceptionInterface
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Exception;
use RuntimeException as SplRuntimeException;
final class InvalidQueryException extends SplRuntimeException implements TimeManagementExceptionInterface
{
}
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Exception;
final class InvalidRequestTokenException extends \InvalidArgumentException implements TimeManagementExceptionInterface
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Exception;
use RuntimeException as SplRuntimeException;
final class PatternMissingException extends SplRuntimeException implements TimeManagementExceptionInterface
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Exception;
use RuntimeException as SplRuntimeException;
final class SupervisorNotFoundException extends SplRuntimeException implements TimeManagementExceptionInterface
{
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Exception;
use Xentral\Core\Exception\ModuleExceptionInterface;
interface TimeManagementExceptionInterface extends ModuleExceptionInterface
{
}
+34
View File
@@ -0,0 +1,34 @@
# Time-Management-Module
## Description
The time-management module handles all vacation and illness requests off the employees.
It is based (and replaces in certain places) the older module 'Mitarbeiterzeiterfassung'.
### Create new instance
```php
/** @var \Xentral\Modules\TimeManagement\TimeManagementModule $timeManagement */
$timeManagement = $container->get('TimeManagementModule');
```
A hook is provided in mitarbeiterzeiterfassung.php to intervene in the process of changing the state of a day:
```php
$this->app->erp->RunHook(
'timemanagement_after_day_status_change',
6,
$addressId,
$fromDate,
$tillDate,
$halfday,
$statusOldType,
$statusWishType
);
```
## Open issues
- half days: It is not possible to differentiate between morning and afternoon
- the module is designed for vacation and illnesses. It does not handle unpaid vacation or absent days like the old module
- it is not designed for shiftworking over more than one day
- if someone only works half-days he has to take a whole day of vacation. This seems not to be correct and must be changed in future.
@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\TimeManagement\Exception\SupervisorNotFoundException;
final class GroupGateway
{
/** @var Database $db */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param int $addressId
*
* @return array
*/
public function findGroupsByAddressId(int $addressId): array
{
$sql =
'SELECT DISTINCT
g.id AS `group_id`,
g.name AS `group_name`
FROM `gruppen` AS `g`
INNER JOIN `adresse_rolle` AS `ar` ON ar.parameter = g.id AND ar.subjekt = \'Mitglied\'
WHERE ar.adresse = :address_id
AND (ar.bis = \'0000-00-00\' OR ar.bis > CURDATE())';
return $this->db->fetchAll($sql, ['address_id' => $addressId]);
}
/**
* @param int $addressId
* @param int $groupId
*
* @return bool
*/
public function isAddressInGroup(int $addressId, int $groupId): bool
{
$sql =
'SELECT
g.id AS `group_id`,
g.name AS `group_name`
FROM `gruppen` AS `g`
INNER JOIN `adresse_rolle` AS `ar` ON ar.parameter = g.id AND ar.subjekt = \'Mitglied\'
WHERE ar.adresse = :address_id
AND ar.parameter = :group_id
AND (ar.bis = \'0000-00-00\' OR ar.bis > CURDATE())';
$result = $this->db->fetchAll($sql, ['address_id' => $addressId, 'group_id' => $groupId]);
return !empty($result);
}
/**
* @return array
*/
public function findAllActiveGroupsWithMembers(): array
{
$sql =
'SELECT DISTINCT
g.id AS `group_id`,
g.name AS `group_name`
FROM `gruppen` AS `g`
INNER JOIN `adresse_rolle` AS `ar` ON ar.parameter = g.id AND ar.subjekt = \'Mitglied\'
WHERE (ar.bis = \'0000-00-00\' OR ar.bis > CURDATE())';
return $this->db->fetchAll($sql);
}
/**
* @param int $employeeAddressId
* @param int $groupId
*
* @throws SupervisorNotFoundException
*
* @return int[]
*/
public function getSupervisorAddressIds(int $employeeAddressId, int $groupId = 0): array
{
$bindValues = [];
$sql =
'SELECT a.id AS `id`
FROM `userrights` AS `ur`
INNER JOIN `user` AS `u` ON u.id = ur.user
INNER JOIN `adresse` AS `a` ON a.id = u.adresse
INNER JOIN `adresse_rolle` AS `ar_supervisor` ON ar_supervisor.adresse = a.id
WHERE ur.action = \'timemanagementhandle\'
AND ar_supervisor.subjekt = \'Mitglied\'
AND ar_supervisor.objekt = \'Gruppe\'
';
if ($groupId == 0) {
$sql .= 'AND ar_supervisor.parameter IN (
SELECT ar_employee.parameter
FROM `adresse_rolle` AS `ar_employee`
WHERE ar_employee.adresse = :employee_address_id
AND ar_employee.subjekt = \'Mitglied\'
AND ar_employee.objekt = \'Gruppe\'
)';
$bindValues['employee_address_id'] = $employeeAddressId;
} else {
$sql .= 'AND ar_supervisor.parameter = :group_id';
$bindValues['group_id'] = $groupId;
}
$superVisorAddressIds = $this->db->fetchAll($sql, $bindValues);
//superprivilege
if (empty($superVisorAddressIds)) {
$superVisorAddressIds = $this->findSupervisorAddressIdsBySuperPrivilege();
}
//admin
if (empty($superVisorAddressIds)) {
$superVisorAddressIds = $this->findSupervisorAddressIdByAdminRight();
}
$return = [];
if (!empty($superVisorAddressIds)) {
foreach ($superVisorAddressIds as $superVisorAddressId) {
$return[] = $superVisorAddressId['id'];
}
} else {
throw new SupervisorNotFoundException('No supervisor found for ' . $employeeAddressId);
}
return $return;
}
/**
* @return array
*/
private function findSupervisorAddressIdsBySuperPrivilege()
{
$sql =
'SELECT a.id AS `id`
FROM `userrights` AS `ur`
INNER JOIN `user` AS `u` ON u.id = ur.user
INNER JOIN `adresse` AS `a` ON a.id = u.adresse
WHERE ur.action = \'timemanagementsuperprivilege\'
ORDER BY a.id DESC';
return $this->db->fetchAll($sql);
}
/**
* @return array
*/
private function findSupervisorAddressIdByAdminRight()
{
$sql =
'SELECT u.adresse AS `id`
FROM `user` AS `u`
WHERE u.type = \'admin\'
ORDER BY u.adresse DESC';
return $this->db->fetchAll($sql);
}
}
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\TimeManagement\Data\HolidayData;
use Xentral\Modules\TimeManagement\Exception\InvalidDateFormatException;
final class HolidayGateway
{
/** @var Database $db */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param $year
*
* @throws InvalidDateFormatException
*
* @return array|HolidayData[]
*/
public function findHolidayDataByYear(int $year): array
{
$sql =
'SELECT
af.bezeichnung as `name`,
af.datum as `date`
FROM `arbeitsfreietage` AS `af`
WHERE af.datum >= :first_date_of_year
AND af.typ = \'feiertag\'
ORDER BY af.datum';
$holidays = $this->db->fetchAll($sql, ['first_date_of_year' => $year . '-01-01']);
$holidayData = [];
if (!empty($holidays)) {
foreach ($holidays as $holiday) {
$holidayData[] = HolidayData::fromDbState($holiday);
}
}
return $holidayData;
}
}
@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Service;
use DateTimeInterface;
use Xentral\Components\Database\Database;
use Xentral\Modules\TimeManagement\Exception\InvalidArgumentException;
use Xentral\Modules\TimeManagement\Exception\InvalidQueryException;
final class TimeManagementHistoryService
{
/** @var Database $db */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param int $employeeAddressId
* @param int $supervisorAddressId
* @param string $oldType
* @param string $newType
* @param string $requestToken
* @param DateTimeInterface $from
* @param DateTimeInterface $till
* @param string $comment
*
* @throws InvalidArgumentException
* @throws InvalidQueryException
*/
public function saveActivity(
int $employeeAddressId,
int $supervisorAddressId,
string $oldType,
string $newType,
string $requestToken,
DateTimeInterface $from,
DateTimeInterface $till,
string $comment
): void {
if ($employeeAddressId === 0 && $supervisorAddressId === 0) {
throw new InvalidArgumentException('No addresses given.');
}
if (empty($oldType) && empty($newType)) {
throw new InvalidArgumentException('No types given.');
}
$sql =
'INSERT INTO `timemanagement_history` (
`employee_address_id`,
`supervisor_address_id`,
`old_day_type`,
`new_day_type`,
`request_token`,
`from`,
`till`,
`comment`
) VALUES (
:employee_address_id,
:supervisor_address_id,
:old_day_type,
:new_day_type,
:request_token,
:from,
:till,
:comment
)';
$arguments = [
'employee_address_id' => $employeeAddressId,
'supervisor_address_id' => $supervisorAddressId,
'old_day_type' => $oldType,
'new_day_type' => $newType,
'request_token' => $requestToken,
'from' => $from->format('Y-m-d'),
'till' => $till->format('Y-m-d'),
'comment' => $comment,
];
$numAffected = (int)$this->db->fetchAffected($sql, $arguments);
if ($numAffected === 0) {
throw new InvalidQueryException(
'Time management history could not be updated. Arguments: ' . implode(', ', $arguments)
);
}
}
}
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Service;
use Xentral\Components\Database\Database;
use Xentral\Modules\TimeManagement\Data\WorkDayData;
use Xentral\Modules\TimeManagement\Exception\InvalidQueryException;
final class TimeManagementSettingGateway
{
/** @var Database $db */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param int $addressId
*
* @throws InvalidQueryException
*
* @return WorkDayData
*/
public function getWorkingDaysForAddress(int $addressId): WorkDayData
{
$sql =
'SELECT
e.vorlagemo > 0 AS `is_monday_workday`,
e.vorlagedi > 0 AS `is_tuesday_workday`,
e.vorlagemi > 0 AS `is_wednesday_workday`,
e.vorlagedo > 0 AS `is_thursday_workday`,
e.vorlagefr > 0 AS `is_friday_workday`,
e.vorlagesa > 0 AS `is_saturday_workday`,
e.vorlageso > 0 AS `is_sunday_workday`
FROM `mitarbeiterzeiterfassung_einstellungen` AS `e`
WHERE e.adresse = :address_id
ORDER BY e.id DESC
LIMIT 1';
$result = $this->db->fetchRow($sql, ['address_id' => $addressId]);
if (empty($result)) {
throw new InvalidQueryException('Address is not valid: ' . $addressId);
}
return WorkDayData::fromDbState($result);
}
}
@@ -0,0 +1,445 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Service;
use DateTimeImmutable;
use DateTimeInterface;
use Exception;
use Xentral\Components\Database\Database;
use Xentral\Modules\TimeManagement\Data\CalendarData;
use Xentral\Modules\TimeManagement\Data\DayInfoData;
use Xentral\Modules\TimeManagement\Data\RequestInfoData;
use Xentral\Modules\TimeManagement\Exception\InvalidDateFormatException;
use Xentral\Modules\TimeManagement\Exception\InvalidRequestTokenException;
final class TimeManagementTargetHourGateway
{
/** @var Database $db */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param int $year
* @param int $addressId
* @param int $groupId
*
* @throws InvalidDateFormatException
*
* @return array|CalendarData[]
*/
public function findAnonymisedVacationCalendarDataByYearAndAddressIdAndGroupId(
int $year,
int $addressId,
int $groupId
): array {
$sql =
'SELECT
MONTH(days.date) AS `month`,
days.date,
days.address_id,
days.name,
days.type,
IF(days.urlaubminuten > 0,true,false) AS `is_half`
FROM
(
SELECT
ms.datum AS `date`,
ms.adresse AS `address_id`,
a.name,
\'away\' AS `type`,
ms.urlaubminuten
FROM `mitarbeiterzeiterfassung_sollstunden` AS `ms`
INNER JOIN `adresse` AS `a` ON ms.adresse = a.id
WHERE (ms.kuerzel LIKE \'%U%\' OR ms.kuerzel LIKE \'%S%\')
AND ms.adresse != :address_id
AND year(ms.datum) = :year
AND ms.datum > CURDATE()
UNION
SELECT
ms.datum AS `date`,
ms.adresse AS `address_id`,
a.name,
(CASE
WHEN ms.kuerzel LIKE \'%U%\' THEN \'vacation\'
WHEN ms.kuerzel LIKE \'%R%\' THEN \'request-vacation\'
WHEN ms.kuerzel LIKE \'%L%\' THEN \'remove-vacation\'
WHEN ms.kuerzel LIKE \'%K%\' THEN \'sick\'
WHEN ms.kuerzel LIKE \'%S%\' THEN \'request-sick\'
WHEN ms.kuerzel LIKE \'%V%\' THEN \'remove-sick\'
WHEN ms.kuerzel LIKE \'%X%\' THEN \'absent\'
WHEN ms.kuerzel LIKE \'%N%\' THEN \'unpaid\'
END) AS `type`,
ms.urlaubminuten
FROM `mitarbeiterzeiterfassung_sollstunden` AS `ms`
INNER JOIN `adresse` AS `a` ON ms.adresse = a.id
WHERE ms.kuerzel != \'\' AND ms.kuerzel NOT LIKE \'%C%\' AND ms.kuerzel NOT LIKE \'%J%\'
AND ms.adresse = :address_id
AND year(ms.datum) = :year
) AS `days`
INNER JOIN(
SELECT DISTINCT
ar_groups.adresse
FROM `adresse_rolle` AS `ar_groups`
WHERE ar_groups.parameter = :group_id
AND ar_groups.subjekt = :subject
AND (ar_groups.bis = "0000-00-00" OR ar_groups.bis > CURDATE())
) AS `ar` ON ar.adresse = days.address_id
ORDER BY days.address_id, days.date';
$results = $this->db->fetchAll(
$sql,
[
'year' => $year,
'group_id' => $groupId,
'subject' => 'Mitglied',
'address_id' => $addressId,
]
);
$calendarDatas = [];
if (!empty($results)) {
foreach ($results as $result) {
$calendarDatas[] = CalendarData::fromDbState($result);
}
}
return $calendarDatas;
}
/**
* @param int $year
* @param int $addressId
*
* @throws InvalidDateFormatException
*
* @return array|CalendarData[]
*/
public function findAnonymisedVacationCalendarDataByYearAndAddressId(int $year, int $addressId): array
{
$sql =
'SELECT
MONTH(days.date) AS `month`,
days.date,
days.address_id,
days.name,
days.type,
IF(days.urlaubminuten > 0,true,false) AS `is_half`
FROM
(
SELECT
ms.datum AS `date`,
ms.adresse AS `address_id`,
a.name,
\'away\' AS `type`,
ms.urlaubminuten
FROM `mitarbeiterzeiterfassung_sollstunden` AS `ms`
INNER JOIN `adresse` AS `a` ON ms.adresse = a.id
WHERE (ms.kuerzel LIKE \'%U%\' OR ms.kuerzel LIKE \'%S%\')
AND ms.adresse != :address_id
AND year(ms.datum) = :year
AND ms.datum > CURDATE()
UNION
SELECT
ms.datum AS `date`,
ms.adresse AS `address_id`,
a.name,
(CASE
WHEN ms.kuerzel LIKE \'%U%\' THEN \'vacation\'
WHEN ms.kuerzel LIKE \'%R%\' THEN \'request-vacation\'
WHEN ms.kuerzel LIKE \'%L%\' THEN \'remove-vacation\'
WHEN ms.kuerzel LIKE \'%K%\' THEN \'sick\'
WHEN ms.kuerzel LIKE \'%S%\' THEN \'request-sick\'
WHEN ms.kuerzel LIKE \'%V%\' THEN \'remove-sick\'
WHEN ms.kuerzel LIKE \'%X%\' THEN \'absent\'
WHEN ms.kuerzel LIKE \'%N%\' THEN \'unpaid\'
END) AS `type`,
ms.urlaubminuten
FROM `mitarbeiterzeiterfassung_sollstunden` AS `ms`
INNER JOIN `adresse` AS `a` ON ms.adresse = a.id
WHERE ms.kuerzel != \'\' AND ms.kuerzel NOT LIKE \'%C%\' AND ms.kuerzel NOT LIKE \'%J%\'
AND ms.adresse = :address_id
AND year(ms.datum) = :year
) AS `days`
WHERE days.address_id = :address_id
ORDER BY days.address_id, days.date';
$results = $this->db->fetchAll($sql, ['year' => $year, 'address_id' => $addressId]);
$calendarDatas = [];
if (!empty($results)) {
foreach ($results as $result) {
$calendarDatas[] = CalendarData::fromDbState($result);
}
}
return $calendarDatas;
}
/**
* @param int $year
*
* @throws InvalidDateFormatException
*
* @return array|CalendarData[]
*/
public function findAllVacationCalendarDataByYear(int $year): array
{
$sql =
'SELECT
MONTH(days.date) AS `month`,
days.date,
days.address_id,
days.name,
days.type,
IF(days.urlaubminuten > 0,true,false) AS `is_half`
FROM
(
SELECT
ms.datum AS `date`,
ms.adresse AS `address_id`,
a.name,
(CASE
WHEN ms.kuerzel LIKE \'%U%\' THEN \'vacation\'
WHEN ms.kuerzel LIKE \'%K%\' THEN \'sick\'
WHEN ms.kuerzel LIKE \'%X%\' THEN \'absent\'
WHEN ms.kuerzel LIKE \'%N%\' THEN \'unpaid\'
END) AS `type`,
ms.urlaubminuten
FROM `mitarbeiterzeiterfassung_sollstunden` AS `ms`
INNER JOIN `adresse` AS `a` ON ms.adresse = a.id
WHERE (
ms.kuerzel LIKE \'%U%\'
OR ms.kuerzel LIKE \'%N%\'
OR ms.kuerzel LIKE \'%K%\'
OR ms.kuerzel LIKE \'%X%\'
)
AND year(ms.datum) = :year
) AS `days`
INNER JOIN(
SELECT DISTINCT
ar_groups.adresse
FROM `adresse_rolle` AS `ar_groups`
WHERE ar_groups.subjekt = :subject
AND (ar_groups.bis = \'0000-00-00\' OR ar_groups.bis > CURDATE())
) AS `ar` ON ar.adresse = days.address_id
ORDER BY days.date, days.address_id';
$results = $this->db->fetchAll($sql, ['year' => $year, 'subject' => 'Mitarbeiter']);
$calendarDatas = [];
if (!empty($results)) {
foreach ($results as $result) {
$calendarDatas[] = CalendarData::fromDbState($result);
}
}
return $calendarDatas;
}
/**
* @param int $addressId
* @param DateTimeInterface $date
*
* @return DayInfoData
*/
public function findDayInfo(int $addressId, DateTimeInterface $date): DayInfoData
{
$sql =
'SELECT
ms.kuerzel AS `type`,
ms.internal_comment AS `internal_comment`,
(CASE WEEKDAY(:date)
WHEN 0 THEN IFNULL(minutes.vorlagemo,0)
WHEN 1 THEN IFNULL(minutes.vorlagedi,0)
WHEN 2 THEN IFNULL(minutes.vorlagemi,0)
WHEN 3 THEN IFNULL(minutes.vorlagedo,0)
WHEN 4 THEN IFNULL(minutes.vorlagefr,0)
WHEN 5 THEN IFNULL(minutes.vorlagesa,0)
WHEN 6 THEN IFNULL(minutes.vorlageso,0)
END) AS `workminutes`,
ms.urlaubminuten AS `vacationminutes`
FROM (
SELECT
me.adresse,
me.vorlagemo,
me.vorlagedi,
me.vorlagemi,
me.vorlagedo,
me.vorlagefr,
me.vorlagesa,
me.vorlageso
FROM `mitarbeiterzeiterfassung_einstellungen` AS `me`
WHERE me.adresse = :address_id
ORDER BY id DESC
LIMIT 1
) AS `minutes`
LEFT JOIN `mitarbeiterzeiterfassung_sollstunden` AS `ms` ON minutes.adresse = ms.adresse AND ms.datum = :date
LIMIT 1';
$result = $this->db->fetchRow($sql, ['date' => $date->format('Y-m-d'), 'address_id' => $addressId]);
return DayInfoData::fromDbState($result);
}
/**
* @param string $requestToken
*
* @throws InvalidRequestTokenException
* @throws InvalidDateFormatException
*
* @return RequestInfoData
*/
public function getRequestInfoByToken(string $requestToken): RequestInfoData
{
$sql =
'SELECT
a.id AS `employee_id`,
a.mitarbeiternummer AS `employee_number`,
a.name AS `employee_name`,
DATE_FORMAT(MIN(ms.datum), \'%d.%m.%Y\') AS `min_date`,
DATE_FORMAT(MAX(ms.datum), \'%d.%m.%Y\') AS `max_date`,
COUNT(ms.id) AS `amount`,
ms.kommentar AS `comment`,
ms.kuerzel AS `type`,
ms.internal_comment
FROM `mitarbeiterzeiterfassung_sollstunden` AS `ms`
INNER JOIN `adresse` AS `a` ON a.id = ms.adresse
WHERE ms.vacation_request_token = :request_token
AND (
ms.kuerzel LIKE \'%R%\'
OR ms.kuerzel LIKE \'%L%\'
OR ms.kuerzel LIKE \'%S%\'
OR ms.kuerzel LIKE \'%V%\'
)
GROUP BY ms.vacation_request_token
ORDER BY ms.datum';
$result = $this->db->fetchRow($sql, ['request_token' => $requestToken]);
if (empty($result)) {
throw new InvalidRequestTokenException($requestToken . 'not valid.');
}
return RequestInfoData::fromDbState($result);
}
/**
* @param string $requestToken
*
* @throws InvalidDateFormatException
*
* @throws InvalidRequestTokenException
* @return array
*/
public function getRequestedDaysByToken(string $requestToken): array
{
$sql =
'SELECT
ms.datum AS `date`,
ms.kuerzel AS `type`
FROM `mitarbeiterzeiterfassung_sollstunden` AS `ms`
WHERE ms.vacation_request_token = :vacation_request_token
ORDER BY ms.datum';
$results = $this->db->fetchAll($sql, ['vacation_request_token' => $requestToken]);
if (empty($results)) {
throw new InvalidRequestTokenException($requestToken . 'not valid.');
}
$formatted = [];
foreach ($results as $result) {
try {
$formatted[] = ['date' => new DateTimeImmutable($result['date']), 'type' => $result['type']];
} catch (Exception $e) {
throw new InvalidDateFormatException('Could not convert date: ' . $result['date']);
}
}
return $formatted;
}
/**
* @param int $daysTillDeletion
* @param int $addressId
*
* @throws InvalidDateFormatException
*
* @return array
*/
public function findRejectedDays(int $daysTillDeletion, int $addressId): array
{
$sql =
'SELECT
ms.datum AS `date`
FROM `mitarbeiterzeiterfassung_sollstunden` AS `ms`
WHERE (ms.kuerzel LIKE \'%J%\' OR ms.kuerzel LIKE \'%C%\')
AND ms.adresse = :address_id
AND DATE_ADD(FROM_UNIXTIME(ms.vacation_request_token), INTERVAL :days_till_deletion DAY) < CURDATE()';
$results = $this->db->fetchAll(
$sql,
[
'days_till_deletion' => $daysTillDeletion,
'address_id' => $addressId,
]
);
$formatted = [];
if (!empty($results)) {
foreach ($results as $result) {
try {
$formatted[] = ['date' => new DateTimeImmutable($result['date'])];
} catch (Exception $e) {
throw new InvalidDateFormatException('Could not convert date: ' . $result['date']);
}
}
}
return $formatted;
}
/**
* @param int $addressId
*
* @return float
*/
public function findAmountRequestedVacation(int $addressId): float
{
$sql =
'SELECT SUM(info.amount) AS `amount`
FROM(
SELECT
count(ms.id) AS `amount`
FROM `mitarbeiterzeiterfassung_sollstunden` AS `ms`
WHERE ms.adresse = :address_id
AND ms.urlaubminuten = 0
AND ms.kuerzel LIKE \'%R%\'
UNION
SELECT
count(ms.id) / 2 AS `amount`
FROM `mitarbeiterzeiterfassung_sollstunden` AS `ms`
WHERE ms.adresse = :address_id
AND ms.urlaubminuten > 0
AND ms.kuerzel LIKE \'%R%\'
) AS `info`';
$result = $this->db->fetchRow($sql, ['address_id' => $addressId]);
if (!empty($result)) {
return (float)$result['amount'];
}
return 0;
}
}
@@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Service;
use DateTimeInterface;
use Xentral\Components\Database\Database;
use Xentral\Modules\TimeManagement\Exception\InvalidQueryException;
final class TimeManagementTargetHourService
{
/** @var Database $db */
private $db;
/**
* @param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* @param string $vacationRequestToken
* @param string $internalComment
*
*/
public function saveInternalComment(string $vacationRequestToken, string $internalComment): void
{
$sql =
'UPDATE `mitarbeiterzeiterfassung_sollstunden` SET
`internal_comment` = :internal_comment
WHERE `vacation_request_token` = :vacation_request_token';
$this->db->perform(
$sql,
[
'vacation_request_token' => $vacationRequestToken,
'internal_comment' => $internalComment,
]
);
}
/**
* @param int $addressId
* @param DateTimeInterface $date
* @param string $oldType
* @param string $newType
*
*/
public function updateTargetHourType(
int $addressId,
DateTimeInterface $date,
string $oldType,
string $newType
): void {
$sql =
'UPDATE `mitarbeiterzeiterfassung_sollstunden` SET
`kuerzel` = REPLACE(`kuerzel`,:old_type, :new_type)
WHERE `adresse` = :address_id
AND `datum` = :date';
$numAffected = (int)$this->db->fetchAffected(
$sql,
[
'old_type' => $oldType,
'new_type' => $newType,
'address_id' => $addressId,
'date' => $date->format('Y-m-d'),
]
);
if ($numAffected == 0) {
throw new InvalidQueryException(
'Target hour could not be updated. Maybe wrong arguments. addressId: ' . $addressId .
', dateString: ' . $date->format('Y-m-d') .
', oldType: ' . $oldType .
', newType: ' . $newType
);
}
}
/**
* @param int $addressId
* @param DateTimeInterface $date
* @param string $requestToken
*
* @throws InvalidQueryException
*/
public function updateVacationRequestToken(int $addressId, DateTimeInterface $date, string $requestToken): void
{
$sql =
'UPDATE `mitarbeiterzeiterfassung_sollstunden` SET
`vacation_request_token` = :vacation_request_token
WHERE `adresse` = :address_id
AND `datum` = :date';
$numAffected = (int)$this->db->fetchAffected(
$sql,
[
'vacation_request_token' => $requestToken,
'address_id' => $addressId,
'date' => $date->format('Y-m-d'),
]
);
if ($numAffected == 0) {
throw new InvalidQueryException(
'Target hour could not be updated. Maybe wrong arguments. addressId: ' . $addressId .
', dateString: ' . $date->format('Y-m-d') .
', requestToken: ' . $requestToken
);
}
}
}
@@ -0,0 +1,663 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement;
use DateTimeImmutable;
use DateTimeInterface;
use Xentral\Modules\TimeManagement\Data\CalendarData;
use Xentral\Modules\TimeManagement\Data\DayInfoData;
use Xentral\Modules\TimeManagement\Data\HolidayData;
use Xentral\Modules\TimeManagement\Data\RequestInfoData;
use Xentral\Modules\TimeManagement\Data\WorkDayData;
use Xentral\Modules\TimeManagement\Exception\InvalidDateFormatException;
use Xentral\Modules\TimeManagement\Exception\InvalidQueryException;
use Xentral\Modules\TimeManagement\Exception\InvalidRequestTokenException;
use Xentral\Modules\TimeManagement\Exception\SupervisorNotFoundException;
use Xentral\Modules\TimeManagement\Service\GroupGateway;
use Xentral\Modules\TimeManagement\Service\HolidayGateway;
use Xentral\Modules\TimeManagement\Service\TimeManagementHistoryService;
use Xentral\Modules\TimeManagement\Service\TimeManagementSettingGateway;
use Xentral\Modules\TimeManagement\Service\TimeManagementTargetHourGateway;
use Xentral\Modules\TimeManagement\Service\TimeManagementTargetHourService;
use Xentral\Modules\TimeManagement\Wrapper\TimeManagementTargetHourWrapper;
class TimeManagementModule
{
/** @var TimeManagementTargetHourGateway $targetHourGateway */
private $targetHourGateway;
/** @var TimeManagementTargetHourService $targetHourService */
private $targetHourService;
/** @var HolidayGateway $holidayGateway */
private $holidayGateway;
/** @var GroupGateway $groupGateway */
private $groupGateway;
/** @var TimeManagementSettingGateway $settingGateway */
private $settingGateway;
/** @var TimeManagementTargetHourWrapper $targetHourWrapper */
private $targetHourWrapper;
/** @var TimeManagementHistoryService $historyService */
private $historyService;
/** @var string UNPAID */
public const UNPAID = 'N';
/** @var string ABSENT_DAY */
public const ABSENT_DAY = 'X';
/** @var string NONE */
public const NONE = '';
/** @var string SICK */
public const SICK = 'K';
/** @var string SICK */
public const SICKREQUEST = 'S';
/** @var string SICKREMOVE */
public const SICKREMOVE = 'V';
/** @var string SICKREJECT */
public const SICKREJECT = 'C';
/** @var string VACATION */
public const VACATION = 'U';
/** @var string VACATIONREQUEST */
public const VACATIONREQUEST = 'R';
/** @var string VACATIONREMOVE */
public const VACATIONREMOVE = 'L';
/** @var string VACATIONREJECT */
public const VACATIONREJECT = 'J';
/**
* @param TimeManagementTargetHourGateway $targetHourGateway
* @param TimeManagementTargetHourService $targetHourService
* @param TimeManagementSettingGateway $settingGateway
* @param HolidayGateway $holidayGateway
* @param GroupGateway $groupGateway
* @param TimeManagementTargetHourWrapper $targetHourWrapper
* @param TimeManagementHistoryService $historyService
*/
public function __construct(
TimeManagementTargetHourGateway $targetHourGateway,
TimeManagementTargetHourService $targetHourService,
TimeManagementSettingGateway $settingGateway,
HolidayGateway $holidayGateway,
GroupGateway $groupGateway,
TimeManagementTargetHourWrapper $targetHourWrapper,
TimeManagementHistoryService $historyService
) {
$this->targetHourGateway = $targetHourGateway;
$this->targetHourService = $targetHourService;
$this->holidayGateway = $holidayGateway;
$this->groupGateway = $groupGateway;
$this->settingGateway = $settingGateway;
$this->targetHourWrapper = $targetHourWrapper;
$this->historyService = $historyService;
}
/**
* @param int $addressId
* @param DateTimeImmutable $fromDate
* @param DateTimeImmutable $tillDate
* @param string $statusOldType
* @param string $statusWishType
*
* @return array
*/
private function findPossibleDays(
int $addressId,
DateTimeImmutable $fromDate,
DateTimeImmutable $tillDate,
string $statusOldType,
string $statusWishType
): array {
$evaluatedDays = [];
while ($fromDate <= $tillDate) {
$dayInfo = $this->targetHourGateway->findDayInfo($addressId, $fromDate);
$dayType = $dayInfo->getType();
$workminutes = (int)$dayInfo->getWorkMinutes();
if ($workminutes < 0) {
$workminutes = 0;
}
$isWorkDay = $workminutes != 0;
//allowed are:
//- days with no type
//- days of the same type like the old
//- rejected days which can be reclaimed
if (
empty($dayType) ||
$dayType === $statusOldType ||
$dayType === self::SICKREJECT ||
$dayType === self::VACATIONREJECT
) {
if ($this->isAddableDay($fromDate, $isWorkDay)) {
$evaluatedDays[] = [
'date' => $fromDate,
'type' => $this->getPossibleDayType($statusOldType, $statusWishType),
'workminutes' => $workminutes,
'vacationminutes' => $dayInfo->getVacationMinutes(),
];
}
}
$fromDate = $fromDate->modify('1 day');
}
return $evaluatedDays;
}
/**
* @param DateTimeInterface $date
* @param bool $isWorkDay
*
* @return bool
*/
private function isAddableDay(DateTimeInterface $date, bool $isWorkDay): bool
{
$year = (int)$date->format('Y');
$holidays = $this->holidayGateway->findHolidayDataByYear($year);
$isHoliday = $this->isHoliday($date, $holidays);
if (!$isHoliday && $isWorkDay) {
return true;
}
return false;
}
/**
* @param string $statusOldType
* @param string $statusWishType
*
* @return string
*/
private function getPossibleDayType(string $statusOldType, string $statusWishType): string
{
$isStatusAcceptedRemove = false;
$isStatusRequestedRemove = false;
if (
strstr($statusOldType, TimeManagementModule::VACATIONREQUEST) !== false ||
strstr($statusOldType, TimeManagementModule::SICKREQUEST) !== false
) {
$isStatusRequestedRemove = true;
}
if (
strstr($statusOldType, TimeManagementModule::VACATION) !== false ||
strstr($statusOldType, TimeManagementModule::SICK) !== false ||
strstr($statusOldType, TimeManagementModule::ABSENT_DAY) !== false ||
strstr($statusOldType, TimeManagementModule::UNPAID) !== false
) {
$isStatusAcceptedRemove = true;
}
$type = self::VACATIONREQUEST;
if ($statusWishType === self::SICK) {
$type = self::SICKREQUEST;
}
if ($isStatusRequestedRemove) {
$type = self::NONE;
} elseif ($isStatusAcceptedRemove) {
$type = self::VACATIONREMOVE;
if ($statusOldType === self::SICK) {
$type = self::SICKREMOVE;
}
}
return $type;
}
/**
* @param DateTimeInterface $date
* @param array $holidays
*
* @return bool
*/
private function isHoliday(DateTimeInterface $date, array $holidays): bool
{
/** @var HolidayData $holiday */
foreach ($holidays as $holiday) {
if ($holiday->getDate()->getTimestamp() === $date->getTimestamp()) {
return true;
}
}
return false;
}
/**
* @param int $addressId
* @param bool $hasSuperPrivileges
*
* @return array
*/
public function findGroupsByAddressId(int $addressId, bool $hasSuperPrivileges): array
{
if ($hasSuperPrivileges) {
return $this->groupGateway->findAllActiveGroupsWithMembers();
}
return $this->groupGateway->findGroupsByAddressId($addressId);
}
/**
* @param string $requestToken
*
* @throws InvalidRequestTokenException
* @throws InvalidDateFormatException
*
* @return RequestInfoData
*/
public function getRequestInfoByToken(string $requestToken): RequestInfoData
{
return $this->targetHourGateway->getRequestInfoByToken($requestToken);
}
/**
* @param $year
*
* @return array|HolidayData[]
*/
public function findHolidayDataByYear($year): array
{
return $this->holidayGateway->findHolidayDataByYear($year);
}
/**
* @param int $addressId
* @param int $year
* @param bool $isAnonymised
* @param int $groupId
*
* @throws InvalidDateFormatException
*
* @return array|CalendarData[]
*/
public function getCalendarData(
int $addressId,
int $year,
bool $isAnonymised,
int $groupId = 0
): array {
if ($isAnonymised) {
if (empty($groupId)) {
$calendarData = $this->targetHourGateway->findAnonymisedVacationCalendarDataByYearAndAddressId(
$year,
$addressId
);
} else {
$calendarData = $this->targetHourGateway->findAnonymisedVacationCalendarDataByYearAndAddressIdAndGroupId(
$year,
$addressId,
$groupId
);
}
} else {
$calendarData = $this->targetHourGateway->findAllVacationCalendarDataByYear($year);
}
return $calendarData;
}
/**
* @param int $addressId
*
* @throws InvalidQueryException
*
* @return WorkDayData
*/
public function getWorkingDaysForAddress(int $addressId): WorkDayData
{
return $this->settingGateway->getWorkingDaysForAddress($addressId);
}
/**
* @param int $addressId
* @param DateTimeInterface $date
*
* @return DayInfoData
*/
public function getDayInfo(int $addressId, DateTimeInterface $date): DayInfoData
{
return $this->targetHourGateway->findDayInfo($addressId, $date);
}
/**
* @param int $addressId
*
* @return float
*/
public function getAmountRequestedVacation(int $addressId): float
{
return $this->targetHourGateway->findAmountRequestedVacation($addressId);
}
/**
* @param string $dayType
* @param bool $isReject
*
* @return string
*/
private function evaluateNextDayType(string $dayType, bool $isReject): string
{
$isVacation =
strstr($dayType, self::VACATIONREMOVE) !== false ||
strstr($dayType, self::VACATIONREQUEST) !== false;
$isRemove =
strstr($dayType, self::VACATIONREMOVE) !== false ||
strstr($dayType, self::SICKREMOVE) !== false;
if ($isRemove) {
if ($isReject) {
if ($isVacation) {
$type = self::VACATION;
} else {
$type = self::SICK;
}
} else {
$type = self::NONE;
}
} else {
if ($isReject) {
if ($isVacation) {
$type = self::VACATIONREJECT;
} else {
$type = self::SICKREJECT;
}
} else {
if ($isVacation) {
$type = self::VACATION;
} else {
$type = self::SICK;
}
}
}
return $type;
}
/**
* @param int $addressId
* @param DateTimeImmutable $from
* @param DateTimeImmutable $till
* @param bool $halfDay
* @param string $comment
* @param string $statusOldType
* @param string $statusWishType
*
* @return string
*/
public function changeDays(
int $addressId,
DateTimeImmutable $from,
DateTimeImmutable $till,
bool $halfDay,
string $comment,
string $statusOldType,
string $statusWishType
): string {
if ($from > $till) {
$till = $from;
}
$possibleDays =
$this->findPossibleDays(
$addressId,
$from,
$till,
$statusOldType,
$statusWishType
);
if (empty($possibleDays)) {
return '';
}
$date = new DateTimeImmutable();
$requestToken = (string)$date->getTimestamp();
$this->historyService->saveActivity(
$addressId,
0,
$statusOldType,
$possibleDays[0]['type'],
$requestToken,
$from,
$till,
$comment
);
foreach ($possibleDays as $day) {
$time = $day['vacationminutes'] / 60;
if ($halfDay) {
$workminutes = $day['workminutes'];
if ($workminutes > 0) {
$time = $workminutes / (2 * 60);
}
}
$this->changeDayType(
$addressId,
$day['date'],
$day['type'],
$comment,
(string)$time,
$requestToken
);
}
if($day['type'] === self::NONE){
return '';
}
else{
return $requestToken;
}
}
/**
* @param int $addressId
* @param DateTimeInterface $date
* @param string $type
* @param string $comment
* @param string $time
* @param string $requestToken
*/
public function changeDayType(
int $addressId,
DateTimeInterface $date,
string $type,
string $comment,
string $time,
string $requestToken = ''
): void {
$this->clearDayType($addressId, $date, $type);
if ($type !== self::NONE) {
$this->targetHourWrapper->handleType($addressId, $date, $type, true, $time, $requestToken);
} else {
if (!empty($requestToken)) {
$this->targetHourService->updateVacationRequestToken($addressId, $date, $requestToken);
}
}
if (!empty($comment)) {
$this->targetHourWrapper->saveComment($addressId, $date, $comment);
}
$this->targetHourWrapper->recalculate($addressId, $date);
}
/**
* @param int $addressId
* @param DateTimeInterface $date
* @param string $type
*/
private function clearDayType(int $addressId, DateTimeInterface $date, string $type): void
{
$types = [
TimeManagementModule::UNPAID,
TimeManagementModule::ABSENT_DAY,
TimeManagementModule::VACATION,
TimeManagementModule::VACATIONREQUEST,
TimeManagementModule::VACATIONREJECT,
TimeManagementModule::VACATIONREMOVE,
TimeManagementModule::SICK,
TimeManagementModule::SICKREQUEST,
TimeManagementModule::SICKREMOVE,
TimeManagementModule::SICKREJECT,
];
if ($type != TimeManagementModule::NONE) {
unset($types[$type]);
}
$this->targetHourWrapper->handleType($addressId, $date, implode('', $types), false);
}
/**
* @param int $employeeAddressId
* @param int $supervisorAddressId
* @param bool $isReject
* @param string $internalComment
* @param string $requestToken
*
* @throws InvalidDateFormatException
* @throws InvalidRequestTokenException
*/
public function handleRequestedDays(
int $employeeAddressId,
int $supervisorAddressId,
bool $isReject,
string $internalComment,
string $requestToken
): void {
$requestedDays = $this->targetHourGateway->getRequestedDaysByToken($requestToken);
if (!empty($requestedDays)) {
$from = $requestedDays[0]['date'];
$till = $requestedDays[count($requestedDays) - 1]['date'];
$oldType = $requestedDays[0]['type'];
$newType = $this->evaluateNextDayType($oldType, $isReject);
$this->historyService->saveActivity(
0,
$supervisorAddressId,
$oldType,
$newType,
$requestToken,
$from,
$till,
$internalComment
);
$date = new DateTimeImmutable();
$requestToken = (string)$date->getTimestamp();
foreach ($requestedDays as $requestedDay) {
$this->targetHourService->updateTargetHourType(
$employeeAddressId,
$requestedDay['date'],
$requestedDay['type'],
$newType
);
$this->targetHourService->updateVacationRequestToken(
$employeeAddressId,
$requestedDay['date'],
$requestToken
);
$this->targetHourWrapper->recalculate($employeeAddressId,$requestedDay['date']);
}
}
if (!empty($internalComment)) {
$this->targetHourService->saveInternalComment($requestToken, $internalComment);
}
}
/**
* @param int $daysTillDeletion
* @param int $addressId
*
* @throws InvalidDateFormatException
*/
public function removeRejectedAfterXDays(int $daysTillDeletion, int $addressId): void
{
$rejectedDays = $this->targetHourGateway->findRejectedDays($daysTillDeletion, $addressId);
if (!empty($rejectedDays)) {
foreach ($rejectedDays as $day) {
$this->clearDayType($addressId, $day['date'], TimeManagementModule::NONE);
}
}
}
/**
* @param int $addressId
* @param int $groupId
*
* @return bool
*/
public function checkAddressHasGroup(int $addressId, int $groupId): bool
{
return $this->groupGateway->isAddressInGroup($addressId, $groupId);
}
/**
* @param string $type
*
* @return string
*/
public function mapTypeToLanguage(string $type): string
{
$mapping =
[
self::NONE => 'Kein Status',
self::VACATION => 'Urlaub',
self::VACATIONREQUEST => 'Urlaubsantrag',
self::VACATIONREJECT => 'Urlaub ablehnen',
self::VACATIONREMOVE => 'Urlaub entfernen',
self::SICK => 'Krank',
self::SICKREQUEST => 'Krankheitsantrag',
self::SICKREMOVE => 'Krankheit entfernen',
self::SICKREJECT => 'Krankheit ablehnen',
self::UNPAID => 'Unbezahlter Urlaub',
self::ABSENT_DAY => 'Fehltag',
];
return $mapping[$type];
}
/**
* @param int $employeeAddressId
* @param int $groupId
*
* @throws SupervisorNotFoundException
*
* @return int[]
*/
public function getSupervisorAddressIds(int $employeeAddressId, int $groupId): array
{
return $this->groupGateway->getSupervisorAddressIds($employeeAddressId, $groupId);
}
}
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Xentral\Modules\TimeManagement\Wrapper;
use erpAPI;
use Xentral\Modules\TimeManagement\Exception\EmailNotSentException;
use Xentral\Modules\TimeManagement\Exception\EmailAccountNotFoundException;
final class TimeManagementMailerWrapper
{
/** @var erpAPI erp */
private $erp;
/**
* @param erpAPI $erp
*/
public function __construct(erpAPI $erp)
{
$this->erp = $erp;
}
/**
* @param string $senderEmail
* @param string[] $recipientEmails
* @param string $mailSubject
* @param string $mailContent
*
* @throws EmailAccountNotFoundException
* @throws EmailNotSentException
*/
public function send(string $senderEmail, array $recipientEmails, string $mailSubject, string $mailContent): void
{
foreach ($recipientEmails as $email) {
$isSent = $this->erp->MailSend(
$this->erp->GetFirmaMail(),
$senderEmail,
$email,
'',
$mailSubject,
$mailContent,
"",
""
);
if (!$isSent) {
throw new EmailNotSentException('Mail could not be sent. More info in the logger');
}
}
}
}
@@ -0,0 +1,69 @@
<?php
namespace Xentral\Modules\TimeManagement\Wrapper;
use DateTimeInterface;
use Mitarbeiterzeiterfassung;
final class TimeManagementTargetHourWrapper
{
/** @var Mitarbeiterzeiterfassung $timeRecordingModule */
private $timeRecordingModule;
public function __construct(Mitarbeiterzeiterfassung $timeRecordingModule)
{
$this->timeRecordingModule = $timeRecordingModule;
}
/**
* @param int $address_id
* @param DateTimeInterface $date
*
* @return bool
*/
public function recalculate(int $address_id, DateTimeInterface $date): bool
{
return $this->timeRecordingModule->MitarbeitererfassungIstNeuberechnen($address_id, $date->format('Y-m-d'));
}
/**
* @param int $addressId
* @param DateTimeInterface $date
* @param string $type
* @param bool $add
* @param string $time
* @param string $requestToken
*/
public function handleType(
int $addressId,
DateTimeInterface $date,
string $type,
bool $add = true,
string $time = '0',
string $requestToken = ''
): void {
$this->timeRecordingModule->MitarbeiterzeiterfassungInsertUpdateKuerzel(
$addressId,
$date->format('Y-m-d'),
$type,
$add,
$time,
$requestToken
);
}
/**
* @param int $addressId
* @param DateTimeInterface $date
* @param string $comment
*/
public function saveComment(int $addressId, DateTimeInterface $date, string $comment): void
{
$this->timeRecordingModule->MitarbeiterzeiterfassungInsertUpdateKommentar(
$addressId,
$date->format('Y-m-d'),
$comment
);
}
}
@@ -0,0 +1,205 @@
:root{
--calendar-vacation-standard: var(--xentral-signature-green); /* green */
--calendar-vacation-away: rgba(44, 229, 162, 0.63);
--calendar-vacation-request: rgba(44, 229, 162, 0.25);
--calendar-vacation-reject: var(--xentral-signature-petrol); /* petrol */
--calendar-vacation-remove: rgba(59, 184, 195, 0.5);
--calendar-sick-standard:var(--xentral-signature-pink); /* pink */
--calendar-sick-request: rgba(229, 110, 202, 0.25);
--calendar-sick-reject: var(--xentral-signature-orange); /* orange */
--calendar-sick-remove: rgba(238, 134, 103, 0.5);
--calendar-unpaid-standard: var(--xentral-signature-violet); /* violett */
--calendar-absent-standard: var(--xentral-signature-blue); /* blue */
}
#easycalendar {
display: grid;
grid-template-columns: 7% repeat(31, 3%);
box-sizing: border-box;
color: #6d6d6f;
}
#easycalendar-legend {
border-top:1px solid var(--current-grey);
padding: 15px 0 15px 0;
}
#easycalendar-legend span{
line-height:1.5em;
}
#easycalendar-legend .txt{
padding: 0 5px 0 3px;
}
#easycalendar-legend .box{
margin: 0 0 0 5px;
color: transparent;
}
#easycalendar .standard,
#easycalendar .saturday,
#easycalendar .sunday,
#easycalendar .holiday,
#easycalendar .vacation,
#easycalendar .unpaid,
#easycalendar .sick,
#easycalendar .requestsick,
#easycalendar .absent,
#easycalendar .away,
#easycalendar .requested,
#easycalendar .noday,
#easycalendar .rejected,
#easycalendar .remove{
box-sizing: border-box;
border-top:1px solid var(--current-grey);
border-left:1px solid var(--current-grey);
color:#fff;
text-align:center;
}
#easycalendar .month,
#easycalendar .top {
box-sizing: border-box;
font-weight:bold;
padding: 3px 0 4px 4px;
}
#easycalendar .month {
border-top:1px solid var(--current-grey);
}
#easycalendar .employee-name {
box-sizing: border-box;
font-style:italic;
padding: 3px 0 4px 10px;
}
#easycalendar .top {
text-align: center;
}
#easycalendar.monclick .monday,
#easycalendar.tueclick .tuesday,
#easycalendar.wedclick .wednesday,
#easycalendar.thuclick .thursday,
#easycalendar.friclick .friday,
#easycalendar.satclick .saturday,
#easycalendar.sunclick .sunday{
cursor: pointer;
}
#easycalendar.monclick .monday:hover,
#easycalendar.tueclick .tuesday:hover,
#easycalendar.wedclick .wednesday:hover,
#easycalendar.thuclick .thursday:hover,
#easycalendar.friclick .friday:hover,
#easycalendar.satclick .saturday:hover,
#easycalendar.sunclick .sunday:hover{
background-color:var(--xentral-signature-violet-transparent);
}
#easycalendar .saturday,
#easycalendar .sunday,
#easycalendar .holiday {
background-color:var(--current-grey);
}
#easycalendar .noday {
background-color:#fff;
}
#easycalendar-legend .unpaid,
#easycalendar .unpaid {
background-color: var(--calendar-unpaid-standard);
}
#easycalendar-legend .absent,
#easycalendar .absent {
background-color: var(--calendar-absent-standard);
}
#easycalendar-legend .away,
#easycalendar .away {
background-color: var(--calendar-vacation-away);
}
#easycalendar-legend .vacation,
#easycalendar .vacation {
background-color: var(--calendar-vacation-standard);
}
#easycalendar-legend .request-vacation,
#easycalendar .request-vacation {
background-color: var(--calendar-vacation-request);
}
#easycalendar-legend .reject-vacation,
#easycalendar .reject-vacation {
background-color: var(--calendar-vacation-reject);
}
#easycalendar-legend .remove-vacation,
#easycalendar .remove-vacation {
background-color: var(--calendar-vacation-remove);
}
#easycalendar-legend .sick,
#easycalendar .sick {
background-color: var(--calendar-sick-standard);
}
#easycalendar-legend .request-sick,
#easycalendar .request-sick {
background-color: var(--calendar-sick-request);
}
#easycalendar-legend .reject-sick,
#easycalendar .reject-sick {
background-color: var(--calendar-sick-reject);
}
#easycalendar-legend .remove-sick,
#easycalendar .remove-sick {
background-color: var(--calendar-sick-remove);
}
#easycalendar .unpaid.half,
#easycalendar .absent.half,
#easycalendar .away.half,
#easycalendar .vacation.half,
#easycalendar .request-vacation.half,
#easycalendar .reject-vacation.half,
#easycalendar .remove-vacation.half,
#easycalendar .sick.half,
#easycalendar .request-sick.half,
#easycalendar .reject-sick.half,
#easycalendar .remove-sick.half {
color: transparent;
shape-outside: polygon(
0 0,
0 100%,
100% 0
);
clip-path: polygon(
0 0,
0 100%,
100% 0
);
}
#easycalendar .inline {
display: inline-block;
height:100%;
border: 0 none;
text-align: center;
padding: 3px 0 0 0;
}
#easycalendar .today {
color:var(--calendar-vacation-standard);
}
@@ -0,0 +1,421 @@
var TimeManagementEasyCalendar = (function ($) {
'use strict';
var me = {
isInitialized: false,
selector: {
easyCalendar: '#easycalendar',
easyCalendarLegend: '#easycalendar-legend',
calendarattributes: '#calendarattributes'
},
storage: {
days: [],
dataEndpoint: '',
monthNames: [
'Januar', 'Februar', 'März', 'April', 'Mai',
'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
statusMapping: {
'unpaid': 'Unbezahlt',
'absent': 'Fehltag',
'away': 'Abwesend',
'vacation': 'Urlaub',
'request-vacation': 'Urlaubsantrag',
'remove-vacation': 'Stornoantrag Urlaub',
'sick': 'Krank',
'request-sick': 'Krankheitsantrag',
'remove-sick': 'Stornoantrag Krankheit'
}
},
init: function () {
if (me.isInitialized === true) {
return;
}
me.registerMonthNames();
me.drawCalendar();
me.drawLegend();
},
drawLegend: function () {
for (let statusClass in me.storage.statusMapping) {
let statusTxt = me.storage.statusMapping[statusClass];
let html = '<span class="' + statusClass + ' box">m</span><span class="txt">' + statusTxt + '</span>';
$(me.selector.easyCalendarLegend).append(html);
}
},
registerMonthNames: function () {
if ($(me.selector.calendarattributes).length !== 0) {
try {
let calendarattributes = JSON.parse($(me.selector.calendarattributes).html());
me.storage.monthNames = calendarattributes.monthNames;
}
catch (e) {
//do nothing, fallback from storage
}
}
},
drawCalendar: function () {
me.storage.dataEndpoint = $(me.selector.easyCalendar).data('endpoint');
$.ajax({
url: me.storage.dataEndpoint,
type: 'POST',
dataType: 'json',
success: function (data) {
if (data.error) {
me.storage.$dialog.find(me.selector.msg).text(data.error);
} else {
let formattedData = [];
if (data.is_expanded === true) {
formattedData = me.buildCalendarDataExpanded(data.holidays, data.calendar_data,
data.calendar_year);
} else {
formattedData = me.buildCalendarDataSummed(data.holidays, data.calendar_data,
data.calendar_year);
}
let html = '';
for (let i = 0; i < formattedData.length; i++) {
html +=
'<div ' +
'class="' + formattedData[i].class + '" ' +
'title="' + formattedData[i].title + '" ' +
'data-day="' + formattedData[i].day + '" ' +
'data-month="' + formattedData[i].month + '" ' +
'data-year="' + formattedData[i].year + '" ' +
'>' +
formattedData[i].txt +
'</div>';
}
$(me.selector.easyCalendar).append(html);
}
},
beforeSend: function () {}
});
},
buildCalendarDataExpanded: function (holidays, vacations, year) {
let dayAmount = 31;
let monthAmount = 12;
let currentMonth = new Date().getMonth() + 1;
let allDays = me.buildCalendarFirstLine(year, dayAmount);
for (let month = 1; month <= monthAmount; month++) {
let daysInMonth = me.daysInMonth(year, month);
let monthName = me.storage.monthNames[month - 1];
allDays.push({
txt: monthName,
class: (currentMonth === month ? 'month today' : 'month'),
title: '',
year: year,
month: month,
day: 0
});
// first line with month
for (let day = 1; day <= dayAmount; day++) {
let classAndTitle = me.findClassAndTitle(daysInMonth, holidays, year, month, day);
let htmlClass = classAndTitle.htmlClass;
let title = classAndTitle.title;
allDays.push({
txt: '',
class: htmlClass,
title: title,
year: year,
month: month,
day: day
});
}
// month info by employee
if (vacations) {
for (let employeeName in vacations) {
if (vacations.hasOwnProperty(employeeName)) {
let employeeVacation = vacations[employeeName];
allDays.push({
txt: employeeName,
class: 'employee-name',
title: '',
year: year,
month: month,
day: 0
});
for (let day = 1; day <= dayAmount; day++) {
let classAndTitle = me.findClassAndTitle(daysInMonth, holidays, year, month, day);
let htmlClass = classAndTitle.htmlClass;
let title = classAndTitle.title;
let date = me.buildDateString(year, month, day);
if (date in employeeVacation) {
let type = employeeVacation[date];
htmlClass += ' ' + type;
title = me.mapTypeToName(type) + ': ' + employeeName;
}
allDays.push({
txt: '',
class: htmlClass,
title: title,
year: year,
month: month,
day: day
});
}
}
}
}
}
return allDays;
},
mapTypeToName: function (type) {
if (type.search('half')) {
type = type.replace('half', '').trim();
}
if (type in me.storage.statusMapping) {
return me.storage.statusMapping[type];
}
return '';
},
buildCalendarFirstLine: function (year, dayAmount) {
let allDays = [];
let currentDay = new Date().getDate();
//first line with day-numbers
for (let day = 0; day <= dayAmount; day++) {
if (day === 0) {
allDays.push({
txt: '',
class: 'top',
title: '',
year: year,
month: 0,
day: 0
});
} else {
allDays.push({
txt: day,
class: (day === currentDay ? 'top today' : 'top'),
title: '',
year: year,
month: 0,
day: 0
});
}
}
return allDays;
},
buildCalendarDataSummed: function (holidays, vacations, year) {
let dayAmount = 31;
let monthAmount = 12;
let summedVacations = me.sumVacations(vacations);
let allDays = me.buildCalendarFirstLine(year, dayAmount);
let currentMonth = new Date().getMonth() + 1;
for (let month = 1; month <= monthAmount; month++) {
let daysInMonth = me.daysInMonth(year, month);
let monthName = me.storage.monthNames[month - 1];
allDays.push({
txt: monthName,
class: (currentMonth === month ? 'month today' : 'month'),
title: '',
year: year,
month: month,
day: 0
});
for (let day = 1; day <= dayAmount; day++) {
let classAndTitle = me.findClassAndTitle(daysInMonth, holidays, year, month, day);
let htmlClass = classAndTitle.htmlClass;
let title = classAndTitle.title;
let date = me.buildDateString(year, month, day);
if (summedVacations.hasOwnProperty(date)) {
let txt = '';
let typeAddress = summedVacations[date];
let countTypes = 0;
for (let type in typeAddress) {
countTypes++;
}
for (let type in typeAddress) {
if (typeAddress.hasOwnProperty(type)) {
let addresses = typeAddress[type];
txt +=
'<div class="' + type + ' inline" ' +
'title="' + me.mapTypeToName(type) + ': ' + addresses.join(', ') + '" ' +
'style="width:' + Math.floor(100 / countTypes) + '%">' +
(addresses.length === 1 ? '' : addresses.length) +
'</div>';
}
}
allDays.push({
txt: txt,
class: htmlClass,
title: title,
year: year,
month: month,
day: day
});
} else {
allDays.push({
txt: '',
class: htmlClass,
title: title,
year: year,
month: month,
day: day
});
}
}
}
return allDays;
},
sumVacations: function (vacations) {
let summedVacation = {};
for (let employeeName in vacations) {
if (vacations.hasOwnProperty(employeeName)) {
let employeeVacation = vacations[employeeName];
for (let date in employeeVacation) {
if (employeeVacation.hasOwnProperty(date)) {
let type = employeeVacation[date];
if (summedVacation.hasOwnProperty(date)) {
let typeAddress = summedVacation[date];
if (typeAddress.hasOwnProperty(type)) {
let addresses = typeAddress[type];
addresses.push(employeeName);
typeAddress[type] = addresses;
summedVacation[date] = typeAddress;
} else {
let typeAddress = summedVacation[date];
typeAddress[type] = [employeeName];
summedVacation[date] = typeAddress;
}
} else {
let typeAddress = {};
typeAddress[type] = [employeeName];
summedVacation[date] = typeAddress;
}
}
}
}
}
return summedVacation;
},
buildDateString: function (year, month, day) {
return year + '-' + (month < 10 ? '0' + month : month) + '-' + (day < 10 ? '0' + day : day);
},
findClassAndTitle: function (daysInMonth, holidays, year, month, day) {
let date = me.buildDateString(year, month, day);
let htmlClass = 'standard';
let title = '';
//always counting to 31, therefore some days dont exist
if (day > daysInMonth) {
htmlClass = 'noday';
}
if (me.isSaturday(date) || me.isSunday(date)) {
htmlClass = me.getWeekDayName(date);
} else {
htmlClass += ' ' + me.getWeekDayName(date);
}
// is a holiday
if (date in holidays) {
htmlClass = 'holiday';
title = holidays[date];
}
return {
htmlClass: htmlClass,
title: title
};
},
daysInMonth: function (year, month) {
return new Date(year, month, 0).getDate();
},
isSaturday: function (dateString) {
let day = new Date(dateString).getDay();
return (day === 6);
},
isSunday: function (dateString) {
let day = new Date(dateString).getDay();
return (day === 0);
},
getWeekDayName: function (dateString) {
let weekdays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
let day = new Date(dateString).getDay();
return weekdays[day];
}
};
return {
init: me.init
};
})(jQuery);
$(document).ready(function () {
TimeManagementEasyCalendar.init();
});
@@ -0,0 +1,162 @@
var TimeManagementHandle = (function ($) {
'use strict';
var me = {
isInitialized: false,
selector: {
handleDialog: '#timemanagement-handle-dialog',
form: '#timemanagement-handle-form',
msg: '#timemanagement-handle-msg',
overviewTable: '#requesteddaystatus',
clickClass: '.handle-day-status',
commentSpan: '#comment',
fromSpan: '#from',
tillSpan: '#till',
amountSpan: '#amount',
employeeNameSpan: '#employee-name',
employeeNumberSpan: '#employee-number',
requestTokenHidden: '#request-token',
requestAddressIdHidden: '#request-address-id',
requestRejectHidden: '#request-reject',
deleteTitle: '#delete-title',
requestTitle: '#request-title',
defaultNoteVacation: '#default-note-vacation',
defaultNoteSick: '#default-note-sick',
internalComment: '#internal-comment'
},
storage: {
$dialog: null
},
init: function () {
if (me.isInitialized === true) {
return;
}
me.storage.$dialog = $(me.selector.handleDialog);
me.dialogInit();
me.registerEvents();
me.isInitialized = true;
},
registerEvents: function () {
$(me.selector.overviewTable).on('click', me.selector.clickClass, function (event) {
event.preventDefault();
me.dialogOpen(this.id.replace('vac-', ''));
});
},
dialogInit: function () {
me.storage.$dialog.dialog({
modal: true,
bgiframe: true,
closeOnEscape: false,
minWidth: 650,
minHeight: 200,
autoOpen: false,
open: function () {
$(me.selector.inputKey).trigger('focus');
},
close: function () {
me.dialogReset();
},
buttons: {
ZUSTIMMEN: function () {
$(me.selector.form).submit();
},
ABLEHNEN: function () {
$(me.selector.requestRejectHidden).val(1);
$(me.selector.form).submit();
}
}
});
},
dialogOpen: function (requestToken) {
$.ajax({
url: 'index.php?module=mitarbeiterzeiterfassung&action=timemanagementhandle&cmd=timemanagementhandleinfo',
type: 'POST',
dataType: 'json',
data: {
requestToken: requestToken
},
success: function (data) {
if (data.error) {
me.storage.$dialog.find(me.selector.msg).text(data.error);
} else {
me.dialogReset();
let title = '';
if (data.type === 'L' || data.type === 'V') {
title = $(me.selector.deleteTitle).text();
} else {
title = $(me.selector.requestTitle).text();
}
let defaultNote = '';
if(data.type === 'S'){
defaultNote = $(me.selector.defaultNoteSick).text();
}
if(data.type === 'R'){
defaultNote = $(me.selector.defaultNoteVacation).text();
}
me.storage.$dialog.find(me.selector.commentSpan).text(data.comment);
me.storage.$dialog.find(me.selector.fromSpan).text(data.min_date);
me.storage.$dialog.find(me.selector.tillSpan).text(data.max_date);
me.storage.$dialog.find(me.selector.amountSpan).text(data.amount);
me.storage.$dialog.find(me.selector.employeeNameSpan).text(data.employee_name);
me.storage.$dialog.find(me.selector.employeeNumberSpan).text(data.employee_number);
me.storage.$dialog.find(me.selector.internalComment).val(defaultNote);
me.storage.$dialog.find(me.selector.requestTokenHidden).val(requestToken);
me.storage.$dialog.find(me.selector.requestAddressIdHidden).val(data.employee_id);
me.storage.$dialog.dialog('option', 'title', title);
me.storage.$dialog.dialog('open');
}
},
beforeSend: function () {}
});
},
dialogClose: function () {
me.storage.$dialog.dialog('close');
},
dialogReset: function () {
me.storage.$dialog.find(me.selector.commentSpan).text('');
me.storage.$dialog.find(me.selector.fromSpan).text('');
me.storage.$dialog.find(me.selector.tillSpan).text('');
me.storage.$dialog.find(me.selector.amountSpan).text('');
me.storage.$dialog.find(me.selector.employeeNameSpan).text('');
me.storage.$dialog.find(me.selector.employeeNumberSpan).text('');
me.storage.$dialog.find(me.selector.internalComment).val('');
me.storage.$dialog.find(me.selector.requestRejectHidden).val(0);
me.storage.$dialog.find(me.selector.requestTokenHidden).val(null);
me.storage.$dialog.find(me.selector.requestAddressIdHidden).val(null);
}
};
return {
init: me.init
};
})(jQuery);
$(document).ready(function () {
TimeManagementHandle.init();
});
@@ -0,0 +1,261 @@
var TimeManagementRequest = (function ($) {
'use strict';
var me = {
isInitialized: false,
selector: {
msg: '#timemanagement-msg',
easyCalendar: '#easycalendar',
form: '#timemanagement-form',
newEdit: '#timemanagement-new-edit',
fromInput: '#from',
tillInput: '#till',
remainingVacationSpan: '#remaining-vacation',
totalVacationSpan: '#total-vacation',
acceptedVacationSpan: '#accepted-vacation',
commentTextarea: '#comment',
newDialog: '#timemanagement-new-dialog',
requestTitle: '#request-title',
deleteTitle: '#delete-title',
calendarattributes: '#calendarattributes',
buttonOk: '#button-ok',
plannedVacationSpan: '#planned-vacation',
statusOldTypeHidden: '#status-old-type',
statusWishTypeDiv: '#status-wish-type-box',
//internalComment: '#internal-comment',
halfday: '#halfday'
},
storage: {
$dialog: null,
monthNames: [
'Januar', 'Februar', 'März', 'April', 'Mai',
'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
dayNames: ['SO', 'MO', 'DI', 'MI', 'DO', 'FR', 'SA']
},
init: function () {
if (me.isInitialized === true) {
return;
}
me.storage.$dialog = $(me.selector.newEdit);
me.dialogInit();
me.registerEvents();
me.registerDatepicker(me.selector.fromInput);
me.registerDatepicker(me.selector.tillInput);
me.isInitialized = true;
},
registerDatepicker: function (field) {
if ($(me.selector.calendarattributes).length !== 0) {
try {
var calendarattributes = JSON.parse($(me.selector.calendarattributes).html());
me.storage.monthNames = calendarattributes.monthNames;
let dayNames = calendarattributes.dayNames;
let shortNames = [];
me.storage.dayNames = [];
for (let i = 0; i <= dayNames.length; i++) {
let shortname = dayNames[i].toUpperCase().substring(0, 2);
me.storage.dayNames.push(shortname);
}
}
catch (e) {
//do nothing, fallback from storage
}
}
$(field).datepicker({
dateFormat: 'dd.mm.yy',
dayNamesMin: me.storage.dayNames,
firstDay: 1,
showWeek: false,
monthNames: me.storage.monthNames
});
},
registerEvents: function () {
let clickables = [
'monclick',
'tueclick',
'wedclick',
'thuclick',
'friclick',
'satclick',
'sunclick'
];
let clickClasses = [
'.monday',
'.tuesday',
'.wednesday',
'.thursday',
'.friday',
'.saturday',
'.sunday'
];
for (let i = 0; i <= clickables.length; i++) {
if ($(me.selector.easyCalendar).hasClass(clickables[i])) {
$(me.selector.easyCalendar).on('click', clickClasses[i], function (event) {
let day = $(this).data('day');
let month = $(this).data('month');
let year = $(this).data('year');
me.dialogOpen(day, month, year);
});
}
}
$(me.selector.newDialog).on('click', function (event) {
event.preventDefault();
me.dialogOpen();
});
},
dialogInit: function () {
me.storage.$dialog.dialog({
modal: true,
bgiframe: true,
closeOnEscape: false,
minWidth: 650,
minHeight: 350,
maxHeight: 500,
autoOpen: false,
open: function () {
if ($(me.selector.fromInput).val() === '') {
$(me.selector.fromInput).trigger('focus');
} else {
$(me.selector.tillInput).trigger('focus');
}
},
close: function () {
me.dialogReset();
},
buttons: [
{
id: 'button-ok',
text: 'SPEICHERN',
click: function () {
$(me.selector.form).submit();
}
}
]
});
},
dialogOpen: function (day, month, year) {
$.ajax({
url: 'index.php?module=mitarbeiterzeiterfassung&action=timemanagementrequest&cmd=timemanagementinfo',
type: 'POST',
dataType: 'json',
data: {
year: year,
month: month,
day: day
},
success: function (data) {
if (data.error) {
me.storage.$dialog.find(me.selector.msg).text(data.error);
$(me.selector.msg).addClass('error');
} else {
me.dialogReset();
var date = '';
if (day && month && year) {
date = day + '.' + month + '.' + year;
}
let title = '';
if (
data.day_type !== '' &&
data.day_type !== 'C' &&
data.day_type !== 'J'
) {
title = $(me.selector.deleteTitle).text();
me.storage.$dialog.find(me.selector.statusOldTypeHidden).val(data.day_type);
$(me.selector.statusWishTypeDiv).css('display', 'none');
} else {
title = $(me.selector.requestTitle).text();
}
if (me.isInPast(day, month, year)) {
$(me.selector.msg).text('Der Tag liegt in der Vergangenheit.');
if (data.is_accepted_type) {
$(me.selector.msg).addClass('error');
} else {
$(me.selector.msg).addClass('warning');
}
}
me.storage.$dialog.find(me.selector.totalVacationSpan).text(data.vacation_total);
me.storage.$dialog.find(me.selector.acceptedVacationSpan).text(data.vacation_accepted);
me.storage.$dialog.find(me.selector.plannedVacationSpan).text(data.planned);
me.storage.$dialog.find(me.selector.remainingVacationSpan).text(
data.vacation_total - data.vacation_accepted - data.planned
);
//me.storage.$dialog.find(me.selector.internalComment).text(data.internal_comment);
me.storage.$dialog.find(me.selector.fromInput).val(date);
me.storage.$dialog.find(me.selector.tillInput).val(date);
if (me.isInPast(day, month, year) && data.is_accepted_type) {
$(me.selector.buttonOk).button('disable');
}
me.storage.$dialog.dialog('option', 'title', title);
me.storage.$dialog.dialog('open');
}
},
beforeSend: function () {}
});
},
isInPast: function (day, month, year) {
let dateString = year + '-' + month + '-' + day;
let date = new Date(dateString).setHours(0, 0, 0, 0);
let now = new Date().setHours(0, 0, 0, 0);
return date < now;
},
dialogClose: function () {
me.storage.$dialog.dialog('close');
},
dialogReset: function () {
me.storage.$dialog.find(me.selector.fromInput).val(null);
me.storage.$dialog.find(me.selector.tillInput).val(null);
me.storage.$dialog.find(me.selector.commentTextarea).val(null);
me.storage.$dialog.find(me.selector.statusOldTypeHidden).val('');
$(me.selector.statusWishTypeDiv).css('display', 'inline');
me.storage.$dialog.find(me.selector.halfday).prop('checked', false);
$(me.selector.msg).removeClass('error');
$(me.selector.msg).removeClass('warning');
me.storage.$dialog.find(me.selector.msg).text('');
$(me.selector.buttonOk).button('enable');
let title = $(me.selector.requestTitle).text();
me.storage.$dialog.dialog('option', 'title', title);
}
};
return {
init: me.init
};
})(jQuery);
$(document).ready(function () {
TimeManagementRequest.init();
});