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