Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
58.82% covered (warning)
58.82%
80 / 136
55.56% covered (warning)
55.56%
5 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ProcessStatusFree
58.82% covered (warning)
58.82%
80 / 136
55.56% covered (warning)
55.56%
5 / 9
103.49
0.00% covered (danger)
0.00%
0 / 1
 prepareCalendarAndDays
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 getProcessDataHandle
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 readFreeProcesses
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
3
 readFreeProcessesMinimalDeduplicated
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 extractProcessInfo
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 generateUniqueKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 createMinimalProcess
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
2
 readReservedProcesses
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
4
 writeEntityReserved
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2
3namespace BO\Zmsdb;
4
5use BO\Zmsentities\Process as Entity;
6use BO\Zmsentities\Collection\ProcessList as Collection;
7
8/**
9 * @SuppressWarnings(Coupling)
10 */
11class ProcessStatusFree extends Process
12{
13    private function prepareCalendarAndDays(
14        \BO\Zmsentities\Calendar $calendar,
15        \DateTimeInterface $now,
16        $slotsRequired = null
17    ) {
18        $calendar = (new Calendar())->readResolvedEntity($calendar, $now, true);
19        $dayquery = new Day();
20        $dayquery->writeTemporaryScopeList($calendar, $slotsRequired);
21        $selectedDate = $calendar->getFirstDay();
22        $days = [$selectedDate];
23        if ($calendar->getLastDay(false)) {
24            $days = [];
25            while ($selectedDate <= $calendar->getLastDay(false)) {
26                $days[] = $selectedDate;
27                $selectedDate = $selectedDate->modify('+1 day');
28            }
29        }
30        return [$calendar, $dayquery, $days];
31    }
32
33    private function getProcessDataHandle(
34        array $days,
35        $slotType,
36        $slotsRequired,
37        $groupData
38    ) {
39        return $this->fetchHandle(
40            sprintf(
41                Query\ProcessStatusFree::QUERY_SELECT_PROCESSLIST_DAYS,
42                Query\ProcessStatusFree::buildDaysCondition($days)
43            )
44            . ($groupData ? Query\ProcessStatusFree::GROUPBY_SELECT_PROCESSLIST_DAY : ''),
45            [
46                'slotType' => $slotType,
47                'forceRequiredSlots' =>
48                    ($slotsRequired === null || $slotsRequired < 1) ? 1 : intval($slotsRequired),
49            ]
50        );
51    }
52
53    public function readFreeProcesses(
54        \BO\Zmsentities\Calendar $calendar,
55        \DateTimeInterface $now,
56        $slotType = 'public',
57        $slotsRequired = null,
58        $groupData = false
59    ) {
60        list($calendar, $dayquery, $days) = $this->prepareCalendarAndDays($calendar, $now, $slotsRequired);
61        $processData = $this->getProcessDataHandle($days, $slotType, $slotsRequired, $groupData);
62        $processList = new Collection();
63        $scopeList = [];
64        while ($item = $processData->fetch(\PDO::FETCH_ASSOC)) {
65            $process = new \BO\Zmsentities\Process($item);
66            $process->requests = $calendar->requests;
67            $process->appointments->getFirst()->setDateByString(
68                $process->appointments->getFirst()->date,
69                'Y-m-d H:i:s'
70            );
71
72            if (! isset($scopeList[$process->scope->id])) {
73                $scopeList[$process->scope->id] = $calendar->scopes->getEntity($process->scope->id);
74            }
75
76            $process->scope = $scopeList[$process->scope->id];
77            $process->queue['withAppointment'] = 1;
78            $process->appointments->getFirst()->scope = $process->scope;
79            $processList->addEntity($process);
80        }
81        $processData->closeCursor();
82        unset($dayquery);
83        return $processList;
84    }
85
86    public function readFreeProcessesMinimalDeduplicated(
87        \BO\Zmsentities\Calendar $calendar,
88        \DateTimeInterface $now,
89        string $slotType = 'public',
90        ?int $slotsRequired = null,
91        bool $groupData = false
92    ): array {
93        list($calendar, $dayquery, $days) = $this->prepareCalendarAndDays($calendar, $now, $slotsRequired);
94        $processData = $this->getProcessDataHandle($days, $slotType, $slotsRequired, $groupData);
95
96        $unique = [];
97        while ($item = $processData->fetch(\PDO::FETCH_ASSOC)) {
98            $processInfo = $this->extractProcessInfo($item, $calendar);
99            if ($processInfo) {
100                $key = $this->generateUniqueKey($processInfo['providerId'], $processInfo['date']);
101                if (!isset($unique[$key])) {
102                    $unique[$key] = $this->createMinimalProcess($processInfo);
103                }
104            }
105        }
106
107        $processData->closeCursor();
108        unset($dayquery);
109
110        return array_values($unique);
111    }
112
113    private function extractProcessInfo(array $item, \BO\Zmsentities\Calendar $calendar): ?array
114    {
115        $scopeId = $item['scope__id'] ?? null;
116        $dateString = $item['appointments__0__date'] ?? null;
117
118        if (!$scopeId || !$dateString) {
119            return null;
120        }
121
122        $date = strtotime($dateString);
123        if (!$date) {
124            return null;
125        }
126
127        $scope = $calendar->scopes->getEntity($scopeId);
128        if (!$scope) {
129            return null;
130        }
131
132        $providerId = $scope->getProviderId();
133        if (!$providerId) {
134            return null;
135        }
136
137        return [
138            'scopeId' => $scopeId,
139            'source' => $scope->getSource(),
140            'providerId' => $providerId,
141            'date' => $date
142        ];
143    }
144
145    private function generateUniqueKey(string $providerId, int $date): string
146    {
147        return $providerId . '_' . $date;
148    }
149
150    private function createMinimalProcess(array $processInfo): array
151    {
152        return [
153            '$schema' => 'https://schema.berlin.de/queuemanagement/process.json',
154            'scope' => [
155                'id' => $processInfo['scopeId'],
156                'source' => $processInfo['source'],
157                'provider' => [
158                    'id' => $processInfo['providerId'],
159                    'source' => $processInfo['source'],
160                ]
161            ],
162            'appointments' => [
163                [
164                    'date' => (string)$processInfo['date'],
165                    'scope' => [
166                        'id' => $processInfo['scopeId'],
167                        'source' => $processInfo['source'],
168                        'provider' => [
169                            'id' => $processInfo['providerId'],
170                            'source' => $processInfo['source'],
171                        ]
172                    ]
173                ]
174            ]
175        ];
176    }
177
178    public function readReservedProcesses($resolveReferences = 2)
179    {
180        $processList = new Collection();
181        $query = new Query\Process(Query\Base::SELECT);
182        $query
183            ->addResolvedReferences($resolveReferences)
184            ->addEntityMapping()
185            ->addConditionAssigned()
186            ->addConditionIsReserved();
187        $resultData = $this->fetchList($query, new Entity());
188        foreach ($resultData as $process) {
189            if (2 == $resolveReferences) {
190                $process['requests'] = (new Request())->readRequestByProcessId($process->id, $resolveReferences);
191                $process['scope'] = (new Scope())->readEntity($process->getScopeId(), $resolveReferences);
192            }
193            if ($process instanceof Entity) {
194                $processList->addEntity($process);
195            }
196        }
197        return $processList;
198    }
199
200    /**
201     * Insert a new process if there are free slots
202     *
203     * @param \BO\Zmsentities\Process $process
204     * @param \DateTimeInterface $now
205     * @param String $slotType
206     * @param Int $slotsRequired we cannot use process.appointments.0.slotCount, because setting slotsRequired is
207     *        a priviliged operation. Just using the input would be a security flaw to get a wider selection of times
208     *        If slotsRequired = 0, readFreeProcesses() uses the slotsRequired based on request-provider relation
209     */
210    public function writeEntityReserved(
211        \BO\Zmsentities\Process $process,
212        \DateTimeInterface $now,
213        $slotType = "public",
214        $slotsRequired = 0,
215        $resolveReferences = 0,
216        $userAccount = null
217    ) {
218        $process = clone $process;
219        $process->status = 'reserved';
220        $appointment = $process->getAppointments()->getFirst();
221        $slotList = (new Slot())->readByAppointment(
222            $appointment,
223            $slotsRequired,
224            (null !== $userAccount),
225            true
226        );
227        $freeProcessList = $this->readFreeProcesses($process->toCalendar(), $now, $slotType, $slotsRequired);
228
229        if (!$freeProcessList->getAppointmentList()->hasAppointment($appointment) || ! $slotList) {
230            throw new Exception\Process\ProcessReserveFailed();
231        }
232
233        foreach ($slotList as $slot) {
234            if ($process->id > 99999) {
235                $newProcess = clone $process;
236                $newProcess->getFirstAppointment()->setTime($slot->time);
237                $this->writeNewProcess($newProcess, $now, $process->id, 0, true, $userAccount);
238            } elseif ($process->id === 0) {
239                $process = $this->writeNewProcess($process, $now, 0, count($slotList) - 1, true, $userAccount);
240            } else {
241                throw new \Exception("SQL UPDATE error on inserting new $process on $slot");
242            }
243        }
244        $this->writeRequestsToDb($process);
245        return $this->readEntity($process->getId(), new Helper\NoAuth(), $resolveReferences);
246    }
247}