Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
73.33% covered (warning)
73.33%
55 / 75
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
AppointmentReserveService
73.33% covered (warning)
73.33%
55 / 75
50.00% covered (danger)
50.00%
3 / 6
51.42
0.00% covered (danger)
0.00%
0 / 1
 processReservation
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
5.06
 extractClientData
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 validateClientData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 verifyCaptcha
27.27% covered (danger)
27.27%
6 / 22
0.00% covered (danger)
0.00%
0 / 1
19.85
 findMatchingProcess
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
8.10
 reserveAppointment
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3declare(strict_types=1);
4
5namespace BO\Zmscitizenapi\Services\Appointment;
6
7use BO\Zmscitizenapi\Helper\DateTimeFormatHelper;
8use BO\Zmscitizenapi\Models\ThinnedProcess;
9use BO\Zmscitizenapi\Models\Captcha\FriendlyCaptcha;
10use BO\Zmscitizenapi\Services\Core\ValidationService;
11use BO\Zmscitizenapi\Services\Core\ZmsApiFacadeService;
12use BO\Zmsentities\Process;
13
14class 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}