Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.77% covered (success)
90.77%
177 / 195
92.59% covered (success)
92.59%
25 / 27
CRAP
0.00% covered (danger)
0.00%
0 / 1
QueueList
90.77% covered (success)
90.77%
177 / 195
92.59% covered (success)
92.59%
25 / 27
81.66
0.00% covered (danger)
0.00%
0 / 1
 setWaitingTimePreferences
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 setTransferedProcessList
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getProcessTimeAverage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getWorkstationCount
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 withEstimatedWaitingTime
100.00% covered (success)
100.00%
46 / 46
100.00% covered (success)
100.00%
1 / 1
10
 withWaitingTime
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 withSortedArrival
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 withSortedWaitingTime
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 withAppointment
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 withOutAppointment
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getEstimatedWaitingTime
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 withFakeWaitingnumber
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 getFakeOrLastWaitingnumber
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getQueueByNumber
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getNextProcess
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
5
 getQueuePositionByNumber
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getCountWithWaitingTime
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 withStatus
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 withoutStatus
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 withShortNameDestinationHint
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 withPickupDestination
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 toProcessList
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 withoutDublicates
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 getWaitingNumberList
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getWaitingNumberListCsv
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withSelectedProcessFirst
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 sortByCallTime
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace BO\Zmsentities\Collection;
4
5/**
6 * @SuppressWarnings(Complexity)
7 * @SuppressWarnings(PublicMethod)
8 *
9 */
10class QueueList extends Base implements \BO\Zmsentities\Helper\NoSanitize
11{
12    const ENTITY_CLASS = '\BO\Zmsentities\Queue';
13
14    const FAKE_WAITINGNUMBER = -1;
15
16    const STATUS_IGNORE = ['called', 'processing', 'missed', 'parked', 'deleted', 'pickup'];
17
18    const STATUS_APPEND = ['missed', 'parked', 'deleted'];
19
20    const STATUS_CALLED = ['called', 'processing', 'pickup'];
21
22    const STATUS_FAKE = ['fake'];
23
24    protected $processTimeAverage;
25
26    protected $workstationCount;
27
28    protected $fromProcessList = false;
29
30    public function setWaitingTimePreferences($processTimeAverage, $workstationCount)
31    {
32        if ($processTimeAverage <= 0) {
33            throw new \Exception("QueueList::withEstimatedWaitingTime() requires processTimeAverage");
34        }
35        $this->processTimeAverage = $processTimeAverage;
36        $this->workstationCount = $workstationCount;
37        return $this;
38    }
39
40    public function setTransferedProcessList($bool = true)
41    {
42        $this->fromProcessList = $bool;
43        return $this;
44    }
45
46    public function getProcessTimeAverage()
47    {
48        return $this->processTimeAverage;
49    }
50
51    public function getWorkstationCount()
52    {
53        return $this->workstationCount ? $this->workstationCount : 1;
54    }
55
56    public function withEstimatedWaitingTime(
57        $processTimeAverage,
58        $workstationCount,
59        \DateTimeInterface $dateTime,
60        $createFake = true
61    ) {
62        $this->setWaitingTimePreferences($processTimeAverage, $workstationCount);
63        $queueFull = $this->withWaitingTime($dateTime);
64        $queueWithWaitingTime = $queueFull->withStatus(self::STATUS_CALLED);
65        $queueAppend = $queueFull->withStatus(self::STATUS_APPEND);
66        $queueFull = $queueFull->withoutStatus(self::STATUS_IGNORE);
67        if ($createFake) {
68            $queueFull = $queueFull->withFakeWaitingnumber($dateTime);
69        }
70        $listWithAppointment = $queueFull->withAppointment()->withSortedArrival()->getArrayCopy();
71        $listNoAppointment = $queueFull->withOutAppointment()->withSortedArrival()->getArrayCopy();
72        $nextWithAppointment = array_shift($listWithAppointment);
73        $nextNoAppointment = array_shift($listNoAppointment);
74        $currentTime = $dateTime->getTimestamp() + 120;
75        $optimisticTime = $dateTime->getTimestamp();
76        $pessimisticTime = $dateTime->getTimestamp();
77
78        $waitingTime = 0;
79        $waitingTimeOpt = 0;
80        $waitingTimePes = 0;
81        $timeSlot = ($workstationCount) ? $processTimeAverage * 60 / $workstationCount : $processTimeAverage * 60;
82        $timeSlotOptimistic = $timeSlot * 0.8;
83        $timeSlotPessimistic = $timeSlot * 1.0;
84        $workstationSkip = ceil($workstationCount * 1.0);
85        $pessimisticTime += $timeSlot;
86        $waitingTimePes = (int)floor(($pessimisticTime - $dateTime->getTimestamp()) / 60);
87        while ($nextWithAppointment || $nextNoAppointment) {
88            if ($nextWithAppointment && $currentTime >= $nextWithAppointment->arrivalTime) {
89                $nextWithAppointment->waitingTimeEstimate = $waitingTime + 1;
90                $nextWithAppointment->waitingTimeOptimistic =
91                    floor(($nextWithAppointment->arrivalTime - $dateTime->getTimestamp()) / 60);
92                if ($optimisticTime >= $nextWithAppointment->arrivalTime) {
93                    $nextWithAppointment->waitingTimeOptimistic = $waitingTimeOpt;
94                    $nextWithAppointment->waitingTimeEstimate = $waitingTimePes;
95                }
96                $queueWithWaitingTime->addEntity($nextWithAppointment);
97                $nextWithAppointment = array_shift($listWithAppointment);
98            } elseif ($nextNoAppointment) {
99                $nextNoAppointment->waitingTimeEstimate = $waitingTimePes;
100                $nextNoAppointment->waitingTimeOptimistic = $waitingTimeOpt;
101                $queueWithWaitingTime->addEntity($nextNoAppointment);
102                $nextNoAppointment = array_shift($listNoAppointment);
103            }
104            $optimisticTime += (--$workstationSkip > 0) ? 0 : $timeSlotOptimistic;
105            $pessimisticTime += $timeSlotPessimistic;
106            $currentTime += $timeSlot;
107            $waitingTime = (int)ceil(($currentTime - $dateTime->getTimestamp()) / 60);
108            $waitingTimeOpt = (int)floor(($optimisticTime - $dateTime->getTimestamp()) / 60);
109            $waitingTimePes = (int)floor(($pessimisticTime - $dateTime->getTimestamp()) / 60);
110        }
111        $queueWithWaitingTime->addList($queueAppend);
112        return $queueWithWaitingTime;
113    }
114
115    public function withWaitingTime(\DateTimeInterface $dateTime)
116    {
117        $queueList = clone $this;
118        $timestamp = $dateTime->getTimestamp();
119        foreach ($queueList as $entity) {
120            if ($timestamp > $entity->arrivalTime) {
121                $entity->waitingTime = floor(($timestamp - $entity->arrivalTime) / 60);
122            }
123        }
124        return $queueList;
125    }
126
127    public function withSortedArrival()
128    {
129        $queueList = clone $this;
130        $queueList->uasort(function ($first, $second) {
131            $firstSort = sprintf("%011d%011d", $first['arrivalTime'], $first['number']);
132            //error_log($firstSort);
133            $secondSort = sprintf("%011d%011d", $second['arrivalTime'], $second['number']);
134            return strcmp($firstSort, $secondSort);
135        });
136        return $queueList;
137    }
138
139    public function withSortedWaitingTime()
140    {
141        $queueList = clone $this;
142        return $queueList->sortByCustomKey('waitingTimeEstimate');
143    }
144
145    public function withAppointment()
146    {
147        $queueList = new self();
148        foreach ($this as $entity) {
149            if ($entity->withAppointment) {
150                $queueList->addEntity(clone $entity);
151            }
152        }
153        return $queueList;
154    }
155
156    public function withOutAppointment()
157    {
158        $queueList = new self();
159        foreach ($this as $entity) {
160            if (! $entity->withAppointment) {
161                $queueList->addEntity(clone $entity);
162            }
163        }
164        return $queueList;
165    }
166
167    public function getEstimatedWaitingTime($processTimeAverage, $workstationCount, \DateTimeInterface $dateTime)
168    {
169        $queueList = $this->withFakeWaitingnumber($dateTime);
170        $queueList = $queueList
171          ->withEstimatedWaitingTime($processTimeAverage, $workstationCount, $dateTime);
172        $newEntity = $queueList->getFakeOrLastWaitingnumber();
173        $dataOfFackedEntity = array(
174            'amountBefore' => $queueList->getQueuePositionByNumber($newEntity->number),
175            'waitingTimeEstimate' => $newEntity->waitingTimeEstimate
176        );
177        return $dataOfFackedEntity;
178    }
179
180    public function withFakeWaitingnumber(\DateTimeInterface $dateTime)
181    {
182        $queueList = clone $this;
183        $process = new \BO\Zmsentities\Process(['status' => 'deleted']);
184        $entity = (new \BO\Zmsentities\Queue())->setProcess($process);
185        $entity->number = self::FAKE_WAITINGNUMBER;
186        $entity->status = 'fake';
187        $entity->withAppointment = false;
188        $entity->destination = (string)$this->getProcessTimeAverage();
189        $entity->destinationHint = (string)$this->getWorkstationCount();
190        $entity->arrivalTime = $dateTime->getTimestamp();
191        $queueList->addEntity($entity);
192        return $queueList;
193    }
194
195    public function getFakeOrLastWaitingnumber()
196    {
197        $entity = $this->getQueueByNumber(self::FAKE_WAITINGNUMBER);
198        if (!$entity) {
199            $entity = $this->getLast();
200        }
201
202        return $entity;
203    }
204
205    public function getQueueByNumber($number)
206    {
207        foreach ($this as $entity) {
208            if ($entity->number == $number) {
209                return $entity;
210            }
211        }
212        return null;
213    }
214
215    public function getNextProcess(\DateTimeInterface $dateTime, $exclude = null)
216    {
217        $excludeNumbers = explode(',', $exclude);
218        $queueList = clone $this;
219        // sort by waiting time to get realistic next process
220        $queueList = $queueList
221            ->withStatus(['confirmed', 'queued'])
222            ->withEstimatedWaitingTime(10, 1, $dateTime, false)
223            ->getArrayCopy()
224            ;
225        $next = array_shift($queueList);
226        $currentTime = $dateTime->getTimestamp();
227        while ($next) {
228            if (
229                ! in_array($next->number, $excludeNumbers) &&
230                (0 == $next->lastCallTime || ($next->lastCallTime + (5 * 60)) <= $currentTime)
231            ) {
232                return $next->getProcess();
233            }
234            $next = array_shift($queueList);
235        }
236        return null;
237    }
238
239    public function getQueuePositionByNumber($number)
240    {
241        $list = array_values($this->getArrayCopy());
242        foreach ($list as $key => $entity) {
243            if ($entity->number == $number) {
244                return $key;
245            }
246        }
247        return null;
248    }
249
250    public function getCountWithWaitingTime()
251    {
252        $queueList = new self();
253        foreach ($this as $entity) {
254            if ($entity->waitingTime || ! $entity->withAppointment) {
255                $queueList->addEntity(clone $entity);
256            }
257        }
258        return $queueList;
259    }
260
261    /**
262     * @param array $statusList of possible strings in process.status
263     *
264     */
265    public function withStatus(array $statusList)
266    {
267        $queueList = new self();
268        foreach ($this as $entity) {
269            if ($entity->toProperty()->status->isAvailable() && in_array($entity->status, $statusList)) {
270                $queueList->addEntity(clone $entity);
271            }
272        }
273        return $queueList;
274    }
275
276    /**
277     * @param array $statusList of excepted strings in process.status
278     *
279     */
280    public function withoutStatus(array $statusList)
281    {
282        $queueList = new self();
283        foreach ($this as $entity) {
284            if ($entity->toProperty()->status->isAvailable() && ! in_array($entity->status, $statusList)) {
285                $queueList->addEntity(clone $entity);
286            }
287        }
288        return $queueList;
289    }
290
291    public function withShortNameDestinationHint(\BO\Zmsentities\Cluster $cluster, \BO\Zmsentities\Scope $scope)
292    {
293        $queueList = clone $this;
294        $list = new self();
295        foreach ($queueList as $entity) {
296            if ($cluster->shortNameEnabled && $scope->shortName) {
297                $entity->destinationHint = $scope->shortName;
298            }
299            $list->addEntity($entity);
300        }
301        $listWithPickups = $list->withPickupDestination($scope);
302        return $listWithPickups;
303    }
304
305    public function withPickupDestination(\BO\Zmsentities\Scope $scope)
306    {
307        $queueList = clone $this;
308        $list = new self();
309        foreach ($queueList as $entity) {
310            if (! $entity->toProperty()->destination->get()) {
311                $entity->destination = $scope->toProperty()->preferences->pickup->alternateName->get();
312            }
313            $list->addEntity($entity);
314        }
315        return $list;
316    }
317
318    public function toProcessList()
319    {
320        $processList = new ProcessList();
321        foreach ($this as $queue) {
322            $process = $queue->getProcess();
323            if ($process) {
324                $processList->addEntity($process);
325            }
326        }
327        return $processList;
328    }
329
330    public function withoutDublicates()
331    {
332        $list = new self();
333        $exists = [];
334        foreach ($this as $entity) {
335            $key = "$entity->number-$entity->arrivalTime-$entity->withAppointment";
336            if (!isset($exists[$key])) {
337                $list[] = $entity;
338                $exists[$key] = true;
339            }
340        }
341        return $list; // Cloning with this function
342    }
343
344    public function getWaitingNumberList()
345    {
346        $list = [];
347        foreach ($this as $entity) {
348            $list[] = $entity->number;
349        }
350        return $list;
351    }
352
353    public function getWaitingNumberListCsv()
354    {
355        return implode(',', $this->getWaitingNumberList());
356    }
357
358    public function withSelectedProcessFirst(\BO\Zmsentities\Process $process)
359    {
360        $queueList = clone $this;
361        $list = new self();
362        $list->addEntity($process->queue);
363        foreach ($queueList as $entity) {
364            if ($entity->number != $process->queue->number) {
365                $list->addEntity($entity);
366            }
367        }
368        return $list;
369    }
370
371    public function sortByCallTime(string $order)
372    {
373        $queueListArray = [];
374        foreach ($this as $entity) {
375            $queueListArray[] = $entity;
376        }
377        usort($queueListArray, function ($a, $b) use ($order) {
378            if ($order === 'ascending') {
379                return $a->callTime <=> $b->callTime;
380            } elseif ($order === 'descending') {
381                return $b->callTime <=> $a->callTime;
382            }
383            throw new InvalidArgumentException("Invalid sort order: $order. Use 'ascending' or 'descending'.");
384        });
385        return new self($queueListArray);
386    }
387}