Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
73.33% |
55 / 75 |
|
50.00% |
3 / 6 |
CRAP | |
0.00% |
0 / 1 |
AppointmentReserveService | |
73.33% |
55 / 75 |
|
50.00% |
3 / 6 |
51.42 | |
0.00% |
0 / 1 |
processReservation | |
86.67% |
13 / 15 |
|
0.00% |
0 / 1 |
5.06 | |||
extractClientData | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
5 | |||
validateClientData | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
verifyCaptcha | |
27.27% |
6 / 22 |
|
0.00% |
0 / 1 |
19.85 | |||
findMatchingProcess | |
88.24% |
15 / 17 |
|
0.00% |
0 / 1 |
8.10 | |||
reserveAppointment | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
7 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace BO\Zmscitizenapi\Services\Appointment; |
6 | |
7 | use BO\Zmscitizenapi\Helper\DateTimeFormatHelper; |
8 | use BO\Zmscitizenapi\Models\ThinnedProcess; |
9 | use BO\Zmscitizenapi\Models\Captcha\FriendlyCaptcha; |
10 | use BO\Zmscitizenapi\Services\Core\ValidationService; |
11 | use BO\Zmscitizenapi\Services\Core\ZmsApiFacadeService; |
12 | use BO\Zmsentities\Process; |
13 | |
14 | class AppointmentReserveService |
15 | { |
16 | public function processReservation(array $body): ThinnedProcess|array |
17 | { |
18 | $clientData = $this->extractClientData($body); |
19 | $errors = $this->validateClientData($clientData); |
20 | if (!empty($errors['errors'])) { |
21 | return $errors; |
22 | } |
23 | |
24 | $captchaErrors = $this->verifyCaptcha($clientData->officeId, $clientData->captchaSolution); |
25 | if (!empty($captchaErrors['errors'])) { |
26 | return $captchaErrors; |
27 | } |
28 | |
29 | $errors = ValidationService::validateServiceLocationCombination($clientData->officeId, $clientData->serviceIds); |
30 | if (!empty($errors['errors'])) { |
31 | return $errors; |
32 | } |
33 | |
34 | $selectedProcess = $this->findMatchingProcess($clientData->officeId, $clientData->serviceIds, $clientData->serviceCounts, $clientData->timestamp); |
35 | $errors = ValidationService::validateGetProcessNotFound($selectedProcess); |
36 | if (!empty($errors['errors'])) { |
37 | return $errors; |
38 | } |
39 | |
40 | return $this->reserveAppointment($selectedProcess, $clientData->serviceIds, $clientData->serviceCounts, $clientData->officeId); |
41 | } |
42 | |
43 | private function extractClientData(array $body): object |
44 | { |
45 | return (object) [ |
46 | 'officeId' => isset($body['officeId']) && is_numeric($body['officeId']) ? (int) $body['officeId'] : null, |
47 | 'serviceIds' => $body['serviceId'] ?? null, |
48 | 'serviceCounts' => $body['serviceCount'] ?? [1], |
49 | 'captchaSolution' => $body['captchaSolution'] ?? null, |
50 | 'timestamp' => isset($body['timestamp']) && is_numeric($body['timestamp']) ? (int) $body['timestamp'] : null, |
51 | ]; |
52 | } |
53 | |
54 | private function validateClientData(object $data): array |
55 | { |
56 | return ValidationService::validatePostAppointmentReserve($data->officeId, $data->serviceIds, $data->serviceCounts, $data->timestamp); |
57 | } |
58 | |
59 | private function verifyCaptcha(?int $officeId, ?string $captchaSolution): array |
60 | { |
61 | $providerScope = ZmsApiFacadeService::getScopeByOfficeId($officeId); |
62 | $captchaRequired = \App::$CAPTCHA_ENABLED === true && |
63 | isset($providerScope->captchaActivatedRequired) && |
64 | $providerScope->captchaActivatedRequired === "1"; |
65 | if (!$captchaRequired) { |
66 | return []; |
67 | } |
68 | |
69 | try { |
70 | $captcha = new FriendlyCaptcha(); |
71 | if (!$captcha->verifyCaptcha($captchaSolution)) { |
72 | return ['errors' => [ |
73 | [ |
74 | 'errorCode' => 'captchaVerificationFailed', |
75 | 'statusCode' => 400 |
76 | ] |
77 | ]]; |
78 | } |
79 | } catch (\Exception $e) { |
80 | return ['errors' => [ |
81 | [ |
82 | 'errorCode' => 'captchaVerificationError', |
83 | 'statusCode' => 400 |
84 | ] |
85 | ]]; |
86 | } |
87 | |
88 | return []; |
89 | } |
90 | |
91 | private function findMatchingProcess(int $officeId, array $serviceIds, array $serviceCounts, int $timestamp): ?Process |
92 | { |
93 | $freeAppointments = ZmsApiFacadeService::getFreeAppointments($officeId, $serviceIds, $serviceCounts, DateTimeFormatHelper::getInternalDateFromTimestamp($timestamp)); |
94 | foreach ($freeAppointments as $process) { |
95 | if (!isset($process->appointments) || empty($process->appointments)) { |
96 | continue; |
97 | } |
98 | |
99 | foreach ($process->appointments as $appointment) { |
100 | if ((int) $appointment->date === $timestamp) { |
101 | $requestIds = []; |
102 | if ($process->requests) { |
103 | foreach ($process->requests as $request) { |
104 | $requestIds[] = $request->getId(); |
105 | } |
106 | } |
107 | |
108 | $processData = [ |
109 | 'requests' => $requestIds, |
110 | 'appointments' => [$appointment] |
111 | ]; |
112 | $process->withUpdatedData($processData, new \DateTime("@$timestamp"), $process->scope); |
113 | return $process; |
114 | } |
115 | } |
116 | } |
117 | |
118 | return null; |
119 | } |
120 | |
121 | private function reserveAppointment(Process $process, array $serviceIds, array $serviceCounts, int $officeId): ThinnedProcess |
122 | { |
123 | $process->clients = [ |
124 | [ |
125 | 'email' => 'test@muenchen.de' |
126 | ] |
127 | ]; |
128 | $reservedProcess = ZmsApiFacadeService::reserveTimeslot($process, $serviceIds, $serviceCounts); |
129 | if ($reservedProcess && $reservedProcess->scope && $reservedProcess->scope->id) { |
130 | $scopeId = $reservedProcess->scope->id; |
131 | $scope = ZmsApiFacadeService::getScopeById((int) $scopeId); |
132 | if (!isset($scope['errors']) && isset($scope) && !empty($scope)) { |
133 | $reservedProcess->scope = $scope; |
134 | $reservedProcess->officeId = $officeId; |
135 | } |
136 | } |
137 | |
138 | return $reservedProcess; |
139 | } |
140 | } |