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 * @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 array $slotA modified
22     *
23     */
24    public function takeLowerSlotValue(int $indexA, int $indexB)
25    {
26        $slotA = $this[$indexA];
27        $slotB = $this[$indexB];
28        if (null !== $slotA && null !== $slotB) {
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($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, $extendSlotList = false)
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 && $extendSlotList) {
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)
104    {
105        $startTime = \BO\Zmsentities\Helper\DateTime::create(
106            $appointment->toDateTime()->format('Y-m-d') . ' ' . $prevSlot->time
107        )->modify('+' . $appointment->getAvailability()->slotTimeInMinutes . 'minute');
108        $stopTime = $appointment->getEndTime();
109        do {
110            $slot = clone $prevSlot;
111            $slot->setTime($startTime);
112            $slotList[] = $slot;
113            $startTime = $startTime->modify('+' . $appointment->getAvailability()->slotTimeInMinutes . 'minute');
114            // Only add a slot, if at least a minute is left, otherwise do not ("<" instead "<=")
115        } while ($startTime->getTimestamp() < $stopTime->getTimestamp());
116        return $slotList;
117    }
118
119
120    /**
121     * Reduce free appointments on slot matching appointment
122     *
123     * -----------|----------------|------------
124     * first loop | intern slots 2 | slotCount 3
125     *
126     *
127     * @return bool true on success and false if no matching slot is found or no appointments are free
128     */
129
130
131    public function removeAppointment(\BO\Zmsentities\Appointment $appointment)
132    {
133        $takeFollowingSlot = 0;
134        $startTime = $appointment->toDateTime()->format('H:i');
135        $containsAppointment = false;
136        foreach ($this as $slot) {
137            if ($takeFollowingSlot > 0) {
138                if (0 == $slot['intern']) {
139                    return false;
140                }
141                $slot->removeAppointment();
142                $takeFollowingSlot--;
143            }
144            if ($slot->hasTime() && $slot->time == $startTime) {
145                $takeFollowingSlot = $appointment['slotCount'] - 1;
146                if (0 < $slot['intern']) {
147                    $containsAppointment = true;
148                    $slot->removeAppointment();
149                } else {
150                    return false;
151                }
152            }
153        }
154        return $containsAppointment;
155    }
156
157    public function getSlot($index): Slot|null
158    {
159        $index = intval($index);
160        if (!isset($this[$index])) {
161            return null;
162        }
163        return $this[$index];
164    }
165
166    public function getSummerizedSlot($slot = null): Slot
167    {
168        $sum = ($slot instanceof Slot) ? $slot : new Slot();
169        $sum->type = Slot::SUM;
170        foreach ($this as $slot) {
171            $sum['public'] += $slot['public'];
172            $sum['intern'] += $slot['intern'];
173        }
174        return $sum;
175    }
176
177    /*
178     * reduce slotlist from slots smaller than reference time (today) + 1800 seconds
179     */
180    public function withTimeGreaterThan(\DateTimeInterface $dateTime): static
181    {
182        $slotList = clone $this;
183        $referenceTime = $dateTime->getTimestamp() + 1800;
184        foreach ($this as $index => $slot) {
185            $slotTime = \BO\Zmsentities\Helper\DateTime::create(
186                $dateTime->format('Y-m-d') . ' ' . $slot->time
187            )->getTimeStamp();
188            if ($referenceTime > $slotTime) {
189                $slotList->setEmptySlotValues($index);
190            }
191        }
192        return $slotList;
193    }
194
195    public function getFreeProcesses(
196        $selectedDate,
197        \BO\Zmsentities\Scope $scope,
198        \BO\Zmsentities\Availability $availability,
199        $slotType,
200        $requests,
201        $slotsRequired
202    ): ProcessList {
203        $processList = new ProcessList();
204        foreach ($this as $slot) {
205            if ($slotsRequired > 1 && $slot->type != Slot::REDUCED) {
206                throw new \BO\Zmsentities\Exception\SlotRequiredWithoutReducing(
207                    "With $slotsRequired slots required, "
208                    . "do not use SlotList::getFreeProcesses without reduced slots: $slot"
209                );
210            }
211            if ($slot[$slotType] > 0) {
212                $appointment = new \BO\Zmsentities\Appointment(array(
213                    'scope' => $scope,
214                    'availability' => $availability,
215                    'slotCount' => $slotsRequired
216                ));
217                if (!$slot->hasTime()) {
218                    throw new \BO\Zmsentities\Exception\SlotMissingTime("Time on slot not set: $slot");
219                }
220                $appointment->setDateByString($selectedDate . ' ' . $slot->getTimeString());
221                $process = new \BO\Zmsentities\Process(array(
222                    'scope' => $scope,
223                    'requests' => $requests
224                ));
225                for ($count = 0; $count < $slot[$slotType]; $count++) {
226                    $process->addAppointment($appointment);
227                }
228                $processList[] = $process;
229            }
230        }
231        return $processList;
232    }
233
234    public function withReducedSlots($slotsRequired): static
235    {
236        $slotList = clone $this;
237        if ($slotsRequired > 1) {
238            $slotLength = count($slotList);
239            for ($slotIndex = 0; $slotIndex < $slotLength; $slotIndex++) {
240                if ($slotIndex + $slotsRequired - 1 < $slotLength) {
241                    for ($slotRelative = 1; $slotRelative < $slotsRequired; $slotRelative++) {
242                        if ($slotIndex + $slotRelative < $slotLength) {
243                            $slotList->takeLowerSlotValue($slotIndex, $slotIndex + $slotRelative);
244                        }
245                    }
246                } else {
247                    $slotList->setEmptySlotValues($slotIndex);
248                }
249            }
250        }
251        return $slotList;
252    }
253
254    public function __toString()
255    {
256        $count = count($this);
257        $sum = $this->getSummerizedSlot();
258        return "slotlist#$count ∑$sum";
259    }
260}