mirror of
https://github.com/YunoHost-Apps/kanboard_ynh.git
synced 2024-09-03 19:36:17 +02:00
357 lines
9.3 KiB
PHP
357 lines
9.3 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace Model;
|
||
|
|
||
|
use DateTime;
|
||
|
use DateInterval;
|
||
|
|
||
|
/**
|
||
|
* Timetable
|
||
|
*
|
||
|
* @package model
|
||
|
* @author Frederic Guillot
|
||
|
*/
|
||
|
class Timetable extends Base
|
||
|
{
|
||
|
/**
|
||
|
* User time slots
|
||
|
*
|
||
|
* @access private
|
||
|
* @var array
|
||
|
*/
|
||
|
private $day;
|
||
|
private $week;
|
||
|
private $overtime;
|
||
|
private $timeoff;
|
||
|
|
||
|
/**
|
||
|
* Get a set of events by using the intersection between the timetable and the time tracking data
|
||
|
*
|
||
|
* @access public
|
||
|
* @param integer $user_id
|
||
|
* @param array $events Time tracking data
|
||
|
* @param string $start ISO8601 date
|
||
|
* @param string $end ISO8601 date
|
||
|
* @return array
|
||
|
*/
|
||
|
public function calculateEventsIntersect($user_id, array $events, $start, $end)
|
||
|
{
|
||
|
$start_dt = new DateTime($start);
|
||
|
$start_dt->setTime(0, 0);
|
||
|
|
||
|
$end_dt = new DateTime($end);
|
||
|
$end_dt->setTime(23, 59);
|
||
|
|
||
|
$timetable = $this->calculate($user_id, $start_dt, $end_dt);
|
||
|
|
||
|
// The user has no timetable
|
||
|
if (empty($this->week)) {
|
||
|
return $events;
|
||
|
}
|
||
|
|
||
|
$results = array();
|
||
|
|
||
|
foreach ($events as $event) {
|
||
|
$results = array_merge($results, $this->calculateEventIntersect($event, $timetable));
|
||
|
}
|
||
|
|
||
|
return $results;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get a serie of events based on the timetable and the provided event
|
||
|
*
|
||
|
* @access public
|
||
|
* @param array $event
|
||
|
* @param array $timetable
|
||
|
* @return array
|
||
|
*/
|
||
|
public function calculateEventIntersect(array $event, array $timetable)
|
||
|
{
|
||
|
$events = array();
|
||
|
|
||
|
foreach ($timetable as $slot) {
|
||
|
|
||
|
$start_ts = $slot[0]->getTimestamp();
|
||
|
$end_ts = $slot[1]->getTimestamp();
|
||
|
|
||
|
if ($start_ts > $event['end']) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ($event['start'] <= $start_ts) {
|
||
|
$event['start'] = $start_ts;
|
||
|
}
|
||
|
|
||
|
if ($event['start'] >= $start_ts && $event['start'] <= $end_ts) {
|
||
|
|
||
|
if ($event['end'] >= $end_ts) {
|
||
|
$events[] = array_merge($event, array('end' => $end_ts));
|
||
|
}
|
||
|
else {
|
||
|
$events[] = $event;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $events;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Calculate effective worked hours by taking into consideration the timetable
|
||
|
*
|
||
|
* @access public
|
||
|
* @param integer $user_id
|
||
|
* @param \DateTime $start
|
||
|
* @param \DateTime $end
|
||
|
* @return float
|
||
|
*/
|
||
|
public function calculateEffectiveDuration($user_id, DateTime $start, DateTime $end)
|
||
|
{
|
||
|
$end_timetable = clone($end);
|
||
|
$end_timetable->setTime(23, 59);
|
||
|
|
||
|
$timetable = $this->calculate($user_id, $start, $end_timetable);
|
||
|
$found_start = false;
|
||
|
$hours = 0;
|
||
|
|
||
|
// The user has no timetable
|
||
|
if (empty($this->week)) {
|
||
|
return $this->dateParser->getHours($start, $end);
|
||
|
}
|
||
|
|
||
|
foreach ($timetable as $slot) {
|
||
|
|
||
|
$isStartSlot = $this->dateParser->withinDateRange($start, $slot[0], $slot[1]);
|
||
|
$isEndSlot = $this->dateParser->withinDateRange($end, $slot[0], $slot[1]);
|
||
|
|
||
|
// Start and end are within the same time slot
|
||
|
if ($isStartSlot && $isEndSlot) {
|
||
|
return $this->dateParser->getHours($start, $end);
|
||
|
}
|
||
|
|
||
|
// We found the start slot
|
||
|
if (! $found_start && $isStartSlot) {
|
||
|
$found_start = true;
|
||
|
$hours = $this->dateParser->getHours($start, $slot[1]);
|
||
|
}
|
||
|
else if ($found_start) {
|
||
|
|
||
|
// We found the end slot
|
||
|
if ($isEndSlot) {
|
||
|
$hours += $this->dateParser->getHours($slot[0], $end);
|
||
|
break;
|
||
|
}
|
||
|
else {
|
||
|
|
||
|
// Sum hours of the intermediate time slots
|
||
|
$hours += $this->dateParser->getHours($slot[0], $slot[1]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The start date was not found in regular hours so we get the nearest time slot
|
||
|
if (! empty($timetable) && ! $found_start) {
|
||
|
$slot = $this->findClosestTimeSlot($start, $timetable);
|
||
|
|
||
|
if ($start < $slot[0]) {
|
||
|
return $this->calculateEffectiveDuration($user_id, $slot[0], $end);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $hours;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Find the nearest time slot
|
||
|
*
|
||
|
* @access public
|
||
|
* @param DateTime $date
|
||
|
* @param array $timetable
|
||
|
* @return array
|
||
|
*/
|
||
|
public function findClosestTimeSlot(DateTime $date, array $timetable)
|
||
|
{
|
||
|
$values = array();
|
||
|
|
||
|
foreach ($timetable as $slot) {
|
||
|
$t1 = abs($slot[0]->getTimestamp() - $date->getTimestamp());
|
||
|
$t2 = abs($slot[1]->getTimestamp() - $date->getTimestamp());
|
||
|
|
||
|
$values[] = min($t1, $t2);
|
||
|
}
|
||
|
|
||
|
asort($values);
|
||
|
return $timetable[key($values)];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the timetable for a user for a given date range
|
||
|
*
|
||
|
* @access public
|
||
|
* @param integer $user_id
|
||
|
* @param \DateTime $start
|
||
|
* @param \DateTime $end
|
||
|
* @return array
|
||
|
*/
|
||
|
public function calculate($user_id, DateTime $start, DateTime $end)
|
||
|
{
|
||
|
$timetable = array();
|
||
|
|
||
|
$this->day = $this->timetableDay->getByUser($user_id);
|
||
|
$this->week = $this->timetableWeek->getByUser($user_id);
|
||
|
$this->overtime = $this->timetableExtra->getByUserAndDate($user_id, $start->format('Y-m-d'), $end->format('Y-m-d'));
|
||
|
$this->timeoff = $this->timetableOff->getByUserAndDate($user_id, $start->format('Y-m-d'), $end->format('Y-m-d'));
|
||
|
|
||
|
for ($today = clone($start); $today <= $end; $today->add(new DateInterval('P1D'))) {
|
||
|
$week_day = $today->format('N');
|
||
|
$timetable = array_merge($timetable, $this->getWeekSlots($today, $week_day));
|
||
|
$timetable = array_merge($timetable, $this->getOvertimeSlots($today, $week_day));
|
||
|
}
|
||
|
|
||
|
return $timetable;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return worked time slots for the given day
|
||
|
*
|
||
|
* @access public
|
||
|
* @param \DateTime $today
|
||
|
* @param string $week_day
|
||
|
* @return array
|
||
|
*/
|
||
|
public function getWeekSlots(DateTime $today, $week_day)
|
||
|
{
|
||
|
$slots = array();
|
||
|
$dayoff = $this->getDayOff($today);
|
||
|
|
||
|
if (! empty($dayoff) && $dayoff['all_day'] == 1) {
|
||
|
return array();
|
||
|
}
|
||
|
|
||
|
foreach ($this->week as $slot) {
|
||
|
if ($week_day == $slot['day']) {
|
||
|
$slots = array_merge($slots, $this->getDayWorkSlots($slot, $dayoff, $today));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $slots;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the overtime time slots for the given day
|
||
|
*
|
||
|
* @access public
|
||
|
* @param \DateTime $today
|
||
|
* @param string $week_day
|
||
|
* @return array
|
||
|
*/
|
||
|
public function getOvertimeSlots(DateTime $today, $week_day)
|
||
|
{
|
||
|
$slots = array();
|
||
|
|
||
|
foreach ($this->overtime as $slot) {
|
||
|
|
||
|
$day = new DateTime($slot['date']);
|
||
|
|
||
|
if ($week_day == $day->format('N')) {
|
||
|
|
||
|
if ($slot['all_day'] == 1) {
|
||
|
$slots = array_merge($slots, $this->getDaySlots($today));
|
||
|
}
|
||
|
else {
|
||
|
$slots[] = $this->getTimeSlot($slot, $day);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $slots;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get worked time slots and remove time off
|
||
|
*
|
||
|
* @access public
|
||
|
* @param array $slot
|
||
|
* @param array $dayoff
|
||
|
* @param \DateTime $today
|
||
|
* @return array
|
||
|
*/
|
||
|
public function getDayWorkSlots(array $slot, array $dayoff, DateTime $today)
|
||
|
{
|
||
|
$slots = array();
|
||
|
|
||
|
if (! empty($dayoff) && $dayoff['start'] < $slot['end']) {
|
||
|
|
||
|
if ($dayoff['start'] > $slot['start']) {
|
||
|
$slots[] = $this->getTimeSlot(array('end' => $dayoff['start']) + $slot, $today);
|
||
|
}
|
||
|
|
||
|
if ($dayoff['end'] < $slot['end']) {
|
||
|
$slots[] = $this->getTimeSlot(array('start' => $dayoff['end']) + $slot, $today);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
$slots[] = $this->getTimeSlot($slot, $today);
|
||
|
}
|
||
|
|
||
|
return $slots;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get regular day work time slots
|
||
|
*
|
||
|
* @access public
|
||
|
* @param \DateTime $today
|
||
|
* @return array
|
||
|
*/
|
||
|
public function getDaySlots(DateTime $today)
|
||
|
{
|
||
|
$slots = array();
|
||
|
|
||
|
foreach ($this->day as $day) {
|
||
|
$slots[] = $this->getTimeSlot($day, $today);
|
||
|
}
|
||
|
|
||
|
return $slots;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the start and end time slot for a given day
|
||
|
*
|
||
|
* @access public
|
||
|
* @param array $slot
|
||
|
* @param \DateTime $today
|
||
|
* @return array
|
||
|
*/
|
||
|
public function getTimeSlot(array $slot, DateTime $today)
|
||
|
{
|
||
|
$date = $today->format('Y-m-d');
|
||
|
|
||
|
return array(
|
||
|
new DateTime($date.' '.$slot['start']),
|
||
|
new DateTime($date.' '.$slot['end']),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return day off time slot
|
||
|
*
|
||
|
* @access public
|
||
|
* @param \DateTime $today
|
||
|
* @return array
|
||
|
*/
|
||
|
public function getDayOff(DateTime $today)
|
||
|
{
|
||
|
foreach ($this->timeoff as $day) {
|
||
|
|
||
|
if ($day['date'] === $today->format('Y-m-d')) {
|
||
|
return $day;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return array();
|
||
|
}
|
||
|
}
|