Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.70% covered (warning)
87.70%
107 / 122
76.92% covered (warning)
76.92%
10 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
SlotList
87.70% covered (warning)
87.70%
107 / 122
76.92% covered (warning)
76.92%
10 / 13
58.22
0.00% covered (danger)
0.00%
0 / 1
 takeLowerSlotValue
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 setEmptySlotValues
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 isAvailableForAll
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getByDateTime
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 withSlotsForAppointment
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
9.01
 extendList
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 removeAppointment
75.00% covered (warning)
75.00%
12 / 16
0.00% covered (danger)
0.00%
0 / 1
7.77
 getSlot
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getSummerizedSlot
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 withTimeGreaterThan
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 getFreeProcesses
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
7
 withReducedSlots
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 __toString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace BO\Zmsentities\Collection;
4
5use BO\Zmsentities\Slot;
6
7/**
8 * @SuppressWarnings(Complexity)
9 */
10class SlotList extends Base
11{
12    public const ENTITY_CLASS = '\BO\Zmsentities\Slot';
13
14    /**
15     * Compare two slots and return the lower values
16     * @param array $slotA
17     * @param array $slotB
18     * @return array $slotA modified
19     */
20    public function takeLowerSlotValue($indexA, $indexB)
21    {
22        $slotA = $this[$indexA];
23        $slotB = $this[$indexB];
24        if (null !== $slotA && null !== $slotB) {
25            $slotA->type = Slot::REDUCED;
26            foreach (['public', 'intern'] as $type) {
27                $slotA[$type] = $slotA[$type] < $slotB[$type] ? $slotA[$type] : $slotB[$type];
28            }
29        }
30        return $this;
31    }
32
33    public function setEmptySlotValues($index)
34    {
35        $slot = $this->getSlot($index);
36        if (null !== $slot) {
37            $slot['public'] = 0;
38            $slot['intern'] = 0;
39            $slot->type = Slot::REDUCED;
40        }
41        return $this;
42    }
43
44    public function isAvailableForAll($slotType)
45    {
46        foreach ($this as $slot) {
47            if ($slot[$slotType] < 1) {
48                return false;
49            }
50        }
51        return true;
52    }
53
54    /**
55     * Get a slot for a given time
56     *
57     */
58    public function getByDateTime(\DateTimeInterface $dateTime)
59    {
60        foreach ($this as $slot) {
61            if ($slot->hasTime() && $slot->time == $dateTime->format('H:i')) {
62                return $slot;
63            }
64        }
65        return false;
66    }
67
68    /**
69     * Get all slots for an appointment
70     *
71     */
72    public function withSlotsForAppointment(\BO\Zmsentities\Appointment $appointment, $extendSlotList = false)
73    {
74        $slotList = new SlotList();
75        $takeFollowingSlot = 0;
76        $startTime = $appointment->toDateTime();
77        $currentSlot = null;
78        foreach ($this as $slot) {
79            $currentSlot = clone $slot;
80            if ($takeFollowingSlot > 0) {
81                $takeFollowingSlot--;
82                $slotList[] = $currentSlot;
83            }
84            if ($slot->hasTime() && $slot->time == $startTime->format('H:i')) {
85                $slotList[] = $currentSlot;
86                $takeFollowingSlot = $appointment['slotCount'] - 1;
87            }
88        }
89        if (0 < $takeFollowingSlot && ! $extendSlotList) {
90            throw new \BO\Zmsentities\Exception\AppointmentNotFitInSlotList(
91                "$appointment does not fit in $this"
92            );
93        }
94        if (0 < $takeFollowingSlot && $extendSlotList) {
95            $slotList = $this->extendList($slotList, $currentSlot, $appointment);
96        }
97        return $slotList;
98    }
99
100    public function extendList($slotList, $prevSlot, $appointment)
101    {
102        $startTime = \BO\Zmsentities\Helper\DateTime::create(
103            $appointment->toDateTime()->format('Y-m-d') . ' ' . $prevSlot->time
104        )->modify('+' . $appointment->getAvailability()->slotTimeInMinutes . 'minute');
105        $stopTime = $appointment->getEndTime();
106        do {
107            $slot = clone $prevSlot;
108            $slot->setTime($startTime);
109            $slotList[] = $slot;
110            $startTime = $startTime->modify('+' . $appointment->getAvailability()->slotTimeInMinutes . 'minute');
111            // Only add a slot, if at least a minute is left, otherwise do not ("<" instead "<=")
112        } while ($startTime->getTimestamp() < $stopTime->getTimestamp());
113        return $slotList;
114    }
115
116
117    /**
118     * Reduce free appointments on slot matching appointment
119     *
120     * -----------|----------------|------------
121     * first loop | intern slots 2 | slotCount 3
122     *
123     *
124     * @return bool true on success and false if no matching slot is found or no appointments are free
125     */
126
127
128    public function removeAppointment(\BO\Zmsentities\Appointment $appointment)
129    {
130        $takeFollowingSlot = 0;
131        $startTime = $appointment->toDateTime()->format('H:i');
132        $containsAppointment = false;
133        foreach ($this as $slot) {
134            if ($takeFollowingSlot > 0) {
135                if (0 == $slot['intern']) {
136                    return false;
137                }
138                $slot->removeAppointment();
139                $takeFollowingSlot--;
140            }
141            if ($slot->hasTime() && $slot->time == $startTime) {
142                $takeFollowingSlot = $appointment['slotCount'] - 1;
143                if (0 < $slot['intern']) {
144                    $containsAppointment = true;
145                    $slot->removeAppointment();
146                } else {
147                    return false;
148                }
149            }
150        }
151        return $containsAppointment;
152    }
153
154    public function getSlot($index)
155    {
156        $index = intval($index);
157        if (!isset($this[$index])) {
158            return null;
159        }
160        return $this[$index];
161    }
162
163    public function getSummerizedSlot($slot = null)
164    {
165        $sum = ($slot instanceof Slot) ? $slot : new Slot();
166        $sum->type = Slot::SUM;
167        foreach ($this as $slot) {
168            $sum['public'] += $slot['public'];
169            $sum['intern'] += $slot['intern'];
170        }
171        return $sum;
172    }
173
174    /*
175     * reduce slotlist from slots smaller than reference time (today) + 1800 seconds
176     */
177    public function withTimeGreaterThan(\DateTimeInterface $dateTime)
178    {
179        $slotList = clone $this;
180        $referenceTime = $dateTime->getTimestamp() + 1800;
181        foreach ($this as $index => $slot) {
182            $slotTime = \BO\Zmsentities\Helper\DateTime::create(
183                $dateTime->format('Y-m-d') . ' ' . $slot->time
184            )->getTimeStamp();
185            if ($referenceTime > $slotTime) {
186                $slotList->setEmptySlotValues($index);
187            }
188        }
189        return $slotList;
190    }
191
192    public function getFreeProcesses(
193        $selectedDate,
194        \BO\Zmsentities\Scope $scope,
195        \BO\Zmsentities\Availability $availability,
196        $slotType,
197        $requests,
198        $slotsRequired
199    ) {
200        $processList = new ProcessList();
201        foreach ($this as $slot) {
202            if ($slotsRequired > 1 && $slot->type != Slot::REDUCED) {
203                throw new \BO\Zmsentities\Exception\SlotRequiredWithoutReducing(
204                    "With $slotsRequired slots required, "
205                    . "do not use SlotList::getFreeProcesses without reduced slots: $slot"
206                );
207            }
208            if ($slot[$slotType] > 0) {
209                $appointment = new \BO\Zmsentities\Appointment(array(
210                    'scope' => $scope,
211                    'availability' => $availability,
212                    'slotCount' => $slotsRequired
213                ));
214                if (!$slot->hasTime()) {
215                    throw new \BO\Zmsentities\Exception\SlotMissingTime("Time on slot not set: $slot");
216                }
217                $appointment->setDateByString($selectedDate . ' ' . $slot->getTimeString());
218                $process = new \BO\Zmsentities\Process(array(
219                    'scope' => $scope,
220                    'requests' => $requests
221                ));
222                for ($count = 0; $count < $slot[$slotType]; $count++) {
223                    $process->addAppointment($appointment);
224                }
225                $processList[] = $process;
226            }
227        }
228        return $processList;
229    }
230
231    public function withReducedSlots($slotsRequired)
232    {
233        $slotList = clone $this;
234        if ($slotsRequired > 1) {
235            $slotLength = count($slotList);
236            for ($slotIndex = 0; $slotIndex < $slotLength; $slotIndex++) {
237                if ($slotIndex + $slotsRequired - 1 < $slotLength) {
238                    for ($slotRelative = 1; $slotRelative < $slotsRequired; $slotRelative++) {
239                        if ($slotIndex + $slotRelative < $slotLength) {
240                            $slotList->takeLowerSlotValue($slotIndex, $slotIndex + $slotRelative);
241                        }
242                    }
243                } else {
244                    $slotList->setEmptySlotValues($slotIndex);
245                }
246            }
247        }
248        return $slotList;
249    }
250
251    public function __toString()
252    {
253        $count = count($this);
254        $sum = $this->getSummerizedSlot();
255        return "slotlist#$count ∑$sum";
256    }
257}