Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.02% covered (success)
93.02%
80 / 86
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
AppointmentUpdateService
93.02% covered (success)
93.02%
80 / 86
75.00% covered (warning)
75.00%
6 / 8
56.03
0.00% covered (danger)
0.00%
0 / 1
 processUpdate
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 validateClientData
88.24% covered (warning)
88.24%
30 / 34
0.00% covered (danger)
0.00%
0 / 1
9.13
 extractClientData
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
21
 isRebookingUpdate
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getReservedProcess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 updateProcessWithClientData
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
11
 writeFamilyNameIfAllowed
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 saveProcessUpdate
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
5.58
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\Core\ValidationService;
10use BO\Zmscitizenapi\Services\Core\ZmsApiFacadeService;
11use BO\Zmscitizenapi\Services\Core\MapperService;
12use BO\Zmsentities\Helper\ProcessPlainText;
13
14class AppointmentUpdateService
15{
16    public function processUpdate(array $body, ?AuthenticatedUser $authenticatedUser): ThinnedProcess|array
17    {
18        $clientData = $this->extractClientData($body);
19
20        $validated = $this->validateClientData($clientData, $authenticatedUser);
21        if (!$validated instanceof ThinnedProcess) {
22            return $validated;
23        }
24
25        $updatedProcess = $this->updateProcessWithClientData(
26            $validated,
27            $clientData,
28            $this->isRebookingUpdate($clientData)
29        );
30        return $this->saveProcessUpdate($updatedProcess, $authenticatedUser);
31    }
32
33    /**
34     * @return ThinnedProcess|array{errors: array}
35     */
36    private function validateClientData(object $data, ?AuthenticatedUser $authenticatedUser): ThinnedProcess|array
37    {
38        $authErrors = ValidationService::validateGetProcessById($data->processId, $data->authKey);
39        if ($authErrors['errors'] !== []) {
40            return ['errors' => $authErrors['errors']];
41        }
42
43        $reservedProcess = $this->getReservedProcess($data->processId, $data->authKey, $authenticatedUser);
44        if (!$reservedProcess instanceof ThinnedProcess) {
45            $errors = array_key_exists('errors', $reservedProcess) && is_array($reservedProcess['errors'])
46                ? $reservedProcess['errors']
47                : [];
48            return ['errors' => $errors];
49        }
50
51        $statusErrors = ValidationService::validateAppointmentReservedStatus($reservedProcess);
52        if ($statusErrors['errors'] !== []) {
53            return ['errors' => $statusErrors['errors']];
54        }
55
56        if ($this->isRebookingUpdate($data)) {
57            $lockErrors = ValidationService::validateUnchangedStoredContact(
58                $reservedProcess,
59                $data->familyName,
60                $data->email,
61                $data->telephone,
62                $data->customTextfield,
63                $data->customTextfield2
64            );
65            if ($lockErrors['errors'] !== []) {
66                return ['errors' => $lockErrors['errors']];
67            }
68        }
69
70        $fieldErrors = ValidationService::validateAppointmentUpdateFields(
71            $data->familyName,
72            $data->email,
73            $data->telephone,
74            $data->customTextfield,
75            $data->customTextfield2,
76            $reservedProcess->scope ?? null
77        );
78        if ($fieldErrors['errors'] !== []) {
79            return ['errors' => $fieldErrors['errors']];
80        }
81
82        return $reservedProcess;
83    }
84
85    private function extractClientData(array $body): object
86    {
87        return (object) [
88            'processId' => isset($body['processId']) && is_numeric($body['processId'])
89                ? (int) $body['processId']
90                : null,
91            'authKey' => isset($body['authKey']) && is_string($body['authKey']) && trim($body['authKey']) !== ''
92                ? htmlspecialchars(trim($body['authKey']), ENT_QUOTES, 'UTF-8')
93                : null,
94            'familyName' => isset($body['familyName']) && is_string($body['familyName']) ? $body['familyName'] : null,
95            'email' => isset($body['email']) && is_string($body['email']) ? $body['email'] : null,
96            'telephone' => isset($body['telephone']) && is_string($body['telephone']) ? $body['telephone'] : null,
97            'customTextfield' => isset($body['customTextfield']) && is_string($body['customTextfield']) ? $body['customTextfield'] : null,
98            'customTextfield2' => isset($body['customTextfield2']) && is_string($body['customTextfield2']) ? $body['customTextfield2'] : null,
99            'sourceProcessId' => isset($body['sourceProcessId']) && is_numeric($body['sourceProcessId'])
100                ? (int) $body['sourceProcessId']
101                : null,
102            'sourceAuthKey' => isset($body['sourceAuthKey']) && is_string($body['sourceAuthKey'])
103                && trim($body['sourceAuthKey']) !== ''
104                ? htmlspecialchars(trim($body['sourceAuthKey']), ENT_QUOTES, 'UTF-8')
105                : null,
106        ];
107    }
108
109    private function isRebookingUpdate(object $data): bool
110    {
111        return ($data->sourceProcessId ?? null) !== null
112            && ($data->sourceAuthKey ?? null) !== null;
113    }
114
115    private function getReservedProcess(int $processId, ?string $authKey, ?AuthenticatedUser $user): ThinnedProcess|array
116    {
117        return ZmsApiFacadeService::getThinnedProcessById($processId, $authKey, $user);
118    }
119
120    private function updateProcessWithClientData(
121        ThinnedProcess $process,
122        object $data,
123        bool $lockStoredContact = false
124    ): ThinnedProcess {
125        $this->writeFamilyNameIfAllowed($process, $data, $lockStoredContact);
126        if (!$lockStoredContact || !ValidationService::isFilledEmail($process->email)) {
127            $process->email = $data->email ?? $process->email ?? null;
128        }
129        if (!$lockStoredContact || !ValidationService::isFilledContactValue($process->telephone)) {
130            $process->telephone = $data->telephone ?? $process->telephone ?? null;
131        }
132        if (
133            $data->customTextfield !== null
134            && (!$lockStoredContact || !ValidationService::isFilledContactValue($process->customTextfield))
135        ) {
136            $process->customTextfield = ProcessPlainText::normalize($data->customTextfield);
137        }
138        if (
139            $data->customTextfield2 !== null
140            && (!$lockStoredContact || !ValidationService::isFilledContactValue($process->customTextfield2))
141        ) {
142            $process->customTextfield2 = ProcessPlainText::normalize($data->customTextfield2);
143        }
144        return $process;
145    }
146
147    private function writeFamilyNameIfAllowed(
148        ThinnedProcess $process,
149        object $data,
150        bool $lockStoredContact
151    ): void {
152        if (
153            !$lockStoredContact
154            || !ValidationService::isFilledContactValue($process->familyName)
155            || ValidationService::isSameOrCompletedFamilyName($process->familyName, $data->familyName ?? null)
156        ) {
157            $process->familyName = $data->familyName ?? $process->familyName ?? null;
158        }
159    }
160
161    private function saveProcessUpdate(ThinnedProcess $process, ?AuthenticatedUser $authenticatedUser): ThinnedProcess|array
162    {
163        $processEntity = MapperService::thinnedProcessToProcess($process);
164        if (!is_null($authenticatedUser) && is_null($processEntity->getExternalUserId())) {
165            $processEntity->setExternalUserId($authenticatedUser->getExternalUserId());
166        }
167        $result = ZmsApiFacadeService::updateClientData($processEntity);
168        if (is_array($result) && !empty($result['errors'])) {
169            return $result;
170        }
171
172        return MapperService::processToThinnedProcess($result);
173    }
174}