Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
86.34% |
455 / 527 |
|
85.71% |
36 / 42 |
CRAP | |
0.00% |
0 / 1 |
Process | |
86.34% |
455 / 527 |
|
85.71% |
36 / 42 |
159.96 | |
0.00% |
0 / 1 |
readEntity | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
4 | |||
readById | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
readResolvedReferences | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
updateEntity | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
4 | |||
updateEntityWithSlots | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
42 | |||
writeNewPickup | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
redirectToScope | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
2 | |||
readSlotCount | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
4 | |||
writeEntityWithNewAppointment | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
2 | |||
updateFollowingProcesses | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
4 | |||
updateReassignedRequests | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
writeNewProcess | |
100.00% |
32 / 32 |
|
100.00% |
1 / 1 |
5 | |||
readNewProcessId | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
readAuthKeyByProcessId | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
readList | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
readEntityList | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
readByWorkstation | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
readProcessListByScopeAndTime | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
readConflictListByScopeAndTime | |
100.00% |
24 / 24 |
|
100.00% |
1 / 1 |
4 | |||
readProcessListByScopeAndStatus | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
readSearch | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
4 | |||
addSearchConditions | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
14 | |||
readProcessListByClusterAndTime | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
readProcessListCountByScope | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
readProcessListByMailAddress | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
readListByMailAndStatusList | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
updateProcessStatus | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
writeDeletedEntity | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
4 | |||
deleteEntity | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
writeCanceledEntity | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
2 | |||
writeBlockedEntity | |
100.00% |
27 / 27 |
|
100.00% |
1 / 1 |
6 | |||
writeRequestsToDb | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
7 | |||
deleteRequestsForProcessId | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
readExpiredProcessList | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
readUnconfirmedProcessList | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
readExpiredProcessListByStatus | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
readExpiredReservationsList | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
readNotificationReminderProcessList | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
1 | |||
readEmailReminderProcessListByInterval | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
readDeallocateProcessList | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
isAppointmentAllowedWithSameMail | |
90.48% |
19 / 21 |
|
0.00% |
0 / 1 |
10.09 | |||
isMailWhitelisted | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
6.04 |
1 | <?php |
2 | |
3 | namespace BO\Zmsdb; |
4 | |
5 | use BO\Zmsentities\Collection\AppointmentList; |
6 | use BO\Zmsentities\Process as Entity; |
7 | use BO\Zmsentities\Scope as ScopeEntity; |
8 | use BO\Zmsentities\Collection\ProcessList as Collection; |
9 | use BO\Zmsdb\Helper\ProcessStatus; |
10 | |
11 | /** |
12 | * |
13 | * @SuppressWarnings(CouplingBetweenObjects) |
14 | * @SuppressWarnings(TooManyPublicMethods) |
15 | * @SuppressWarnings(Complexity) |
16 | * @SuppressWarnings(TooManyMethods) |
17 | */ |
18 | class Process extends Base implements Interfaces\ResolveReferences |
19 | { |
20 | public function readEntity($processId = null, $authKey = null, $resolveReferences = 2) |
21 | { |
22 | if (null === $processId || null === $authKey) { |
23 | return null; |
24 | } |
25 | $query = new Query\Process(Query\Base::SELECT); |
26 | $query->addEntityMapping() |
27 | ->addResolvedReferences($resolveReferences) |
28 | ->addConditionProcessId($processId); |
29 | if (!$authKey instanceof Helper\NoAuth) { |
30 | $query->addConditionAuthKey($authKey); |
31 | } |
32 | $process = $this->fetchOne($query, new Entity()); |
33 | $process = $this->readResolvedReferences($process, $resolveReferences); |
34 | return $process; |
35 | } |
36 | |
37 | public function readById($processId, $resolveReferences = 1) |
38 | { |
39 | $query = new Query\Process(Query\Base::SELECT); |
40 | $query->addEntityMapping() |
41 | ->addResolvedReferences($resolveReferences) |
42 | ->addConditionProcessId($processId); |
43 | |
44 | return $this->fetchOne($query, new Entity()); |
45 | } |
46 | |
47 | public function readResolvedReferences(\BO\Zmsentities\Schema\Entity $process, $resolveReferences) |
48 | { |
49 | if (1 <= $resolveReferences) { |
50 | if ($process->archiveId) { |
51 | $process['requests'] = (new Request()) |
52 | ->readRequestByArchiveId($process->archiveId, $resolveReferences - 1); |
53 | } else { |
54 | $process['requests'] = (new Request()) |
55 | ->readRequestByProcessId($process->id, $resolveReferences - 1); |
56 | } |
57 | } |
58 | return $process; |
59 | } |
60 | |
61 | |
62 | /** |
63 | * Update a process without changing appointment or scope |
64 | */ |
65 | public function updateEntity( |
66 | \BO\Zmsentities\Process $process, |
67 | \DateTimeInterface $now, |
68 | $resolveReferences = 0, |
69 | $previousStatus = null, |
70 | ?\BO\Zmsentities\Useraccount $useraccount = null |
71 | ) { |
72 | $processEntity = $process; |
73 | $query = new Query\Process(Query\Base::UPDATE); |
74 | $query->addConditionProcessId($process['id']); |
75 | $query->addConditionAuthKey($process['authKey']); |
76 | $query->addValuesUpdateProcess($process, $now, 0, $previousStatus); |
77 | |
78 | if ($this->perform($query->getLockProcessId(), ['processId' => $process->getId()])) { |
79 | $this->writeItem($query); |
80 | $this->writeRequestsToDb($process); |
81 | } |
82 | $process = $this->readEntity($process->getId(), $process->authKey, $resolveReferences); |
83 | if (!$process->getId()) { |
84 | throw new Exception\Process\ProcessUpdateFailed(); |
85 | } |
86 | $this->perform(Query\Process::QUERY_UPDATE_FOLLOWING_PROCESS, [ |
87 | 'reserved' => ($process->status == 'reserved') ? 1 : 0, |
88 | 'processID' => $process->getId(), |
89 | ]); |
90 | |
91 | Log::writeProcessLog( |
92 | "UPDATE (Process::updateEntity) $process ", |
93 | Log::ACTION_EDITED, |
94 | $processEntity, |
95 | $useraccount |
96 | ); |
97 | |
98 | return $process; |
99 | } |
100 | |
101 | /** |
102 | * Update a process with overbooked slots |
103 | * |
104 | * @param \BO\Zmsentities\Process $process |
105 | * @param \DateTimeInterface $now |
106 | * @param Int $slotsRequired we cannot use process.appointments.0.slotCount, because setting slotsRequired is |
107 | * a priviliged operation. Just using the input would be a security flaw to get a wider selection of times |
108 | * If slotsRequired = 0, readFreeProcesses() uses the slotsRequired based on request-provider relation |
109 | */ |
110 | public function updateEntityWithSlots( |
111 | \BO\Zmsentities\Process $process, |
112 | \DateTimeInterface $now, |
113 | $slotType = "intern", |
114 | $slotsRequired = 0, |
115 | $resolveReferences = 0, |
116 | $userAccount = null |
117 | ) { |
118 | if ('intern' != $slotType) { |
119 | return $this->updateEntity( |
120 | $process, |
121 | $now, |
122 | $resolveReferences, |
123 | null, |
124 | $userAccount |
125 | ); |
126 | } |
127 | $process = clone $process; |
128 | $appointment = $process->getAppointments()->getFirst(); |
129 | $slotList = (new Slot())->readByAppointment($appointment, $slotsRequired, true); |
130 | $processEntityList = $this->readEntityList($process->getId()); |
131 | foreach ($processEntityList as $entity) { |
132 | if ($process->getId() != $entity->getId()) { |
133 | $this->writeDeletedEntity($entity->getId()); |
134 | } |
135 | } |
136 | |
137 | foreach ($slotList as $slot) { |
138 | $newProcess = clone $process; |
139 | $newProcess->getFirstAppointment()->setTime($slot->time); |
140 | if (! $newProcess->getFirstAppointment()->isMatching($process->getFirstAppointment())) { |
141 | $this->writeNewProcess($newProcess, $now, $process->id, 0, true, $userAccount); |
142 | } |
143 | } |
144 | |
145 | $appointment->addSlotCount($slotList->count()); |
146 | Log::writeProcessLog( |
147 | "CREATE (Process::updateEntityWithSlots) $process ", |
148 | Log::ACTION_EDITED, |
149 | $process, |
150 | $userAccount |
151 | ); |
152 | |
153 | return $this->updateEntity( |
154 | $process, |
155 | $now, |
156 | $resolveReferences, |
157 | null, |
158 | $userAccount |
159 | ); |
160 | } |
161 | |
162 | public function writeNewPickup( |
163 | \BO\Zmsentities\Scope $scope, |
164 | \DateTimeInterface $dateTime, |
165 | $newQueueNumber = 0, |
166 | \BO\Zmsentities\Useraccount $useraccount = null |
167 | ) { |
168 | $process = Entity::createFromScope($scope, $dateTime); |
169 | $process->setStatus('pending'); |
170 | if (!$newQueueNumber) { |
171 | $newQueueNumber = (new Scope())->readWaitingNumberUpdated($scope->id, $dateTime); |
172 | } |
173 | $process->addQueue($newQueueNumber, $dateTime); |
174 | Log::writeProcessLog( |
175 | "CREATE (Process::writeNewPickup) $process ", |
176 | Log::ACTION_NEW_PICKUP, |
177 | $process, |
178 | $useraccount |
179 | ); |
180 | return $this->writeNewProcess($process, $dateTime); |
181 | } |
182 | |
183 | public function redirectToScope( |
184 | $process, |
185 | \BO\Zmsentities\Scope $scope, |
186 | int $waitingNumber, |
187 | ?\BO\Zmsentities\Useraccount $useraccount = null |
188 | ) { |
189 | $datetime = \App::$now; |
190 | $process->setStatus('confirmed'); |
191 | |
192 | $appointment = $process->getFirstAppointment(); |
193 | $date = (new \DateTimeImmutable())->setTimestamp($appointment->date); |
194 | $date = $date->setTime(0, 0, 0); |
195 | $appointment->date = $date->getTimestamp(); |
196 | $process->appointments = new AppointmentList([$appointment]); |
197 | |
198 | $newQueueNumber = (new Scope())->readWaitingNumberUpdated($scope->id, $datetime); |
199 | $process->addQueue($newQueueNumber, $datetime); |
200 | $process->queue['number'] = $waitingNumber; |
201 | |
202 | Log::writeProcessLog( |
203 | "CREATE (Process::redirectToScope) $process ", |
204 | Log::ACTION_REDIRECTED, |
205 | $process, |
206 | $useraccount |
207 | ); |
208 | |
209 | $process = $this->writeNewProcess($process, $datetime); |
210 | $this->writeRequestsToDb($process); |
211 | |
212 | return $process; |
213 | } |
214 | |
215 | public function readSlotCount(\BO\Zmsentities\Process $process) |
216 | { |
217 | $scope = new \BO\Zmsentities\Scope($process->scope); |
218 | $requestRelationList = (new RequestRelation()) |
219 | ->readListByProviderId($scope->getProviderId(), $scope->getSource()); |
220 | $appointment = $process->getAppointments()->getFirst(); |
221 | $appointment->slotCount = 0; |
222 | foreach ($process->requests as $request) { |
223 | foreach ($requestRelationList as $requestRelation) { |
224 | if ($requestRelation->getRequest()->getId() == $request->getId()) { |
225 | $appointment->slotCount += $requestRelation->getSlotCount(); |
226 | } |
227 | } |
228 | } |
229 | $appointment->slotCount = round($appointment->slotCount, 0); |
230 | return $process; |
231 | } |
232 | |
233 | /** |
234 | * write a new process with appointment and keep id and authkey from original process |
235 | */ |
236 | public function writeEntityWithNewAppointment( |
237 | \BO\Zmsentities\Process $process, |
238 | \BO\Zmsentities\Appointment $appointment, |
239 | \DateTimeInterface $now, |
240 | $slotType = 'public', |
241 | $slotsRequired = 0, |
242 | $resolveReferences = 0, |
243 | $keepReserved = false |
244 | ) { |
245 | // clone to new process with id = 0 and new appointment to reserve |
246 | $processNew = clone $process; |
247 | $processNew->id = 0; |
248 | $processNew->scope = $appointment->scope; |
249 | $processNew->queue['arrivalTime'] = 0; |
250 | $processNew->queue['number'] = 0; |
251 | $processNew->appointments = (new \BO\Zmsentities\Collection\AppointmentList())->addEntity($appointment); |
252 | //delete old process with following processes |
253 | $this->writeDeletedEntity($process->getId()); |
254 | //reserve new appointment |
255 | $processNew = ProcessStatusFree::init() |
256 | ->writeEntityReserved($processNew, $now, $slotType, $slotsRequired); |
257 | $processTempNewId = $processNew->getId(); |
258 | |
259 | // reassign credentials of new process with credentials of old process |
260 | $processNew->withReassignedCredentials($process); |
261 | |
262 | // update new process with old credentials, also assigned requests and following slots |
263 | $this->updateFollowingProcesses($processTempNewId, $processNew); |
264 | $this->updateReassignedRequests($processTempNewId, $processNew->getId()); |
265 | |
266 | //delete slot mapping for temp process id |
267 | (new Slot())->deleteSlotProcessMappingFor($processTempNewId); |
268 | //write new slot mapping for changed process with old credentials because new appointment data |
269 | (new Slot())->writeSlotProcessMappingFor($processNew->getId()); |
270 | Log::writeProcessLog( |
271 | "UPDATE (Process::writeEntityWithNewAppointment) $process ", |
272 | Log::ACTION_EDITED, |
273 | $process |
274 | ); |
275 | |
276 | $status = ($keepReserved) ? Entity::STATUS_RESERVED : ENTITY::STATUS_CONFIRMED; |
277 | return $this->updateProcessStatus($processNew, $status, $now, $resolveReferences); |
278 | } |
279 | |
280 | /** |
281 | * update following process with new credentials (also change process id if necessary) |
282 | */ |
283 | public function updateFollowingProcesses( |
284 | $processId, |
285 | \BO\Zmsentities\Process $processData |
286 | ) { |
287 | $this->perform(Query\Process::QUERY_REASSIGN_PROCESS_CREDENTIALS, [ |
288 | 'newProcessId' => $processData->getId(), |
289 | 'newAuthKey' => $processData->getAuthKey(), |
290 | 'processId' => $processId |
291 | ]); |
292 | |
293 | $processEntityList = $this->readEntityList($processId); |
294 | if ($processEntityList->count()) { |
295 | foreach ($processEntityList as $entity) { |
296 | if ($entity->getId() != $processId) { |
297 | $this->perform(Query\Process::QUERY_REASSIGN_FOLLWING_PROCESS, [ |
298 | 'newProcessId' => $processData->getId(), |
299 | 'processId' => $processId |
300 | ]); |
301 | } |
302 | } |
303 | } |
304 | } |
305 | |
306 | /** |
307 | * update process requests with new credentials |
308 | */ |
309 | public function updateReassignedRequests( |
310 | $processId, |
311 | $newProcessId |
312 | ) { |
313 | $this->perform(Query\Process::QUERY_REASSIGN_PROCESS_REQUESTS, [ |
314 | 'newProcessId' => $newProcessId, |
315 | 'processId' => $processId |
316 | ]); |
317 | } |
318 | |
319 | /** |
320 | * write a new process to DB |
321 | * |
322 | */ |
323 | protected function writeNewProcess( |
324 | \BO\Zmsentities\Process $process, |
325 | \DateTimeInterface $dateTime, |
326 | $parentProcess = 0, |
327 | $childProcessCount = 0, |
328 | $retry = true, |
329 | $userAccount = null |
330 | ) { |
331 | $query = new Query\Process(Query\Base::INSERT); |
332 | $process->id = $this->readNewProcessId(); |
333 | $process->setRandomAuthKey(); |
334 | $process->createTimestamp = $dateTime->getTimestamp(); |
335 | $query->addValuesNewProcess($process, $parentProcess, $childProcessCount); |
336 | $query->addValuesScopeData($process); |
337 | $query->addValuesAppointmentData($process); |
338 | $query->addValuesUpdateProcess($process, $dateTime, $parentProcess); |
339 | try { |
340 | $this->writeItem($query); |
341 | } catch (Exception\Pdo\PDOFailed $exception) { |
342 | if ($retry) { |
343 | // First try might fail if two processes are created with the same number at the same time |
344 | sleep(1); // Let the other process complete his transaction |
345 | return $this->writeNewProcess( |
346 | $process, |
347 | $dateTime, |
348 | $parentProcess, |
349 | $childProcessCount, |
350 | false, |
351 | $userAccount |
352 | ); |
353 | } |
354 | throw new Exception\Process\ProcessCreateFailed($exception->getMessage()); |
355 | } |
356 | (new Slot())->writeSlotProcessMappingFor($process->id); |
357 | |
358 | $checksum = ($userAccount) ? sha1($process->id . '-' . $userAccount->getId()) : ''; |
359 | Log::writeProcessLog( |
360 | "CREATE (Process::writeNewProcess) $process $checksum ", |
361 | Log::ACTION_NEW, |
362 | $process, |
363 | $userAccount |
364 | ); |
365 | |
366 | if (!$process->toQueue($dateTime)->withAppointment) { |
367 | (new ExchangeWaitingscope())->writeWaitingTimeCalculated($process->scope, $dateTime, false); |
368 | } |
369 | |
370 | return $process; |
371 | } |
372 | |
373 | /** |
374 | * Fetch a free process ID from DB |
375 | * |
376 | */ |
377 | protected function readNewProcessId() |
378 | { |
379 | $query = new Query\Process(Query\Base::SELECT); |
380 | $newProcessId = $this->fetchValue($query->getQueryNewProcessId()); |
381 | return $newProcessId; |
382 | } |
383 | |
384 | /** |
385 | * Read authKey by processId |
386 | * |
387 | * @param |
388 | * processId |
389 | * |
390 | * @return String authKey |
391 | */ |
392 | public function readAuthKeyByProcessId($processId) |
393 | { |
394 | $query = new Query\Process(Query\Base::SELECT); |
395 | $query |
396 | ->addEntityMapping() |
397 | ->addResolvedReferences(0) |
398 | ->addConditionProcessId($processId); |
399 | $process = $this->fetchOne($query, new Entity()); |
400 | return ($process->hasId()) ? array( |
401 | 'authName' => $process->getFirstClient()['familyName'], |
402 | 'authKey' => $process->authKey |
403 | ) : null; |
404 | } |
405 | |
406 | protected function readList($statement, $resolveReferences) |
407 | { |
408 | $query = new Query\Process(Query\Base::SELECT); |
409 | $processList = new Collection(); |
410 | while ($processData = $statement->fetch(\PDO::FETCH_ASSOC)) { |
411 | $entity = new Entity($query->postProcessJoins($processData)); |
412 | $entity = $this->readResolvedReferences($entity, $resolveReferences); |
413 | $processList->addEntity($entity); |
414 | } |
415 | return $processList; |
416 | } |
417 | |
418 | /** |
419 | * Read list with following processes in DB |
420 | */ |
421 | public function readEntityList($processId, $resolveReferences = 0) |
422 | { |
423 | $query = new Query\Process(Query\Base::SELECT); |
424 | $query |
425 | ->addResolvedReferences($resolveReferences) |
426 | ->addEntityMapping() |
427 | ->addConditionProcessIdFollow($processId); |
428 | $statement = $this->fetchStatement($query); |
429 | return $this->readList($statement, $resolveReferences)->sortByAppointmentDate(); |
430 | } |
431 | |
432 | public function readByWorkstation(\BO\Zmsentities\Workstation $workstation, $resolveReferences = 0) |
433 | { |
434 | $query = new Query\Process(Query\Base::SELECT); |
435 | $query |
436 | ->addResolvedReferences($resolveReferences) |
437 | ->addEntityMapping() |
438 | ->addConditionWorkstationId($workstation->id); |
439 | $process = $this->fetchOne($query, new Entity()); |
440 | return ($process->hasId()) ? $this->readResolvedReferences($process, $resolveReferences) : null; |
441 | } |
442 | |
443 | /** |
444 | * Read processList by scopeId and DateTime |
445 | * |
446 | * @param |
447 | * scopeId |
448 | * dateTime |
449 | * |
450 | * @return Collection processList |
451 | */ |
452 | public function readProcessListByScopeAndTime( |
453 | $scopeId, |
454 | \DateTimeInterface $dateTime, |
455 | $resolveReferences = 0 |
456 | ) { |
457 | $query = new Query\Process(Query\Base::SELECT); |
458 | $query |
459 | ->addResolvedReferences($resolveReferences) |
460 | ->addEntityMapping() |
461 | ->addConditionScopeId($scopeId) |
462 | ->addConditionAssigned() |
463 | ->addConditionIgnoreSlots() |
464 | ->addConditionTime($dateTime) |
465 | ->removeDuplicates(); |
466 | $statement = $this->fetchStatement($query); |
467 | return $this->readList($statement, $resolveReferences); |
468 | } |
469 | |
470 | /** |
471 | * Read conflictList by scopeId and DateTime |
472 | * |
473 | * @param |
474 | * scopeId |
475 | * dateTime |
476 | * |
477 | * @return Collection processList |
478 | */ |
479 | public function readConflictListByScopeAndTime( |
480 | \BO\Zmsentities\Scope $scope, |
481 | \DateTimeInterface $startDate = null, |
482 | \DateTimeInterface $endDate = null, |
483 | \DateTimeInterface $now = null, |
484 | $resolveReferences = 1 |
485 | ) { |
486 | $availabilityList = (new Availability()) |
487 | ->readAvailabilityListByScope($scope, 0, $startDate, $endDate) |
488 | ->withScope($scope); |
489 | |
490 | if (! $endDate) { |
491 | $availabilityList = $availabilityList->withDateTime($startDate); |
492 | $endDate = $startDate; |
493 | } |
494 | $currentDate = ($startDate) ? $startDate : $now; |
495 | $conflictList = $availabilityList->getConflicts($startDate, $endDate); |
496 | while ($currentDate <= $endDate) { |
497 | $query = new Query\Process(Query\Base::SELECT); |
498 | $query |
499 | ->addResolvedReferences($resolveReferences) |
500 | ->addEntityMapping() |
501 | ->addConditionScopeId($scope->getId()) |
502 | ->addConditionAssigned() |
503 | ->addConditionIgnoreSlots(); |
504 | $query->addConditionTime($currentDate); |
505 | $statement = $this->fetchStatement($query); |
506 | $processList = $this->readList($statement, $resolveReferences); |
507 | $processList = $processList->toQueueList($currentDate)->withoutStatus(['queued'])->toProcessList(); |
508 | $conflictList->addList($processList->withOutAvailability($availabilityList)); |
509 | $currentDate = $currentDate->modify('+1 day'); |
510 | } |
511 | $conflictList = $conflictList->withoutExpiredAppointmentDate($now); |
512 | return $conflictList; |
513 | } |
514 | |
515 | |
516 | /** |
517 | * Read processList by scopeId and status |
518 | * |
519 | * @return Collection processList |
520 | */ |
521 | public function readProcessListByScopeAndStatus( |
522 | $scopeId, |
523 | $status, |
524 | $resolveReferences = 0, |
525 | $limit = 1000, |
526 | $offset = null |
527 | ) { |
528 | $query = new Query\Process(Query\Base::SELECT); |
529 | $query |
530 | ->addResolvedReferences($resolveReferences) |
531 | ->addEntityMapping() |
532 | ->addConditionScopeId($scopeId) //removed because of dismatching scope and pickup scope |
533 | ->addConditionStatus($status, $scopeId) |
534 | ->addLimit($limit, $offset); |
535 | $statement = $this->fetchStatement($query); |
536 | return $this->readList($statement, $resolveReferences); |
537 | } |
538 | |
539 | public function readSearch(array $parameter, $resolveReferences = 0, $limit = 100) |
540 | { |
541 | $query = new Query\Process(Query\Base::SELECT); |
542 | $query |
543 | ->addResolvedReferences($resolveReferences) |
544 | ->addEntityMapping() |
545 | ->addConditionAssigned() |
546 | ->addConditionIgnoreSlots() |
547 | ->addLimit($limit); |
548 | |
549 | if (isset($parameter['query'])) { |
550 | if (preg_match('#^\d+$#', $parameter['query'])) { |
551 | $query->addConditionProcessId($parameter['query']); |
552 | $query->addConditionSearch($parameter['query'], true); |
553 | } else { |
554 | $query->addConditionSearch($parameter['query']); |
555 | } |
556 | unset($parameter['query']); |
557 | } |
558 | if (count($parameter)) { |
559 | $query = $this->addSearchConditions($query, $parameter); |
560 | } |
561 | |
562 | $statement = $this->fetchStatement($query); |
563 | return $this->readList($statement, $resolveReferences); |
564 | } |
565 | |
566 | protected function addSearchConditions(Query\Process $query, $parameter) |
567 | { |
568 | if (isset($parameter['processId']) && $parameter['processId']) { |
569 | $query->addConditionProcessId($parameter['processId']); |
570 | } |
571 | if (isset($parameter['name']) && $parameter['name']) { |
572 | $exact = (isset($parameter['exact'])) ? $parameter['exact'] : false; |
573 | $query->addConditionName($parameter['name'], $exact); |
574 | } |
575 | if (isset($parameter['amendment']) && $parameter['amendment']) { |
576 | $query->addConditionAmendment($parameter['amendment']); |
577 | } |
578 | if (isset($parameter['scopeId']) && $parameter['scopeId']) { |
579 | $query->addConditionScopeId($parameter['scopeId']); |
580 | } |
581 | if (isset($parameter['authKey']) && $parameter['authKey']) { |
582 | $query->addConditionAuthKey($parameter['authKey']); |
583 | } |
584 | if (isset($parameter['requestId']) && $parameter['requestId']) { |
585 | $query->addConditionRequestId($parameter['requestId']); |
586 | } |
587 | return $query; |
588 | } |
589 | |
590 | /** |
591 | * Read processList by clusterId and DateTime |
592 | * |
593 | * @param |
594 | * clusterId |
595 | * dateTime |
596 | * |
597 | * @return Collection processList |
598 | */ |
599 | public function readProcessListByClusterAndTime($clusterId, \DateTimeInterface $dateTime) |
600 | { |
601 | $processList = new Collection(); |
602 | $cluster = (new Cluster())->readEntity($clusterId, 1); |
603 | if ($cluster->scopes->count()) { |
604 | foreach ($cluster->scopes as $scope) { |
605 | $processList->addList($this->readProcessListByScopeAndTime($scope->id, $dateTime)); |
606 | } |
607 | } |
608 | return $processList; |
609 | } |
610 | |
611 | /** |
612 | * Read processList by scopeId to get a number of all processes of a scope |
613 | * |
614 | * @param |
615 | * scopeId |
616 | * |
617 | * @return Collection processList |
618 | */ |
619 | public function readProcessListCountByScope($scopeId) |
620 | { |
621 | $query = new Query\Process(Query\Base::SELECT); |
622 | $query |
623 | ->addCountValue() |
624 | ->addConditionAssigned() |
625 | ->addConditionIgnoreSlots() |
626 | ->addConditionScopeId($scopeId); |
627 | $statement = $this->fetchStatement($query); |
628 | $result = $statement->fetch(\PDO::FETCH_ASSOC); |
629 | return $result['processCount']; |
630 | } |
631 | |
632 | /** |
633 | * Read processList by mail address |
634 | * |
635 | * @return Collection processList |
636 | */ |
637 | public function readProcessListByMailAddress( |
638 | string $mailAddress, |
639 | int $scopeId = null, |
640 | $resolveReferences = 0, |
641 | $limit = 2000 |
642 | ): Collection { |
643 | $query = new Query\Process(Query\Base::SELECT); |
644 | $query |
645 | ->addResolvedReferences($resolveReferences) |
646 | ->addEntityMapping() |
647 | ->addConditionMail($mailAddress, true) |
648 | ->addConditionIgnoreSlots() |
649 | ->addLimit($limit); |
650 | |
651 | if ($scopeId) { |
652 | $query->addConditionScopeId($scopeId); |
653 | } |
654 | |
655 | $statement = $this->fetchStatement($query); |
656 | return $this->readList($statement, $resolveReferences); |
657 | } |
658 | |
659 | /** |
660 | * Read processList by mail address and statuslist |
661 | * |
662 | * @return Collection processList |
663 | */ |
664 | public function readListByMailAndStatusList( |
665 | string $mailAddress, |
666 | array $statusList, |
667 | $resolveReferences = 1, |
668 | $limit = 300 |
669 | ): Collection { |
670 | $query = new Query\Process(Query\Base::SELECT); |
671 | $query |
672 | ->addResolvedReferences($resolveReferences) |
673 | ->addEntityMapping() |
674 | ->addConditionMail($mailAddress, true) |
675 | ->addConditionIgnoreSlots() |
676 | ->addLimit($limit); |
677 | $statement = $this->fetchStatement($query); |
678 | $collection = $this->readList($statement, $resolveReferences); |
679 | return $collection->toProcessListByStatusList($statusList); |
680 | } |
681 | |
682 | |
683 | /** |
684 | * Markiere einen Termin als bestätigt |
685 | * |
686 | * @param |
687 | * process |
688 | * |
689 | * @return Resource Status |
690 | */ |
691 | public function updateProcessStatus( |
692 | Entity $process, |
693 | $status, |
694 | \DateTimeInterface $dateTime, |
695 | $resolveReferences = 0, |
696 | $userAccount = null |
697 | ) { |
698 | $process = (new ProcessStatus()) |
699 | ->writeUpdatedStatus($process, $status, $dateTime, $resolveReferences, $userAccount); |
700 | return $process; |
701 | } |
702 | |
703 | /** |
704 | * Löscht einen Termin aus der Datenbank |
705 | * Regulär sollte aber ProcessStatusArchived::writeEntityFinished() |
706 | * oder self::writeBlockedEntity() verwendet werden. |
707 | * |
708 | * @return Resource Status |
709 | */ |
710 | public function writeDeletedEntity($processId): bool |
711 | { |
712 | $processEntityList = $this->readEntityList($processId); |
713 | $status = false; |
714 | if ($processEntityList->count()) { |
715 | foreach ($processEntityList as $entity) { |
716 | $entityId = $entity->getId(); |
717 | $query = Query\Process::QUERY_DELETE; |
718 | $status = $this->perform($query, array( |
719 | $entityId, |
720 | $entityId |
721 | )); |
722 | if ($status) { |
723 | $this->deleteRequestsForProcessId($entityId); |
724 | (new Slot())->deleteSlotProcessMappingFor($entityId); |
725 | Log::writeProcessLog( |
726 | "DELETE (Process::writeDeletedEntity) $entityId ", |
727 | Log::ACTION_DELETED, |
728 | $entity |
729 | ); |
730 | } |
731 | } |
732 | } |
733 | |
734 | return $status; |
735 | } |
736 | |
737 | /** |
738 | * ACHTUNG: Nur noch als Helferfunction vor Refactoring durch MF, |
739 | * damit unittests und zmsappointment wie gewohnt funktionieren |
740 | * |
741 | * @param |
742 | * processId and authKey |
743 | * |
744 | * @return Resource Status |
745 | */ |
746 | public function deleteEntity($processId, $authKey) |
747 | { |
748 | return $this->writeCanceledEntity($processId, $authKey); |
749 | } |
750 | |
751 | /** |
752 | * Markiere einen Termin als abgesagt |
753 | * |
754 | * @param |
755 | * processId and authKey |
756 | * |
757 | * @return Resource Status |
758 | */ |
759 | public function writeCanceledEntity( |
760 | $processId, |
761 | $authKey, |
762 | $now = null, |
763 | ?\BO\Zmsentities\Useraccount $useraccount = null |
764 | ) { |
765 | $canceledTimestamp = ($now) ? $now->getTimestamp() : (new \DateTimeImmutable())->getTimestamp(); |
766 | $query = Query\Process::QUERY_CANCELED; |
767 | $this->perform($query, [ |
768 | 'processId' => $processId, |
769 | 'authKey' => $authKey, |
770 | 'canceledTimestamp' => $canceledTimestamp |
771 | ]); |
772 | $process = $this->readEntity($processId, new Helper\NoAuth(), 0); |
773 | Log::writeProcessLog( |
774 | "DELETE (Process::writeCanceledEntity) $processId ", |
775 | Log::ACTION_CANCELED, |
776 | $process, |
777 | $useraccount |
778 | ); |
779 | |
780 | return $process; |
781 | } |
782 | |
783 | /** |
784 | * Markiere einen Termin als dereferenced |
785 | * |
786 | * @param |
787 | * processId and authKey |
788 | * |
789 | * @return Resource Status |
790 | */ |
791 | public function writeBlockedEntity( |
792 | \BO\Zmsentities\Process $process, |
793 | bool $releaseSlots = false, |
794 | ?\BO\Zmsentities\Useraccount $useraccount = null |
795 | ) { |
796 | $amendment = $process->toDerefencedAmendment(); |
797 | $customTextfield = $process->toDerefencedcustomTextfield(); |
798 | |
799 | if (!isset($process->queue['status'])) { |
800 | $process->queue['status'] = $process->status; |
801 | } |
802 | $process->status = 'blocked'; |
803 | $query = Query\Process::QUERY_DEREFERENCED; |
804 | $status = $this->perform($query, array( |
805 | $amendment, |
806 | $customTextfield, |
807 | $process->id, |
808 | $process->authKey, |
809 | $process->id |
810 | )); |
811 | if ($status) { |
812 | $processEntityList = $this->readEntityList($process->id); |
813 | if ($processEntityList->count()) { |
814 | foreach ($processEntityList as $entity) { |
815 | $entityId = $entity->getId(); |
816 | if ($releaseSlots) { |
817 | (new Slot())->deleteSlotProcessMappingFor($entityId); |
818 | } |
819 | Log::writeProcessLog( |
820 | "DELETE (Process::writeBlockedEntity) $entityId ", |
821 | Log::ACTION_DELETED, |
822 | $process, |
823 | $useraccount |
824 | ); |
825 | } |
826 | } |
827 | } |
828 | return $status; |
829 | } |
830 | |
831 | protected function writeRequestsToDb(\BO\Zmsentities\Process $process) |
832 | { |
833 | // Beware of resolveReferences=0 to not delete the existing requests, except for queued processes |
834 | $hasRequests = ($process->requests && count($process->requests)); |
835 | if ($hasRequests || 'queued' == $process->status) { |
836 | $this->deleteRequestsForProcessId($process->id); |
837 | } |
838 | if ($hasRequests) { |
839 | $query = new Query\XRequest(Query\Base::INSERT); |
840 | foreach ($process->requests as $request) { |
841 | if ($request->id >= 0) { // allow deleting requests with a -1 request |
842 | $query->addValues( |
843 | [ |
844 | 'AnliegenID' => $request['id'], |
845 | 'source' => $request['source'], |
846 | 'BuergerID' => $process->id |
847 | ] |
848 | ); |
849 | $this->writeItem($query); |
850 | } |
851 | } |
852 | } |
853 | } |
854 | |
855 | protected function deleteRequestsForProcessId($processId) |
856 | { |
857 | $status = false; |
858 | if (0 < $processId) { |
859 | $query = new Query\XRequest(Query\Base::DELETE); |
860 | $query->addConditionProcessId($processId); |
861 | $status = $this->deleteItem($query); |
862 | } |
863 | return $status; |
864 | } |
865 | |
866 | public function readExpiredProcessList( |
867 | \DateTimeInterface $expirationDate, |
868 | $limit = 500, |
869 | $resolveReferences = 0, |
870 | $offset = null |
871 | ) { |
872 | $selectQuery = new Query\Process(Query\Base::SELECT); |
873 | $selectQuery |
874 | ->addEntityMapping() |
875 | ->addResolvedReferences($resolveReferences) |
876 | ->addConditionProcessDeleteInterval($expirationDate) |
877 | ->addConditionIgnoreSlots() |
878 | ->addLimit($limit, $offset); |
879 | $statement = $this->fetchStatement($selectQuery); |
880 | return $this->readList($statement, $resolveReferences); |
881 | } |
882 | public function readUnconfirmedProcessList( |
883 | \DateTimeInterface $expirationDate, |
884 | $scopeId = 0, |
885 | $limit = 500, |
886 | $offset = null, |
887 | $resolveReferences = 0 |
888 | ) { |
889 | |
890 | $selectQuery = new Query\Process(Query\Base::SELECT); |
891 | $selectQuery |
892 | ->addEntityMapping() |
893 | ->addResolvedReferences($resolveReferences) |
894 | ->addConditionScopeId($scopeId) |
895 | ->addConditionProcessExpiredIPTimeStamp($expirationDate) |
896 | ->addConditionStatus('preconfirmed') |
897 | ->addConditionIgnoreSlots() |
898 | ->addLimit($limit, $offset); |
899 | $statement = $this->fetchStatement($selectQuery); |
900 | return $this->readList($statement, $resolveReferences); |
901 | } |
902 | |
903 | public function readExpiredProcessListByStatus( |
904 | \DateTimeInterface $expirationDate, |
905 | $status, |
906 | $limit = 500, |
907 | $offset = null, |
908 | $resolveReferences = 0 |
909 | ) { |
910 | $selectQuery = new Query\Process(Query\Base::SELECT); |
911 | $selectQuery |
912 | ->addEntityMapping() |
913 | ->addResolvedReferences($resolveReferences) |
914 | ->addConditionProcessDeleteInterval($expirationDate) |
915 | ->addConditionStatus($status) |
916 | ->addConditionIgnoreSlots() |
917 | ->addLimit($limit, $offset); |
918 | $statement = $this->fetchStatement($selectQuery); |
919 | return $this->readList($statement, $resolveReferences); |
920 | } |
921 | |
922 | public function readExpiredReservationsList( |
923 | \DateTimeInterface $expirationDate, |
924 | $scopeId, |
925 | $limit = 500, |
926 | $offset = null, |
927 | $resolveReferences = 0 |
928 | ) { |
929 | $selectQuery = new Query\Process(Query\Base::SELECT); |
930 | $selectQuery |
931 | ->addEntityMapping() |
932 | ->addResolvedReferences($resolveReferences) |
933 | ->addConditionScopeId($scopeId) |
934 | ->addConditionIsReserved() |
935 | ->addConditionProcessExpiredIPTimeStamp($expirationDate) |
936 | ->addConditionIgnoreSlots() |
937 | ->addLimit($limit, $offset); |
938 | $statement = $this->fetchStatement($selectQuery); |
939 | return $this->readList($statement, $resolveReferences); |
940 | } |
941 | |
942 | public function readNotificationReminderProcessList( |
943 | \DateTimeInterface $dateTime, |
944 | $limit = 500, |
945 | $offset = null, |
946 | $resolveReferences = 0 |
947 | ) { |
948 | $selectQuery = new Query\Process(Query\Base::SELECT); |
949 | $selectQuery |
950 | ->addEntityMapping() |
951 | ->addResolvedReferences($resolveReferences) |
952 | ->addConditionProcessReminderInterval($dateTime) |
953 | ->addConditionHasTelephone() |
954 | ->addConditionAssigned() |
955 | ->addConditionIgnoreSlots() |
956 | ->addConditionStatus('confirmed') |
957 | ->addLimit($limit, $offset); |
958 | $statement = $this->fetchStatement($selectQuery); |
959 | return $this->readList($statement, $resolveReferences)->withDepartmentNotificationEnabled(); |
960 | } |
961 | |
962 | public function readEmailReminderProcessListByInterval( |
963 | \DateTimeInterface $now, |
964 | \DateTimeInterface $lastRun, |
965 | $defaultReminderInMinutes, |
966 | $limit = 500, |
967 | $offset = null, |
968 | $resolveReferences = 0 |
969 | ) { |
970 | $selectQuery = new Query\Process(Query\Base::SELECT); |
971 | $selectQuery |
972 | ->addEntityMapping() |
973 | ->addResolvedReferences($resolveReferences) |
974 | ->addConditionProcessMailReminder($now, $lastRun, $defaultReminderInMinutes) |
975 | ->addConditionAssigned() |
976 | ->addConditionIgnoreSlots() |
977 | ->addConditionStatus('confirmed') |
978 | ->addLimit($limit, $offset); |
979 | $statement = $this->fetchStatement($selectQuery); |
980 | return $this->readList($statement, $resolveReferences)->withDepartmentHasMailFrom(); |
981 | } |
982 | |
983 | public function readDeallocateProcessList( |
984 | \DateTimeInterface $now, |
985 | $limit = 500, |
986 | $offset = null, |
987 | $resolveReferences = 0 |
988 | ) { |
989 | $selectQuery = new Query\Process(Query\Base::SELECT); |
990 | $selectQuery |
991 | ->addEntityMapping() |
992 | ->addResolvedReferences($resolveReferences) |
993 | ->addConditionDeallocate($now) |
994 | ->addConditionIgnoreSlots() |
995 | ->addLimit($limit, $offset); |
996 | $statement = $this->fetchStatement($selectQuery); |
997 | return $this->readList($statement, $resolveReferences); |
998 | } |
999 | |
1000 | public function isAppointmentAllowedWithSameMail(Entity $entity): bool |
1001 | { |
1002 | if (empty($entity->getClients()) || empty($entity->getClients()->getFirst())) { |
1003 | return true; |
1004 | } |
1005 | |
1006 | $maxAppointmentsPerMail = $entity->scope->getAppointmentsPerMail(); |
1007 | |
1008 | $emailToCheck = $entity->getClients()->getFirst()->email; |
1009 | |
1010 | if ($maxAppointmentsPerMail < 1 || empty($emailToCheck)) { |
1011 | return true; |
1012 | } |
1013 | |
1014 | if ($this->isMailWhitelisted($emailToCheck, $entity->scope)) { |
1015 | return true; |
1016 | } |
1017 | |
1018 | $processes = $this->readProcessListByMailAddress( |
1019 | $entity->getClients()->getFirst()->email, |
1020 | $entity->scope->id |
1021 | ); |
1022 | $activeAppointments = 0; |
1023 | |
1024 | foreach ($processes as $process) { |
1025 | if ($entity->id == $process->id) { |
1026 | return true; |
1027 | } |
1028 | |
1029 | if (in_array($process->getStatus(), ['preconfirmed', 'confirmed'])) { |
1030 | $activeAppointments++; |
1031 | |
1032 | if ($activeAppointments >= $maxAppointmentsPerMail) { |
1033 | return false; |
1034 | } |
1035 | } |
1036 | } |
1037 | |
1038 | return true; |
1039 | } |
1040 | |
1041 | protected function isMailWhitelisted(string $email, ScopeEntity $scope): bool |
1042 | { |
1043 | $emailsWithNoLimit = explode(',', $scope->getWhitelistedMails()); |
1044 | |
1045 | if (empty($emailsWithNoLimit)) { |
1046 | return false; |
1047 | } |
1048 | |
1049 | foreach ($emailsWithNoLimit as $whitelistedMail) { |
1050 | $whitelistedMail = trim($whitelistedMail); |
1051 | |
1052 | if ($email === $whitelistedMail) { |
1053 | return true; |
1054 | } |
1055 | |
1056 | if (str_starts_with($whitelistedMail, '@') && str_contains($email, $whitelistedMail)) { |
1057 | return true; |
1058 | } |
1059 | } |
1060 | |
1061 | return false; |
1062 | } |
1063 | } |