Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
99.68% |
316 / 317 |
|
96.77% |
30 / 31 |
CRAP | |
0.00% |
0 / 1 |
Availability | |
99.68% |
316 / 317 |
|
96.77% |
30 / 31 |
124 | |
0.00% |
0 / 1 |
getDefaults | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
1 | |||
hasDate | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
hasBookableDates | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
isOpenedOnDate | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
8 | |||
isOpened | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
3 | |||
hasWeekDay | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
hasAppointment | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
hasTime | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
getAvailableSecondsPerDay | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
hasDay | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
hasDayOff | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
hasWeek | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
10 | |||
getStartDateTime | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getEndDateTime | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getDuration | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getBookableStart | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
getBookableEnd | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
isBookable | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
7 | |||
getSlotList | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
getSlotTimeInMinutes | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
hasDateBetween | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
4 | |||
getConflict | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
2 | |||
isMatchOf | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
15 | |||
hasSharedWeekdayWith | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
9 | |||
getTimeOverlaps | |
100.00% |
38 / 38 |
|
100.00% |
1 / 1 |
12 | |||
withCalculatedSlots | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
2 | |||
withScope | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
__toString | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
3 | |||
offsetSet | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
isNewerThan | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
withLessData | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
9 |
1 | <?php |
2 | |
3 | namespace BO\Zmsentities; |
4 | |
5 | /** |
6 | * @SuppressWarnings(Complexity) |
7 | * @SuppressWarnings(Coupling) |
8 | * @SuppressWarnings(PublicMethod) |
9 | * |
10 | */ |
11 | class Availability extends Schema\Entity |
12 | { |
13 | public const PRIMARY = 'id'; |
14 | |
15 | public static $schema = "availability.json"; |
16 | |
17 | /** |
18 | * @var array $weekday english localized weekdays to avoid problems with setlocale() |
19 | */ |
20 | protected static $weekdayNameList = [ |
21 | 'sunday', |
22 | 'monday', |
23 | 'tuesday', |
24 | 'wednesday', |
25 | 'thursday', |
26 | 'friday', |
27 | 'saturday' |
28 | ]; |
29 | |
30 | /** |
31 | * Performance costs for modifying time are high, cache the calculated value |
32 | * @var \DateTimeImmutable $startTimeCache |
33 | */ |
34 | protected $startTimeCache; |
35 | |
36 | /** |
37 | * Performance costs for modifying time are high, cache the calculated value |
38 | * @var \DateTimeImmutable $endTimeCache |
39 | */ |
40 | protected $endTimeCache; |
41 | |
42 | /** |
43 | * Set Default values |
44 | */ |
45 | public function getDefaults() |
46 | { |
47 | return [ |
48 | 'id' => 0, |
49 | 'weekday' => array_fill_keys(self::$weekdayNameList, 0), |
50 | 'repeat' => [ |
51 | 'afterWeeks' => 1, |
52 | 'weekOfMonth' => 0, |
53 | ], |
54 | 'bookable' => [ |
55 | 'startInDays' => 1, |
56 | 'endInDays' => 60, |
57 | ], |
58 | 'workstationCount' => [ |
59 | 'public' => 0, |
60 | 'callcenter' => 0, |
61 | 'intern' => 0, |
62 | ], |
63 | 'lastChange' => 0, |
64 | 'multipleSlotsAllowed' => true, |
65 | 'slotTimeInMinutes' => 10, |
66 | 'startDate' => 0, |
67 | 'endDate' => 0, |
68 | 'startTime' => '0:00', |
69 | 'endTime' => '23:59', |
70 | 'type' => 'appointment', |
71 | 'scope' => [ |
72 | 'id' => 123, |
73 | 'provider' => [ |
74 | 'id' => 0, |
75 | 'name' => '', |
76 | 'source' => '' |
77 | ], |
78 | 'shortName' => '' |
79 | ] |
80 | ]; |
81 | } |
82 | |
83 | /** |
84 | * Check, if the dateTime contains a day given by the settings |
85 | * ATTENTION: Time critical function, keep highly optimized |
86 | * Compared to isOpened() the Booking time is checked too |
87 | * |
88 | * @param \DateTimeInterface $dateTime |
89 | * |
90 | * @return Bool |
91 | */ |
92 | public function hasDate(\DateTimeInterface $dateTime, \DateTimeInterface $now) |
93 | { |
94 | $dateTime = Helper\DateTime::create($dateTime); |
95 | if ( |
96 | !$this->isOpenedOnDate($dateTime) |
97 | || !$this->isBookable($dateTime, $now) |
98 | ) { |
99 | // Out of date range |
100 | return false; |
101 | } |
102 | return true; |
103 | } |
104 | |
105 | public function hasBookableDates(\DateTimeInterface $now) |
106 | { |
107 | if ($this->workstationCount['intern'] <= 0) { |
108 | return false; |
109 | } |
110 | if ($this->getEndDateTime()->getTimestamp() < $now->getTimestamp()) { |
111 | return false; |
112 | } |
113 | $stopDate = $this->getBookableEnd($now); |
114 | if ($this->getStartDateTime()->getTimestamp() > $stopDate->getTimestamp()) { |
115 | return false; |
116 | } |
117 | return $this->hasDateBetween($this->getBookableStart($now), $this->getBookableEnd($now), $now); |
118 | } |
119 | |
120 | /** |
121 | * Check, if the dateTime contains a day |
122 | * ATTENTION: Time critical function, keep highly optimized |
123 | * |
124 | * @param \DateTimeInterface $dateTime |
125 | * @param String $type of "openinghours", "appointment" or false to ignore type |
126 | * |
127 | * @return Bool |
128 | */ |
129 | public function isOpenedOnDate(\DateTimeInterface $dateTime, $type = false) |
130 | { |
131 | $dateTime = Helper\DateTime::create($dateTime); |
132 | if ( |
133 | !$this->hasWeekDay($dateTime) |
134 | || ($type !== false && $this->type != $type) |
135 | || !$this->hasDay($dateTime) |
136 | || !$this->hasWeek($dateTime) |
137 | || ($this->getDuration() > 2 && $this->hasDayOff($dateTime)) |
138 | ) { |
139 | // Out of date range |
140 | return false; |
141 | } |
142 | return true; |
143 | } |
144 | |
145 | /** |
146 | * Check if date and time is in availability |
147 | * Compared to hasDate() the time of the day is checked, but not booking time |
148 | * |
149 | * @param \DateTimeInterface $dateTime |
150 | * @param String $type of "openinghours", "appointment" or false to ignore type |
151 | * |
152 | */ |
153 | public function isOpened(\DateTimeInterface $dateTime, $type = false) |
154 | { |
155 | return (!$this->isOpenedOnDate($dateTime, $type) || !$this->hasTime($dateTime)) ? false : true; |
156 | } |
157 | |
158 | public function hasWeekDay(\DateTimeInterface $dateTime) |
159 | { |
160 | $weekDayName = self::$weekdayNameList[$dateTime->format('w')]; |
161 | if (!$this['weekday'][$weekDayName]) { |
162 | // Wrong weekday |
163 | return false; |
164 | } |
165 | return true; |
166 | } |
167 | |
168 | public function hasAppointment(Appointment $appointment) |
169 | { |
170 | $dateTime = $appointment->toDateTime(); |
171 | $isOpenedStart = $this->isOpened($dateTime, false); |
172 | $duration = $this->slotTimeInMinutes * $appointment->slotCount; |
173 | $endTime = $dateTime->modify("+" . $duration . "minutes") |
174 | ->modify("-1 second"); // To allow the last slot for an appointment |
175 | $isOpenedEnd = $this->isOpened($endTime, false); |
176 | return ($isOpenedStart && $isOpenedEnd); |
177 | } |
178 | |
179 | /** |
180 | * Check, if the dateTime is a time covered by availability |
181 | * |
182 | * @param \DateTimeInterface $dateTime |
183 | * |
184 | * @return Bool |
185 | */ |
186 | public function hasTime(\DateTimeInterface $dateTime) |
187 | { |
188 | $start = $this->getStartDateTime()->getSecondsOfDay(); |
189 | $end = $this->getEndDateTime()->getSecondsOfDay(); |
190 | $compare = Helper\DateTime::create($dateTime)->getSecondsOfDay(); |
191 | if ($start > $compare || $end <= $compare) { |
192 | // Out of time range |
193 | return false; |
194 | } |
195 | return true; |
196 | } |
197 | |
198 | public function getAvailableSecondsPerDay($type = "intern") |
199 | { |
200 | $start = $this->getStartDateTime()->getSecondsOfDay(); |
201 | $end = $this->getEndDateTime()->getSecondsOfDay(); |
202 | return ($end - $start) * $this->workstationCount[$type]; |
203 | } |
204 | |
205 | /** |
206 | * Check, if the dateTime is a day covered by availability |
207 | * |
208 | * @param \DateTimeInterface $dateTime |
209 | * |
210 | * @return Bool |
211 | */ |
212 | public function hasDay(\DateTimeInterface $dateTime) |
213 | { |
214 | $start = $this->getStartDateTime()->modify('0:00:00'); |
215 | $end = $this->getEndDateTime()->modify('23:59:59'); |
216 | if ($dateTime->getTimestamp() < $start->getTimestamp() || $dateTime->getTimestamp() > $end->getTimestamp()) { |
217 | // Out of date range |
218 | return false; |
219 | } |
220 | return true; |
221 | } |
222 | |
223 | /** |
224 | * Check, if the dateTime is a dayoff date |
225 | * |
226 | * @param \DateTimeInterface $dateTime |
227 | * |
228 | * @return Bool |
229 | */ |
230 | public function hasDayOff(\DateTimeInterface $dateTime) |
231 | { |
232 | if (isset($this['scope']['dayoff'])) { |
233 | $timeStamp = $dateTime->format('Y-m-d'); |
234 | foreach ($this['scope']['dayoff'] as $dayOff) { |
235 | if (date('Y-m-d', $dayOff['date']) == $timeStamp) { |
236 | return true; |
237 | } |
238 | } |
239 | } else { |
240 | throw new Exception\DayoffMissing(); |
241 | } |
242 | return false; |
243 | } |
244 | |
245 | /** |
246 | * Check, if the dateTime contains a week given by the week repetition settings |
247 | * |
248 | * @param \DateTimeInterface $dateTime |
249 | * |
250 | * @return Bool |
251 | */ |
252 | public function hasWeek(\DateTimeInterface $dateTime) |
253 | { |
254 | $dateTime = Helper\DateTime::create($dateTime); |
255 | $start = $this->getStartDateTime(); |
256 | $monday = "monday this week"; |
257 | if ( |
258 | $this['repeat']['afterWeeks'] |
259 | && ($this['repeat']['afterWeeks'] == 1 |
260 | || 0 === |
261 | $dateTime->modify($monday)->diff($start->modify($monday))->days |
262 | % ($this['repeat']['afterWeeks'] * 7) |
263 | ) |
264 | ) { |
265 | return true; |
266 | } |
267 | if ( |
268 | $this['repeat']['weekOfMonth'] |
269 | && ( |
270 | $dateTime->isWeekOfMonth($this['repeat']['weekOfMonth']) |
271 | // On a value of 5, always take the last week |
272 | || ($this['repeat']['weekOfMonth'] >= 5 && $dateTime->isLastWeekOfMonth()) |
273 | ) |
274 | ) { |
275 | return true; |
276 | } |
277 | if (!$this['repeat']['weekOfMonth'] && !$this['repeat']['afterWeeks']) { |
278 | return true; |
279 | } |
280 | return false; |
281 | } |
282 | |
283 | /** |
284 | * Get DateTimeInterface for start time of availability |
285 | * |
286 | * @return \DateTimeInterface |
287 | */ |
288 | public function getStartDateTime() |
289 | { |
290 | if (!$this->startTimeCache) { |
291 | $this->startTimeCache = Helper\DateTime::create() |
292 | ->setTimestamp($this['startDate']) |
293 | ->modify('today ' . $this['startTime']); |
294 | } |
295 | return $this->startTimeCache; |
296 | } |
297 | |
298 | /** |
299 | * Get DateTimeInterface for end time of availability |
300 | * |
301 | * @return \DateTimeInterface |
302 | */ |
303 | public function getEndDateTime() |
304 | { |
305 | if (!$this->endTimeCache) { |
306 | $this->endTimeCache = Helper\DateTime::create() |
307 | ->setTimestamp($this['endDate']) |
308 | ->modify('today ' . $this['endTime']); |
309 | } |
310 | return $this->endTimeCache; |
311 | } |
312 | |
313 | /** |
314 | * Get duration of availability |
315 | * |
316 | * @return integer |
317 | */ |
318 | public function getDuration() |
319 | { |
320 | $startTime = $this->getStartDateTime(); |
321 | $endTime = $this->getEndDateTime(); |
322 | return (int)$endTime->diff($startTime)->format("%a"); |
323 | } |
324 | |
325 | /** |
326 | * Get DateTimeInterface for start booking time of availability |
327 | * |
328 | * @param \DateTimeInterface $now relative time to compare booking settings |
329 | * |
330 | * @return \DateTimeInterface |
331 | */ |
332 | public function getBookableStart(\DateTimeInterface $now) |
333 | { |
334 | $now = Helper\DateTime::create($now); |
335 | $availabilityStart = Helper\Property::create($this)->bookable->startInDays->get(); |
336 | $time = $this->getStartDateTime()->format('H:i:s'); |
337 | if (null !== $availabilityStart) { |
338 | return $now->modify('+' . $availabilityStart . 'days')->modify($time); |
339 | } |
340 | $scopeStart = Helper\Property::create($this)->scope->preferences->appointment->startInDaysDefault->get(); |
341 | if (null !== $scopeStart) { |
342 | return $now->modify('+' . $scopeStart . 'days')->modify($time); |
343 | } |
344 | throw new \BO\Zmsentities\Exception\ProcessBookableFailed( |
345 | "Undefined start time for booking, try to set the scope properly" |
346 | ); |
347 | } |
348 | |
349 | /** |
350 | * Get DateTimeInterface for end booking time of availability |
351 | * |
352 | * @param \DateTimeInterface $now relative time to compare booking settings |
353 | * |
354 | * @return \DateTimeInterface |
355 | */ |
356 | public function getBookableEnd(\DateTimeInterface $now) |
357 | { |
358 | $now = Helper\DateTime::create($now); |
359 | $availabilityEnd = Helper\Property::create($this)->bookable->endInDays->get(); |
360 | $time = $this->getEndDateTime()->format('H:i:s'); |
361 | if (null !== $availabilityEnd) { |
362 | return $now->modify('+' . $availabilityEnd . 'days')->modify($time); |
363 | } |
364 | $scopeEnd = Helper\Property::create($this)->scope->preferences->appointment->endInDaysDefault->get(); |
365 | if (null !== $scopeEnd) { |
366 | return $now->modify('+' . $scopeEnd . 'days')->modify($time); |
367 | } |
368 | throw new \BO\Zmsentities\Exception\ProcessBookableFailed( |
369 | "Undefined end time for booking, try to set the scope properly" |
370 | ); |
371 | } |
372 | |
373 | /** |
374 | * Check, if the dateTime contains is within the bookable range (usually for public access) |
375 | * The current time is used to compare the start Time of the availability |
376 | * |
377 | * @param \DateTimeInterface $dateTime |
378 | * @param \DateTimeInterface $now relative time to compare booking settings |
379 | * |
380 | * @return Bool |
381 | */ |
382 | public function isBookable(\DateTimeInterface $bookableDate, \DateTimeInterface $now) |
383 | { |
384 | if (!$this->hasDay($bookableDate)) { |
385 | return false; |
386 | } |
387 | $bookableCurrentTime = $bookableDate->modify($now->format('H:i:s')); |
388 | Helper\DateTime::create($bookableDate)->getTimestamp() + Helper\DateTime::create($now)->getSecondsOfDay(); |
389 | $startDate = $this->getBookableStart($now)->modify('00:00:00'); |
390 | |
391 | if ($bookableCurrentTime->getTimestamp() < $startDate->getTimestamp()) { |
392 | //error_log("START " . $bookableCurrentTime->format('c').'<'.$startDate->format('c'). " " . $this); |
393 | return false; |
394 | } |
395 | $endDate = $this->getBookableEnd($now)->modify('23:59:59'); |
396 | if ($bookableCurrentTime->getTimestamp() > $endDate->getTimestamp()) { |
397 | //error_log("END " . $bookableCurrentTime->format('c').'>'.$endDate->format('c'). " " . $this); |
398 | return false; |
399 | } |
400 | if ( |
401 | $bookableDate->format('Y-m-d') == $endDate->format('Y-m-d') |
402 | && $now->format('Y-m-d') != $this->getEndDateTime()->format('Y-m-d') |
403 | ) { |
404 | // Avoid releasing all appointments on midnight, allow smaller contingents distributed over the day |
405 | $delayedStart = $this->getBookableEnd($now)->modify($this->getStartDateTime()->format('H:i:s')); |
406 | if ($bookableCurrentTime->getTimestamp() < $delayedStart->getTimestamp()) { |
407 | //error_log( |
408 | // sprintf("DELAY %s<%s", $bookableCurrentTime->format('c'), $delayedStart->format('c')) |
409 | // ." $this" |
410 | //); |
411 | return false; |
412 | } |
413 | } |
414 | return true; |
415 | } |
416 | |
417 | /** |
418 | * Creates a list of slots available on a valid day |
419 | * |
420 | * @return Array of arrays with the keys time, public, callcenter, intern |
421 | */ |
422 | public function getSlotList() |
423 | { |
424 | $startTime = Helper\DateTime::create($this['startTime']); |
425 | $stopTime = Helper\DateTime::create($this['endTime']); |
426 | $slotList = new Collection\SlotList(); |
427 | $slotInstance = new Slot($this['workstationCount']); |
428 | if ($this['slotTimeInMinutes'] > 0) { |
429 | do { |
430 | $slot = clone $slotInstance; |
431 | $slot->setTime($startTime); |
432 | $slotList[] = $slot; |
433 | $startTime = $startTime->modify('+' . $this['slotTimeInMinutes'] . 'minute'); |
434 | // Only add a slot, if at least a minute is left, otherwise do not ("<" instead "<=") |
435 | } while ($startTime->getTimestamp() < $stopTime->getTimestamp()); |
436 | } |
437 | return $slotList; |
438 | } |
439 | |
440 | public function getSlotTimeInMinutes() |
441 | { |
442 | return $this['slotTimeInMinutes']; |
443 | } |
444 | |
445 | |
446 | /** |
447 | * Check, if a day between two dates is included |
448 | * |
449 | * @return Array of arrays with the keys time, public, callcenter, intern |
450 | */ |
451 | public function hasDateBetween(\DateTimeInterface $startTime, \DateTimeInterface $stopTime, \DateTimeInterface $now) |
452 | { |
453 | if ($startTime->getTimestamp() < $now->getTimestamp()) { |
454 | $startTime = $now; |
455 | } |
456 | if ($stopTime->getTimestamp() < $now->getTimestamp()) { |
457 | return false; |
458 | } |
459 | do { |
460 | if ($this->hasDate($startTime, $now)) { |
461 | return true; |
462 | } |
463 | $startTime = $startTime->modify('+1 day'); |
464 | } while ($startTime->getTimestamp() <= $stopTime->getTimestamp()); |
465 | return false; |
466 | } |
467 | |
468 | /** |
469 | * Get problems on configuration of this availability |
470 | * |
471 | * @return Collection\ProcessList with processes in status "conflict" |
472 | */ |
473 | public function getConflict() |
474 | { |
475 | $start = $this->getStartDateTime()->getSecondsOfDay(); |
476 | $end = $this->getEndDateTime()->getSecondsOfDay(); |
477 | $minutesPerDay = floor(($end - $start) / 60); |
478 | if ($minutesPerDay % $this->slotTimeInMinutes > 0) { |
479 | $conflict = new Process(); |
480 | $conflict->status = 'conflict'; |
481 | $appointment = $conflict->getFirstAppointment(); |
482 | $appointment->availability = $this; |
483 | $appointment->date = $this->getStartDateTime()->getTimestamp(); |
484 | $conflict->amendment = |
485 | "Der eingestellte Zeitschlitz von {$this->slotTimeInMinutes} Minuten" |
486 | . " sollte in die eingestellte Uhrzeit passen."; |
487 | return $conflict; |
488 | } |
489 | return false; |
490 | } |
491 | |
492 | /** |
493 | * Check of a different availability has the same opening configuration |
494 | * |
495 | */ |
496 | public function isMatchOf(Availability $availability) |
497 | { |
498 | return ($this->type != $availability->type |
499 | || $this->startTime != $availability->startTime |
500 | || $this->endTime != $availability->endTime |
501 | || $this->startDate != $availability->startDate |
502 | || $this->endDate != $availability->endDate |
503 | || $this->repeat['afterWeeks'] != $availability->repeat['afterWeeks'] |
504 | || $this->repeat['weekOfMonth'] != $availability->repeat['weekOfMonth'] |
505 | || (bool)$this->weekday['monday'] != (bool)$availability->weekday['monday'] |
506 | || (bool)$this->weekday['tuesday'] != (bool)$availability->weekday['tuesday'] |
507 | || (bool)$this->weekday['wednesday'] != (bool)$availability->weekday['wednesday'] |
508 | || (bool)$this->weekday['thursday'] != (bool)$availability->weekday['thursday'] |
509 | || (bool)$this->weekday['friday'] != (bool)$availability->weekday['friday'] |
510 | || (bool)$this->weekday['saturday'] != (bool)$availability->weekday['saturday'] |
511 | || (bool)$this->weekday['sunday'] != (bool)$availability->weekday['sunday'] |
512 | ) ? false : true; |
513 | } |
514 | |
515 | public function hasSharedWeekdayWith(Availability $availability) |
516 | { |
517 | return ($this->type == $availability->type |
518 | && (bool)$this->weekday['monday'] != (bool)$availability->weekday['monday'] |
519 | && (bool)$this->weekday['tuesday'] != (bool)$availability->weekday['tuesday'] |
520 | && (bool)$this->weekday['wednesday'] != (bool)$availability->weekday['wednesday'] |
521 | && (bool)$this->weekday['thursday'] != (bool)$availability->weekday['thursday'] |
522 | && (bool)$this->weekday['friday'] != (bool)$availability->weekday['friday'] |
523 | && (bool)$this->weekday['saturday'] != (bool)$availability->weekday['saturday'] |
524 | && (bool)$this->weekday['sunday'] != (bool)$availability->weekday['sunday'] |
525 | ) ? false : true; |
526 | } |
527 | |
528 | /** |
529 | * Get overlaps on daytime |
530 | * This functions does not check, if two availabilities are openend on the same day! |
531 | * |
532 | * @param Availability $availability for comparision |
533 | * |
534 | * @return Collection\ProcessList with processes in status "conflict" |
535 | * |
536 | * |
537 | */ |
538 | |
539 | /* |
540 | 1 |
541 | Case 01: |-----| |
542 | |-----| |
543 | 2 |
544 | |
545 | 1 |
546 | Case 02: |-----| |
547 | |-----| |
548 | 2 |
549 | |
550 | 1 |
551 | Case 03: |-----| |
552 | |-----| |
553 | 2 |
554 | |
555 | 1 |
556 | Case 04: |---------| |
557 | |-----| |
558 | 2 |
559 | |
560 | 1 |
561 | Case 05: |-----| |
562 | |---------| |
563 | 2 |
564 | |
565 | 1 |
566 | Case 06: |-----| |
567 | |-----| |
568 | 2 |
569 | |
570 | 1 |
571 | Case 07: |-----| |
572 | |-----| |
573 | 2 |
574 | |
575 | 1 |
576 | Case 08: |-----| |
577 | |-----| |
578 | 2 |
579 | |
580 | 1 |
581 | Case 09: |-----| |
582 | |-----| |
583 | 2 |
584 | |
585 | 1 |
586 | Case 10: | |
587 | |-----| |
588 | 2 |
589 | |
590 | 1 |
591 | Case 11: |-----| |
592 | | |
593 | 2 |
594 | |
595 | 1 |
596 | Case 12: | |
597 | |-----| |
598 | 2 |
599 | |
600 | 1 |
601 | Case 13: | |
602 | |-----| |
603 | 2 |
604 | |
605 | 1 |
606 | Case 14: |-----| |
607 | | |
608 | 2 |
609 | |
610 | 1 |
611 | Case 15: |-----| |
612 | | |
613 | 2 |
614 | |
615 | 1 |
616 | Case 16: | |
617 | | |
618 | 2 |
619 | |
620 | | | Operlap | Overlap |
621 | Case | Example | Open Interval | Closed Interval |
622 | --------|-------------------------|---------------|----------------- |
623 | Case 01 | 09:00-11:00 09:00-11:00 | Yes | Yes |
624 | Case 02 | 09:00-11:00 10:00-12:00 | Yes | Yes |
625 | Case 03 | 10:00-12:00 09:00-11:00 | Yes | Yes |
626 | Case 04 | 09:00-12:00 10:00-11:00 | Yes | Yes |
627 | Case 05 | 10:00-11:00 09:00-12:00 | Yes | Yes |
628 | Case 06 | 09:00-10:00 11:00-12:00 | No | No |
629 | Case 07 | 11:00-12:00 09:00-10:00 | No | No |
630 | Case 08 | 09:00-10:00 10:00-11:00 | No | Yes |
631 | Case 09 | 10:00-11:00 09:00-10:00 | No | Yes |
632 | Case 10 | 10:00-10:00 09:00-11:00 | Yes | Yes |
633 | Case 11 | 09:00-11:00 10:00-10:00 | Yes | Yes |
634 | Case 12 | 09:00-09:00 09:00-10:00 | No | Yes |
635 | Case 13 | 10:00-10:00 09:00-10:00 | No | Yes |
636 | Case 14 | 09:00-10:00 09:00-09:00 | No | Yes |
637 | Case 15 | 09:00-10:00 10:00-10:00 | No | Yes |
638 | Case 16 | 09:00-09:00 09:00-09:00 | No | Yes |
639 | */ |
640 | |
641 | public function getTimeOverlaps(Availability $availability, \DateTimeInterface $currentDate) |
642 | { |
643 | $processList = new Collection\ProcessList(); |
644 | if ( |
645 | $availability->id != $this->id |
646 | && $availability->type == $this->type |
647 | && $this->hasSharedWeekdayWith($availability) |
648 | ) { |
649 | $processTemplate = new Process(); |
650 | $processTemplate->amendment = "Zwei Öffnungszeiten überschneiden sich."; |
651 | $processTemplate->status = 'conflict'; |
652 | $appointment = $processTemplate->getFirstAppointment(); |
653 | $appointment->availability = $this; |
654 | $appointment->date = $this->getStartDateTime()->getTimestamp(); |
655 | $thisStart = $this->getStartDateTime()->getSecondsOfDay(); |
656 | $thisEnd = $this->getEndDateTime()->getSecondsOfDay(); |
657 | $availabilityStart = $availability->getStartDateTime()->getSecondsOfDay(); |
658 | $availabilityEnd = $availability->getEndDateTime()->getSecondsOfDay(); |
659 | |
660 | $isEqual = ($availabilityStart == $thisStart && $availabilityEnd == $thisEnd); |
661 | |
662 | if ($availabilityStart < $thisEnd && $thisStart < $availabilityEnd && ! $isEqual) { |
663 | $process = clone $processTemplate; |
664 | $process->getFirstAppointment()->date = $this |
665 | ->getStartDateTime() |
666 | ->modify($currentDate->format("Y-m-d")) |
667 | ->getTimestamp(); |
668 | $processList->addEntity($process); |
669 | } elseif ($thisEnd < $availabilityStart && $availabilityEnd < $thisStart && ! $isEqual) { |
670 | $process = clone $processTemplate; |
671 | $process->getFirstAppointment()->date = $availability |
672 | ->getStartDateTime() |
673 | ->modify($currentDate->format("Y-m-d")) |
674 | ->getTimestamp(); |
675 | $processList->addEntity($process); |
676 | } elseif ($isEqual) { |
677 | $process = clone $processTemplate; |
678 | $process->amendment = "Zwei Öffnungszeiten sind gleich."; |
679 | $process->getFirstAppointment()->date = $availability |
680 | ->getStartDateTime() |
681 | ->modify($currentDate->format("Y-m-d")) |
682 | ->getTimestamp(); |
683 | $processList->addEntity($process); |
684 | } |
685 | } |
686 | return $processList; |
687 | } |
688 | |
689 | /** |
690 | * Update workstationCount to number of calculated appointments |
691 | * |
692 | * @return self cloned |
693 | */ |
694 | public function withCalculatedSlots() |
695 | { |
696 | $availability = clone $this; |
697 | $startTime = Helper\DateTime::create($this['startTime']); |
698 | $stopTime = Helper\DateTime::create($this['endTime']); |
699 | $openingSeconds = $stopTime->getTimestamp() - $startTime->getTimestamp(); |
700 | $openingMinutes = floor($openingSeconds / 60); |
701 | $slices = 0; |
702 | if ($this['slotTimeInMinutes'] > 0) { |
703 | $slices = floor($openingMinutes / $this['slotTimeInMinutes']); |
704 | } |
705 | $slot = new Slot([ |
706 | 'type' => Slot::FREE, |
707 | 'intern' => $this['workstationCount']['intern'] * $slices, |
708 | 'callcenter' => $this['workstationCount']['callcenter'] * $slices, |
709 | 'public' => $this['workstationCount']['public'] * $slices, |
710 | ]); |
711 | $availability['workstationCount'] = $slot; |
712 | return $availability; |
713 | } |
714 | |
715 | public function withScope(\BO\Zmsentities\Scope $scope) |
716 | { |
717 | $availability = clone $this; |
718 | $availability->scope = $scope; |
719 | return $availability; |
720 | } |
721 | |
722 | public function __toString() |
723 | { |
724 | $info = "Availability." . $this['type'] . " #" . $this['id']; |
725 | $info .= " starting " . $this->startDate . $this->getStartDateTime()->format(' Y-m-d'); |
726 | $info .= "||now+" . $this['bookable']['startInDays'] . " "; |
727 | $info .= " until " . $this->getEndDateTime()->format('Y-m-d'); |
728 | $info .= "||now+" . $this['bookable']['endInDays'] . " "; |
729 | if ($this['repeat']['afterWeeks']) { |
730 | $info .= " every " . $this['repeat']['afterWeeks'] . " week(s)"; |
731 | } |
732 | if ($this['repeat']['weekOfMonth']) { |
733 | $info .= " each " . $this['repeat']['weekOfMonth'] . ". weekOfMonth"; |
734 | } |
735 | $info .= " on "; |
736 | $weekdays = array_filter($this['weekday'], function ($value) { |
737 | return $value > 0; |
738 | }); |
739 | $info .= implode(',', array_keys($weekdays)); |
740 | $info .= " from " . $this->getStartDateTime()->format('H:i'); |
741 | $info .= " to " . $this->getEndDateTime()->format('H:i'); |
742 | $info .= " using " . $this['slotTimeInMinutes'] . "min slots"; |
743 | $info .= " with p{$this['workstationCount']['public']}/"; |
744 | $info .= "c{$this['workstationCount']['callcenter']}/"; |
745 | $info .= "i{$this['workstationCount']['intern']}"; |
746 | $day = $this->getSlotList()->getSummerizedSlot(); |
747 | $info .= " day $day"; |
748 | return $info; |
749 | } |
750 | |
751 | /** |
752 | * Delete cache on changes |
753 | * |
754 | */ |
755 | public function offsetSet($index, $value) |
756 | { |
757 | $this->startTimeCache = null; |
758 | $this->endTimeCache = null; |
759 | return parent::offsetSet($index, $value); |
760 | } |
761 | |
762 | /** |
763 | * Check if availability is newer than given time |
764 | * |
765 | * @return bool |
766 | */ |
767 | public function isNewerThan(\DateTimeInterface $dateTime) |
768 | { |
769 | return ($dateTime->getTimestamp() < $this->lastChange); |
770 | } |
771 | |
772 | /** |
773 | * Reduce data of dereferenced entities to a required minimum |
774 | * |
775 | */ |
776 | public function withLessData(array $keepArray = []) |
777 | { |
778 | $entity = clone $this; |
779 | if (! in_array('repeat', $keepArray)) { |
780 | unset($entity['repeat']); |
781 | } |
782 | if (! in_array('id', $keepArray)) { |
783 | unset($entity['id']); |
784 | } |
785 | if (! in_array('bookable', $keepArray)) { |
786 | unset($entity['bookable']); |
787 | } |
788 | if (! in_array('workstationCount', $keepArray)) { |
789 | unset($entity['workstationCount']); |
790 | } |
791 | if (! in_array('multipleSlotsAllowed', $keepArray)) { |
792 | unset($entity['multipleSlotsAllowed']); |
793 | } |
794 | if (! in_array('lastChange', $keepArray)) { |
795 | unset($entity['lastChange']); |
796 | } |
797 | if (! in_array('slotTimeInMinutes', $keepArray)) { |
798 | unset($entity['slotTimeInMinutes']); |
799 | } |
800 | if (! in_array('description', $keepArray)) { |
801 | unset($entity['description']); |
802 | } |
803 | |
804 | return $entity; |
805 | } |
806 | } |