Lines 92.30% 72 / 78
Functions and Methods 55.55% 5 / 9
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
AppointmentConfirmService 92.30% 72 / 78 55.55% 5 / 9 38.66 0.00% 0 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 processConfirm 91.30% 21 / 23 0.00% 0 / 1 10.07
 extractClientData 100.00% 15 / 15 100.00% 1 / 1 11
 isRebookingConfirm 100.00% 2 / 2 100.00% 1 / 1 2
 validateClientData 100.00% 4 / 4 100.00% 1 / 1 3
 loadSourceProcessForRebooking 84.61% 11 / 13 0.00% 0 / 1 4.06
 getReservedProcess 92.30% 12 / 13 0.00% 0 / 1 3.00
 confirmProcess 80.00% 4 / 5 0.00% 0 / 1 3.07
 sendConfirmationEmail 100.00% 2 / 2 100.00% 1 / 1 1
1<?php
2
3declare(strict_types=1);
4
5namespace BO\Zmscitizenapi\Services\Appointment;
6
7use BO\Zmscitizenapi\Models\AuthenticatedUser;
8use BO\Zmscitizenapi\Models\ThinnedProcess;
9use BO\Zmscitizenapi\Services\Captcha\CaptchaService;
10use BO\Zmscitizenapi\Services\Core\ValidationService;
11use BO\Zmscitizenapi\Services\Core\ZmsApiFacadeService;
12use BO\Zmscitizenapi\Services\Core\MapperService;
13
14class AppointmentConfirmService
15{
16    private CaptchaService $captchaService;
17
18    public function __construct()
19    {
20        $this->captchaService = new CaptchaService();
21    }
22
23    public function processConfirm(array $body, ?AuthenticatedUser $authenticatedUser): ThinnedProcess|array
24    {
25        $clientData = $this->extractClientData($body);
26        $errors = $this->validateClientData($clientData);
27        if (!empty($errors['errors'])) {
28            return $errors;
29        }
30
31        $sourceProcess = $this->loadSourceProcessForRebooking($clientData);
32        if (is_array($sourceProcess) && ($sourceProcess['errors'] ?? []) !== []) {
33            return $sourceProcess;
34        }
35
36        $reservedProcess = $this->getReservedProcess(
37            $clientData->processId,
38            $clientData->authKey,
39            $authenticatedUser,
40            $sourceProcess instanceof ThinnedProcess ? $sourceProcess : null
41        );
42        if (is_array($reservedProcess) && !empty($reservedProcess['errors'])) {
43            return $reservedProcess;
44        }
45
46        // Todo: check if the email template confirmed exists for the scope before submitting and sending
47        $result = $this->confirmProcess($reservedProcess);
48        if (is_array($result) && !empty($result['errors'])) {
49            return $result;
50        }
51
52        $token = $this->captchaService->generateToken();
53        $result->setCaptchaToken($token);
54
55        if ($result->status === 'confirmed') {
56            $this->sendConfirmationEmail($result);
57        }
58
59        return $result;
60    }
61
62
63    private function extractClientData(array $body): object
64    {
65        return (object) [
66            'processId' => isset($body['processId']) && is_numeric($body['processId'])
67                ? (int) $body['processId']
68                : null,
69            'authKey' => isset($body['authKey']) && is_string($body['authKey']) && trim($body['authKey']) !== ''
70                ? htmlspecialchars(trim($body['authKey']), ENT_QUOTES, 'UTF-8')
71                : null,
72            'sourceProcessId' => isset($body['sourceProcessId']) && is_numeric($body['sourceProcessId'])
73                ? (int) $body['sourceProcessId']
74                : null,
75            'sourceAuthKey' => isset($body['sourceAuthKey']) && is_string($body['sourceAuthKey'])
76                && trim($body['sourceAuthKey']) !== ''
77                ? htmlspecialchars(trim($body['sourceAuthKey']), ENT_QUOTES, 'UTF-8')
78                : null,
79        ];
80    }
81
82    private function isRebookingConfirm(object $data): bool
83    {
84        return ($data->sourceProcessId ?? null) !== null
85            && ($data->sourceAuthKey ?? null) !== null;
86    }
87
88    private function validateClientData(object $data): array
89    {
90        $errors = ValidationService::validateGetProcessById($data->processId, $data->authKey);
91        if ($errors['errors'] !== [] || !$this->isRebookingConfirm($data)) {
92            return $errors;
93        }
94
95        return ValidationService::validateGetProcessById($data->sourceProcessId, $data->sourceAuthKey);
96    }
97
98    /**
99     * @return ThinnedProcess|array{errors: array}|null
100     */
101    private function loadSourceProcessForRebooking(object $clientData): ThinnedProcess|array|null
102    {
103        if (!$this->isRebookingConfirm($clientData)) {
104            return null;
105        }
106
107        $sourceProcess = ZmsApiFacadeService::getThinnedProcessById(
108            $clientData->sourceProcessId,
109            $clientData->sourceAuthKey,
110            null
111        );
112        if (!$sourceProcess instanceof ThinnedProcess) {
113            return $sourceProcess;
114        }
115
116        $rebookingErrors = ValidationService::validateRebookingAllowed($sourceProcess->scope ?? null);
117        if ($rebookingErrors['errors'] !== []) {
118            return $rebookingErrors;
119        }
120
121        return $sourceProcess;
122    }
123
124    private function getReservedProcess(
125        int $processId,
126        ?string $authKey,
127        ?AuthenticatedUser $user,
128        ?ThinnedProcess $sourceProcess
129    ): ThinnedProcess|array {
130        $process = ZmsApiFacadeService::getProcessById($processId, $authKey, $user);
131        $notFound = ValidationService::validateGetProcessNotFound($process);
132        if (!empty($notFound['errors'])) {
133            return $notFound;
134        }
135
136        $thinned = MapperService::processToThinnedProcess($process);
137        $confirmErrors = ValidationService::validateAppointmentConfirm(
138            $thinned,
139            $process->getExternalUserId(),
140            $sourceProcess
141        );
142        if ($confirmErrors['errors'] !== []) {
143            return $confirmErrors;
144        }
145
146        return $thinned;
147    }
148
149    private function confirmProcess(ThinnedProcess $process): ThinnedProcess|array
150    {
151        $processEntity = MapperService::thinnedProcessToProcess($process);
152        $result = ZmsApiFacadeService::confirmAppointment($processEntity);
153        if (is_array($result) && !empty($result['errors'])) {
154            return $result;
155        }
156
157        return MapperService::processToThinnedProcess($result);
158    }
159
160    private function sendConfirmationEmail(ThinnedProcess $process): void
161    {
162        $processEntity = MapperService::thinnedProcessToProcess($process);
163        ZmsApiFacadeService::sendConfirmationEmail($processEntity);
164    }
165}