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