Lines 69.91% 172 / 246
Methods 72.91% 35 / 48
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 getMaxWorkstationCount 100.00% 5 / 5 100.00% 1 / 1 3
 withCalculatedSlots 100.00% 4 / 4 100.00% 1 / 1 2
 withType 100.00% 5 / 5 100.00% 1 / 1 3
 withOutDoubles 100.00% 5 / 5 100.00% 1 / 1 3
 hasMatchOf 100.00% 4 / 4 100.00% 1 / 1 3
 withDateTime 100.00% 5 / 5 100.00% 1 / 1 3
 withDateTimeInRange 0.00% 0 / 8 0.00% 0 / 1 20
 getAvailableSecondsOnDateTime 100.00% 4 / 4 100.00% 1 / 1 2
 isOpenedByDate 100.00% 4 / 4 100.00% 1 / 1 3
 isOpened 100.00% 4 / 4 100.00% 1 / 1 3
 hasAppointment 100.00% 4 / 4 100.00% 1 / 1 3
 getSlotList 100.00% 5 / 5 100.00% 1 / 1 3
 getSlotListByType 0.00% 0 / 6 0.00% 0 / 1 20
 getConflicts 100.00% 18 / 18 100.00% 1 / 1 6
 hasOverlapWith 100.00% 6 / 6 100.00% 1 / 1 3
 hasDayOffOverride 0.00% 0 / 4 0.00% 0 / 1 12
 validateTimeRangesAndRules 0.00% 0 / 16 0.00% 0 / 1 6
 getSummerizedSlotCount 0.00% 0 / 5 0.00% 0 / 1 12
 getCalculatedSlotCount 0.00% 0 / 6 0.00% 0 / 1 6
 withScope 100.00% 4 / 4 100.00% 1 / 1 2
 withLessData 0.00% 0 / 4 0.00% 0 / 1 6
 [BO\Zmsentities\Collection\Base] __construct 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] getFirst 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] getLast 100.00% 3 / 3 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] sortByName 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] sortByContactName 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] sortByCustomKey 100.00% 4 / 4 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] sortByCustomStringKey 100.00% 7 / 7 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] __clone 100.00% 2 / 2 100.00% 1 / 1 2
 [BO\Zmsentities\Collection\Base] hasEntity 100.00% 4 / 4 100.00% 1 / 1 4
 [BO\Zmsentities\Collection\Base] getEntity 100.00% 4 / 4 100.00% 1 / 1 4
 [BO\Zmsentities\Collection\Base] addEntity 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] offsetSet 100.00% 9 / 9 100.00% 1 / 1 3
 [BO\Zmsentities\Collection\Base] addData 91.66% 11 / 12 0.00% 0 / 1 5.01
 [BO\Zmsentities\Collection\Base] addList 100.00% 3 / 3 100.00% 1 / 1 2
 [BO\Zmsentities\Collection\Base] getIds 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsentities\Collection\Base] getIdsCsv 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] getCsvForProperty 0.00% 0 / 5 0.00% 0 / 1 12
 [BO\Zmsentities\Collection\Base] getCsvForPropertyList 100.00% 11 / 11 100.00% 1 / 1 6
 [BO\Zmsentities\Collection\Base] withValueFor 0.00% 0 / 4 0.00% 0 / 1 6
 [BO\Zmsentities\Collection\Base] setJsonCompressLevel 0.00% 0 / 2 0.00% 0 / 1 6
 [BO\Zmsentities\Collection\Base] getAsArray 0.00% 0 / 4 0.00% 0 / 1 6
 [BO\Zmsentities\Collection\Base] jsonSerialize 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] __toString 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsentities\Collection\Base] getResolveLevel 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] setResolveLevel 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Collection\Base] withResolveLevel 100.00% 5 / 5 100.00% 1 / 1 2
 [BO\Zmsentities\Collection\Base] chunk 0.00% 0 / 9 0.00% 0 / 1 12
15class AvailabilityList extends Base
16{
17    public const string ENTITY_CLASS = '\BO\Zmsentities\Availability';
18
19    public function getMaxWorkstationCount(): mixed
20    {
21        $max = 0;
22        foreach ($this as $availability) {
23            if ($availability['workstationCount']['intern'] >  $max) {
24                $max = $availability['workstationCount']['intern'];
25            }
26        }
27        return $max;
28    }
29
30    public function withCalculatedSlots(): static
31    {
32        $list = clone $this;
33        foreach ($list as $key => $availability) {
34            $list[$key] = $availability->withCalculatedSlots();
35        }
36        return $list;
37    }
38
39    public function withType(string $type): static
40    {
41        $collection = new static();
42        foreach ($this as $availability) {
43            if ($availability->type == $type) {
44                $collection[] = clone $availability;
45            }
46        }
47        return $collection;
48    }
49
50    public function withOutDoubles(): static
51    {
52        $collection = new static();
53        foreach ($this as $availability) {
54            if (false === $collection->hasMatchOf($availability)) {
55                $collection[] = clone $availability;
56            }
57        }
58        return $collection;
59    }
60
61    public function hasMatchOf(Availability $availability): Availability|false
62    {
63        foreach ($this as $item) {
64            if ($item->isMatchOf($availability)) {
65                return $item;
66            }
67        }
68        return false;
69    }
70
71    public function withDateTime(\DateTimeInterface $dateTime): self
72    {
73        $list = new self();
74        foreach ($this as $availability) {
75            if ($availability->isOpenedOnDate($dateTime)) {
76                $list->addEntity($availability);
77            }
78        }
79        return $list;
80    }
81
82    public function withDateTimeInRange(\DateTimeInterface $startDateTime, \DateTimeInterface $endDateTime): mixed
83    {
84        $list = new self();
85        $currentDateTime = \BO\Zmsentities\Helper\DateTime::create($startDateTime);
86        while ($currentDateTime <= $endDateTime) {
87            foreach ($this as $availability) {
88                if ($availability->isOpenedOnDate($currentDateTime)) {
89                    $list->addEntity($availability);
90                }
91            }
92            $currentDateTime = $currentDateTime->modify('+1 day');
93        }
94        return $list->withOutDoubles();
95    }
96
97    public function getAvailableSecondsOnDateTime(\DateTimeInterface $dateTime, string $type = "intern"): mixed
98    {
99        $seconds = 0;
100        foreach ($this->withType('appointment')->withDateTime($dateTime) as $availability) {
101            $seconds += $availability->getAvailableSecondsPerDay($type);
102        }
103        return $seconds;
104    }
105
106    /*
107     * is opened on a day -> not specified by a time
108     */
109    public function isOpenedByDate(\DateTimeInterface $dateTime, string|false $type = false): bool
110    {
111        foreach ($this as $availability) {
112            if ($availability->isOpenedOnDate($dateTime, $type)) {
113                return true;
114            }
115        }
116        return false;
117    }
118
119    /*
120     * is opened on a day with specified time
121     */
122    public function isOpened(\DateTimeInterface $dateTime, string $type = "openinghours"): bool
123    {
124        foreach ($this as $availability) {
125            if ($availability->isOpened($dateTime, $type)) {
126                return true;
127            }
128        }
129        return false;
130    }
131
132    public function hasAppointment(\BO\Zmsentities\Appointment $appointment): bool
133    {
134        foreach ($this as $availability) {
135            if ($availability->hasAppointment($appointment)) {
136                return true;
137            }
138        }
139        return false;
140    }
141
142    public function getSlotList(): SlotList
143    {
144        $slotList = new SlotList();
145        foreach ($this as $availability) {
146            foreach ($availability->getSlotList() as $slot) {
147                $slotList->addEntity($slot);
148            }
149        }
150        return $slotList;
151    }
152
153    public function getSlotListByType(mixed $type): SlotList
154    {
155        $slotList = new SlotList();
156        foreach ($this as $availability) {
157            if ($availability->type == $type) {
158                foreach ($availability->getSlotList() as $slot) {
159                    $slotList->addEntity($slot);
160                }
161            }
162        }
163        return $slotList;
164    }
165
166    public function getConflicts(mixed $startDate, mixed $endDate): ProcessList
167    {
168        $processList = new ProcessList();
169        foreach ($this as $availability) {
170            $conflict = $availability->getConflict();
171            $currentDate = \BO\Zmsentities\Helper\DateTime::create($startDate);
172            $endDateTime = \BO\Zmsentities\Helper\DateTime::create($endDate);
173            while ($currentDate <= $endDateTime) {
174                if ($availability->isOpenedOnDate($currentDate)) {
175                    if ($conflict) {
176                        $conflictOnDay = clone $conflict;
177                        // to avoid overwrite time settings from availability getConflict lets modify
178                        $appointmentTime = $conflictOnDay->getFirstAppointment()->getStartTime()->format('H:i');
179                        $newDate = \BO\Zmsentities\Helper\DateTime::create($currentDate);
180                        $conflictOnDay->getFirstAppointment()->setDateTime($newDate->modify($appointmentTime));
181                        $processList->addEntity($conflictOnDay);
182                    }
183                    $overlapList = $this->hasOverlapWith($availability, $currentDate);
184                    if ($overlapList->count()) {
185                        $processList->addList($overlapList);
186                    }
187                }
188                $currentDate = $currentDate->modify('+1day');
189            }
190        }
191        return $processList;
192    }
193
194    public function hasOverlapWith(Availability $availability, \DateTimeInterface $currentDate): ProcessList
195    {
196        $processList = new ProcessList();
197        foreach ($this as $availabilityCompare) {
198            if ($availabilityCompare->isOpenedOnDate($currentDate)) {
199                $overlaps = $availability->getTimeOverlaps($availabilityCompare, $currentDate);
200                $processList->addList($overlaps);
201            }
202        }
203        return $processList;
204    }
205
206    public function hasDayOffOverride(): bool
207    {
208        foreach ($this as $availability) {
209            if ($availability->overridesDayOff()) {
210                return true;
211            }
212        }
213        return false;
214    }
215
216    public function validateTimeRangesAndRules(
217        \DateTimeImmutable $startDate,
218        \DateTimeImmutable $endDate,
219        \DateTimeImmutable $selectedDate,
220        string $kind,
221        mixed $startInDays,
222        mixed $endInDays,
223        array $weekday
224    ): array {
225        $errorList = [];
226
227        $today = new \DateTimeImmutable('now', $selectedDate->getTimezone());
228        $yesterday = (clone $selectedDate)->modify('-1 day');
229        $tomorrow = (clone $selectedDate)->modify('+1 day');
230
231        foreach ($this as $availability) {
232            $errorList = array_merge(
233                $errorList,
234                $availability->validateWeekdays($startDate, $endDate, $weekday, $kind),
235                $availability->validateStartTime($today, $tomorrow, $startDate, $selectedDate, $kind),
236                $availability->validateEndTime($startDate, $endDate),
237                $availability->validateOriginEndTime($today, $yesterday, $endDate, $selectedDate, $kind),
238                $availability->validateType($kind),
239                $availability->validateSlotTime($startDate, $endDate),
240                $availability->validateBookableDayRange((int) $startInDays, (int) $endInDays)
241            );
242        }
243        return $errorList;
244    }
245
246    /**
247     * @return array
248     */
249    public function getSummerizedSlotCount(): array
250    {
251        $slotCounts = [];
252        foreach ($this as $item) {
253            $itemId = ($item->id) ? $item->id : $item->tempId;
254            $slotCounts[$itemId] = (int) $item->getSlotList()->getSummerizedSlot()->intern;
255        }
256        return $slotCounts;
257    }
258
259    /**
260     * @return array
261     */
262    public function getCalculatedSlotCount(\BO\Zmsentities\Collection\ProcessList $processList): array
263    {
264        $slotCounts = [];
265        foreach ($this as $item) {
266            $itemId = $item->id;
267            $listWithAvailability = $processList->withAvailability($item);
268            $slotCounts[$itemId] = $listWithAvailability->getAppointmentList()->getCalculatedSlotCount();
269        }
270        return $slotCounts;
271    }
272
273
274    public function withScope(\BO\Zmsentities\Scope $scope): static
275    {
276        $list = clone $this;
277        foreach ($list as $key => $availability) {
278            $list[$key] = $availability->withScope($scope);
279        }
280        return $list;
281    }
282
283    /**
284     * @return static
285     */
286    #[\Override]
287    public function withLessData(array $keepArray = [])
288    {
289        /** @psalm-suppress UnsafeGenericInstantiation */
290        $list = new static();
291        foreach ($this as $availability) {
292            $list->addEntity(clone $availability->withLessData($keepArray));
293        }
294        return $list;
295    }
296}

Inherited from BO\Zmsentities\Collection\Base

32    public function __construct(object|array $array = [], int $flags = 0, string $iteratorClass = "ArrayIterator")
33    {
34        /** @var class-string<\ArrayIterator> $iteratorClass */
35        parent::__construct($array, $flags, $iteratorClass);
36    }
41    public function getFirst()
42    {
43        $item = $this->getIterator()->current();
44        return $item;
45    }
51    public function getLast()
52    {
53        $copy = $this->getArrayCopy();
54        $item = end($copy);
55        return $item;
56    }
62    public function sortByName()
63    {
64        $this->uasort(function ($a, $b) {
65            return strcmp(
66                Sorter::toSortableString(ucfirst($a->name ?? '')),
67                Sorter::toSortableString(ucfirst($b->name ?? ''))
68            );
69        });
70        return $this;
71    }
73    public function sortByContactName(): static
74    {
75        $this->uasort(function ($a, $b) {
76            return strcmp(
77                Sorter::toSortableString(ucfirst($a->contact['name'] ?? '')),
78                Sorter::toSortableString(ucfirst($b->contact['name'] ?? ''))
79            );
80        });
81        return $this;
82    }
84    public function sortByCustomKey(string $key): static
85    {
86        $this->uasort(function ($a, $b) use ($key): mixed {
87            return ($a[$key] - $b[$key]);
88        });
89        return $this;
90    }
92    public function sortByCustomStringKey(mixed $key): static
93    {
94        $this->uasort(function ($a, $b) use ($key) {
95            return strcmp(
96                Sorter::toSortableString(ucfirst($a[$key] ?? '')),
97                Sorter::toSortableString(ucfirst($b[$key] ?? ''))
98            );
99        });
100        return $this;
101    }
103    public function __clone()
104    {
105        foreach ($this as $key => $property) {
106            $this[$key] = clone $property;
107        }
108    }
110    public function hasEntity(mixed $primary): bool
111    {
112        foreach ($this as $entity) {
113            if (isset($entity->{$entity::PRIMARY}) && $primary == $entity->{$entity::PRIMARY}) {
114                return true;
115            }
116        }
117        return false;
118    }
124    public function getEntity(string|int $primary)
125    {
126        foreach ($this as $entity) {
127            if (isset($entity->{$entity::PRIMARY}) && $primary == $entity->{$entity::PRIMARY}) {
128                return $entity;
129            }
130        }
131        return null;
132    }
134    public function addEntity(\BO\Zmsentities\Schema\Entity $entity): static
135    {
136        /** @var T $entity */
137        $this->append($entity);
138        return $this;
139    }
146    public function offsetSet(mixed $index, mixed $value): void
147    {
148        $className = $this::ENTITY_CLASS;
149        if (is_a($value, $className)) {
150            /** @var T $entity */
151            $entity = $value;
152            /** @psalm-suppress PossiblyNullArgument */
153            parent::offsetSet($index, $entity);
154        } elseif (is_array($value)) {
155            /** @var T $entity */
156            $entity = new $className($value);
157            /** @psalm-suppress PossiblyNullArgument */
158            parent::offsetSet($index, $entity);
159        } else {
160            $given = get_debug_type($value);
161            throw new \Exception('Invalid entity ' . $given . ' for collection ' . __CLASS__);
162        }
163    }
165    public function addData(mixed $mergeData): static
166    {
167        foreach ($mergeData as $item) {
168            if ($item instanceof Entity) {
169                if (null === $item->getResolveLevel()) {
170                    $item->setResolveLevel($this->getResolveLevel());
171                }
172                $this->addEntity($item);
173            } else {
174                $className = $this::ENTITY_CLASS;
175                $entity = new $className($item);
176                if (!$entity instanceof Entity) {
177                    throw new \Exception('Invalid entity ' . get_debug_type($entity) . ' for collection ' . __CLASS__);
178                }
179                $entity->setResolveLevel($this->getResolveLevel());
180                $this->addEntity($entity);
181            }
182        }
183        return $this;
184    }
189    public function addList(Base $list): static
190    {
191        foreach ($list as $item) {
192            $this->addEntity($item);
193        }
194        return $this;
195    }
201    public function getIds()
202    {
203        $list = [];
204        foreach ($this as $entity) {
205            $list[] = $entity->id;
206        }
207        return $list;
208    }
210    public function getIdsCsv(): string
211    {
212        return implode(',', $this->getIds());
213    }
215    public function getCsvForProperty(mixed $propertyName, string $csvSeperator = ','): string
216    {
217        $list = [];
218        foreach ($this as $entry) {
219            if ($entry->hasProperty($propertyName)) {
220                $list[] = $entry->getProperty($propertyName);
221            }
222        }
223        return implode($csvSeperator, $list);
224    }
226    public function getCsvForPropertyList(array $propertyList, string $propertySeperator = '', string $csvSeperator = ','): string
227    {
228        $list = [];
229        foreach ($this as $entry) {
230            $propertyConcat = '';
231            foreach ($propertyList as $propertyName) {
232                if ($entry->hasProperty($propertyName)) {
233                    if ($propertyConcat) {
234                        $propertyConcat .= $propertySeperator;
235                    }
236                    $propertyConcat .= $entry->getProperty($propertyName);
237                }
238            }
239            if ($propertyConcat) {
240                $list[] = $propertyConcat;
241            }
242        }
243        return implode($csvSeperator, $list);
244    }
250    public function withValueFor(mixed $param, mixed $newValue): static
251    {
252        /** @psalm-suppress UnsafeGenericInstantiation */
253        $list = new static();
254        foreach ($this as $entry) {
255            $list[] = $entry->withProperty($param, $newValue);
256        }
257        return $list;
258    }
277    public function setJsonCompressLevel(mixed $jsonCompressLevel): void
278    {
279        foreach ($this as $item) {
280            $item->setJsonCompressLevel($jsonCompressLevel);
281        }
282    }
288    public function getAsArray(): array
289    {
290        $array = [];
291        foreach ($this as $item) {
292            $array[] = $item;
293        }
294
295        return $array;
296    }
299    public function jsonSerialize(): mixed
300    {
301        return $this->getArrayCopy();
302    }
304    public function __toString()
305    {
306        $list = [];
307        foreach ($this as $item) {
308            $list[] = $item->__toString();
309        }
310        return "[" . implode(',', $list) . "]";
311    }
316    public function getResolveLevel()
317    {
318        return $this->resolveLevel;
319    }
325    public function setResolveLevel($resolveLevel)
326    {
327        $this->resolveLevel = $resolveLevel;
328        return $this;
329    }
335    public function withResolveLevel($resolveLevel)
336    {
337        /** @psalm-suppress UnsafeGenericInstantiation */
338        $collection = new static();
339        foreach ($this as $entity) {
340            $reduced = $entity->withResolveLevel($resolveLevel);
341            $collection[] = $reduced;
342        }
343        return $collection;
344    }
350    public function chunk(mixed $length): array
351    {
352        /** @psalm-suppress UnsafeGenericInstantiation */
353        $chunks = [new static()];
354        $id = 0;
355        foreach ($this as $entry) {
356            $chunkIndex = (int) floor($id / $length);
357            if (! isset($chunks[$chunkIndex])) {
358                /** @psalm-suppress UnsafeGenericInstantiation */
359                $chunks[$chunkIndex] = new static();
360            }
361
362            $chunks[$chunkIndex][] = $entry;
363
364            $id++;
365        }
366
367        return $chunks;
368    }