Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
87.29% |
412 / 472 |
|
16.00% |
4 / 25 |
CRAP | |
0.00% |
0 / 1 |
ZmsApiFacadeService | |
87.29% |
412 / 472 |
|
16.00% |
4 / 25 |
205.99 | |
0.00% |
0 / 1 |
setLanguageContext | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getError | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setMappedCache | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
getOffices | |
95.74% |
45 / 47 |
|
0.00% |
0 / 1 |
14 | |||
getScopes | |
97.30% |
36 / 37 |
|
0.00% |
0 / 1 |
10 | |||
getServices | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
8.02 | |||
getServicesAndOffices | |
93.75% |
15 / 16 |
|
0.00% |
0 / 1 |
4.00 | |||
getScopeByOfficeId | |
97.96% |
48 / 49 |
|
0.00% |
0 / 1 |
7 | |||
getOfficeListByServiceId | |
92.50% |
37 / 40 |
|
0.00% |
0 / 1 |
16.11 | |||
getScopeById | |
97.50% |
39 / 40 |
|
0.00% |
0 / 1 |
11 | |||
getServicesByOfficeId | |
96.15% |
25 / 26 |
|
0.00% |
0 / 1 |
13 | |||
getServicesProvidedAtOffice | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
30 | |||
getBookableFreeDays | |
94.59% |
35 / 37 |
|
0.00% |
0 / 1 |
9.01 | |||
getFreeAppointments | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
2 | |||
processFreeSlots | |
51.52% |
17 / 33 |
|
0.00% |
0 / 1 |
65.59 | |||
getAvailableAppointments | |
95.24% |
20 / 21 |
|
0.00% |
0 / 1 |
8 | |||
reserveTimeslot | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 | |||
getThinnedProcessById | |
97.22% |
35 / 36 |
|
0.00% |
0 / 1 |
9 | |||
updateClientData | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
preconfirmAppointment | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
confirmAppointment | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
cancelAppointment | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
sendPreconfirmationEmail | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
sendConfirmationEmail | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
sendCancellationEmail | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace BO\Zmscitizenapi\Services\Core; |
6 | |
7 | use BO\Zmscitizenapi\Helper\DateTimeFormatHelper; |
8 | use BO\Zmscitizenapi\Localization\ErrorMessages; |
9 | use BO\Zmscitizenapi\Models\AvailableAppointmentsByOffice; |
10 | use BO\Zmscitizenapi\Models\AvailableDays; |
11 | use BO\Zmscitizenapi\Models\AvailableAppointments; |
12 | use BO\Zmscitizenapi\Models\AvailableDaysByOffice; |
13 | use BO\Zmscitizenapi\Models\Office; |
14 | use BO\Zmscitizenapi\Models\Service; |
15 | use BO\Zmscitizenapi\Models\ThinnedProcess; |
16 | use BO\Zmscitizenapi\Models\ThinnedScope; |
17 | use BO\Zmscitizenapi\Models\Collections\OfficeList; |
18 | use BO\Zmscitizenapi\Models\Collections\OfficeServiceRelationList; |
19 | use BO\Zmscitizenapi\Models\Collections\OfficeServiceAndRelationList; |
20 | use BO\Zmscitizenapi\Models\Collections\ServiceList; |
21 | use BO\Zmscitizenapi\Models\Collections\ThinnedScopeList; |
22 | use BO\Zmscitizenapi\Services\Core\ZmsApiClientService; |
23 | use BO\Zmsentities\Calendar; |
24 | use BO\Zmsentities\Collection\RequestRelationList; |
25 | use BO\Zmsentities\Process; |
26 | use BO\Zmsentities\Scope; |
27 | use BO\Zmsentities\Collection\ScopeList; |
28 | use BO\Zmsentities\Collection\ProviderList; |
29 | use BO\Zmsentities\Collection\RequestList; |
30 | use BO\Zmsentities\Collection\ProcessList; |
31 | |
32 | /** |
33 | * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) |
34 | * @TODO: Break down this facade into smaller domain-specific facades or use the Command pattern |
35 | */ |
36 | class ZmsApiFacadeService |
37 | { |
38 | private const CACHE_KEY_OFFICES = 'processed_offices'; |
39 | private const CACHE_KEY_SCOPES = 'processed_scopes'; |
40 | private const CACHE_KEY_SERVICES = 'processed_services'; |
41 | private const CACHE_KEY_OFFICES_AND_SERVICES = 'processed_offices_and_services'; |
42 | private const CACHE_KEY_OFFICES_BY_SERVICE_PREFIX = 'processed_offices_by_service_'; |
43 | private const CACHE_KEY_SERVICES_BY_OFFICE_PREFIX = 'processed_services_by_office_'; |
44 | |
45 | private static ?string $currentLanguage = null; |
46 | public static function setLanguageContext(?string $language): void |
47 | { |
48 | self::$currentLanguage = $language; |
49 | } |
50 | |
51 | private static function getError(string $key): array |
52 | { |
53 | return ErrorMessages::get($key, self::$currentLanguage); |
54 | } |
55 | |
56 | private static function setMappedCache(string $cacheKey, mixed $data): void |
57 | { |
58 | if (\App::$cache) { |
59 | \App::$cache->set($cacheKey, $data, \App::$SOURCE_CACHE_TTL); |
60 | LoggerService::logInfo('Second-level cache set', [ |
61 | 'key' => $cacheKey, |
62 | 'ttl' => \App::$SOURCE_CACHE_TTL |
63 | ]); |
64 | } |
65 | } |
66 | |
67 | /** |
68 | * @SuppressWarnings(PHPMD.NPathComplexity) |
69 | */ |
70 | public static function getOffices(bool $showUnpublished = false): OfficeList |
71 | { |
72 | $cacheKey = self::CACHE_KEY_OFFICES . ($showUnpublished ? '_unpublished' : ''); |
73 | |
74 | if (\App::$cache && ($cachedData = \App::$cache->get($cacheKey))) { |
75 | return $cachedData; |
76 | } |
77 | |
78 | $providerList = ZmsApiClientService::getOffices() ?? new ProviderList(); |
79 | $scopeList = ZmsApiClientService::getScopes() ?? new ScopeList(); |
80 | $offices = []; |
81 | $scopeMap = []; |
82 | foreach ($scopeList as $scope) { |
83 | if ($scope->getProvider()) { |
84 | $scopeMap[$scope->getProvider()->source . '_' . $scope->getProvider()->id] = $scope; |
85 | } |
86 | } |
87 | |
88 | foreach ($providerList as $provider) { |
89 | if (!$showUnpublished && isset($provider->data['public']) && !(bool) $provider->data['public']) { |
90 | continue; |
91 | } |
92 | |
93 | $matchingScope = $scopeMap[$provider->source . '_' . $provider->id] ?? null; |
94 | $offices[] = new Office( |
95 | id: (int) $provider->id, |
96 | name: $provider->displayName ?? $provider->name, |
97 | address: $provider->data['address'] ?? null, |
98 | showAlternativeLocations: $provider->data['showAlternativeLocations'] ?? null, |
99 | displayNameAlternatives: $provider->data['displayNameAlternatives'] ?? [], |
100 | organization: $provider->data['organization'] ?? null, |
101 | organizationUnit: $provider->data['organizationUnit'] ?? null, |
102 | slotTimeInMinutes: $provider->data['slotTimeInMinutes'] ?? null, |
103 | geo: $provider->data['geo'] ?? null, |
104 | scope: $matchingScope ? new ThinnedScope( |
105 | id: (int) $matchingScope->id, |
106 | provider: MapperService::providerToThinnedProvider($provider), |
107 | shortName: (string) $matchingScope->getShortName(), |
108 | emailFrom: (string) $matchingScope->getEmailFrom(), |
109 | emailRequired: (bool) $matchingScope->getEmailRequired(), |
110 | telephoneActivated: (bool) $matchingScope->getTelephoneActivated(), |
111 | telephoneRequired: (bool) $matchingScope->getTelephoneRequired(), |
112 | customTextfieldActivated: (bool) $matchingScope->getCustomTextfieldActivated(), |
113 | customTextfieldRequired: (bool) $matchingScope->getCustomTextfieldRequired(), |
114 | customTextfieldLabel: $matchingScope->getCustomTextfieldLabel(), |
115 | customTextfield2Activated: (bool) $matchingScope->getCustomTextfield2Activated(), |
116 | customTextfield2Required: (bool) $matchingScope->getCustomTextfield2Required(), |
117 | customTextfield2Label: $matchingScope->getCustomTextfield2Label(), |
118 | captchaActivatedRequired: (bool) $matchingScope->getCaptchaActivatedRequired(), |
119 | displayInfo: $matchingScope->getDisplayInfo(), |
120 | slotsPerAppointment: ((string) $matchingScope->getSlotsPerAppointment() === '' ? null : (string) $matchingScope->getSlotsPerAppointment()) |
121 | ) : null, |
122 | maxSlotsPerAppointment: $matchingScope ? ((string) $matchingScope->getSlotsPerAppointment() === '' ? null : (string) $matchingScope->getSlotsPerAppointment()) : null |
123 | ); |
124 | } |
125 | |
126 | $result = new OfficeList($offices); |
127 | |
128 | self::setMappedCache($cacheKey, $result); |
129 | |
130 | return $result; |
131 | } |
132 | |
133 | public static function getScopes(): ThinnedScopeList|array |
134 | { |
135 | $cacheKey = self::CACHE_KEY_SCOPES; |
136 | |
137 | if (\App::$cache && ($cachedData = \App::$cache->get($cacheKey))) { |
138 | return $cachedData; |
139 | } |
140 | |
141 | $providerList = ZmsApiClientService::getOffices() ?? new ProviderList(); |
142 | $scopeList = ZmsApiClientService::getScopes() ?? new ScopeList(); |
143 | $scopeMap = []; |
144 | foreach ($scopeList as $scope) { |
145 | $scopeProvider = $scope->getProvider(); |
146 | if ($scopeProvider && $scopeProvider->id && $scopeProvider->source) { |
147 | $key = $scopeProvider->source . '_' . $scopeProvider->id; |
148 | $scopeMap[$key] = $scope; |
149 | } |
150 | } |
151 | |
152 | $scopesProjectionList = []; |
153 | foreach ($providerList as $provider) { |
154 | $key = $provider->source . '_' . $provider->id; |
155 | if (isset($scopeMap[$key])) { |
156 | $matchingScope = $scopeMap[$key]; |
157 | $scopesProjectionList[] = new ThinnedScope( |
158 | id: (int) $matchingScope->id, |
159 | provider: MapperService::providerToThinnedProvider($provider), |
160 | shortName: (string) $matchingScope->getShortName(), |
161 | emailFrom: (string) $matchingScope->getEmailFrom(), |
162 | emailRequired: (bool) $matchingScope->getEmailRequired(), |
163 | telephoneActivated: (bool) $matchingScope->getTelephoneActivated(), |
164 | telephoneRequired: (bool) $matchingScope->getTelephoneRequired(), |
165 | customTextfieldActivated: (bool) $matchingScope->getCustomTextfieldActivated(), |
166 | customTextfieldRequired: (bool) $matchingScope->getCustomTextfieldRequired(), |
167 | customTextfieldLabel: $matchingScope->getCustomTextfieldLabel(), |
168 | customTextfield2Activated: (bool) $matchingScope->getCustomTextfield2Activated(), |
169 | customTextfield2Required: (bool) $matchingScope->getCustomTextfield2Required(), |
170 | customTextfield2Label: $matchingScope->getCustomTextfield2Label(), |
171 | captchaActivatedRequired: (bool) $matchingScope->getCaptchaActivatedRequired(), |
172 | displayInfo: $matchingScope->getDisplayInfo(), |
173 | slotsPerAppointment: ((string) $matchingScope->getSlotsPerAppointment() === '' ? null : (string) $matchingScope->getSlotsPerAppointment()) |
174 | ); |
175 | } |
176 | } |
177 | |
178 | $result = new ThinnedScopeList($scopesProjectionList); |
179 | |
180 | self::setMappedCache($cacheKey, $result); |
181 | |
182 | return $result; |
183 | } |
184 | |
185 | public static function getServices(bool $showUnpublished = false): ServiceList|array |
186 | { |
187 | $cacheKey = self::CACHE_KEY_SERVICES . ($showUnpublished ? '_unpublished' : ''); |
188 | |
189 | if (\App::$cache && ($cachedData = \App::$cache->get($cacheKey))) { |
190 | return $cachedData; |
191 | } |
192 | |
193 | $requestList = ZmsApiClientService::getServices() ?? new RequestList(); |
194 | $services = []; |
195 | foreach ($requestList as $request) { |
196 | $additionalData = $request->getAdditionalData(); |
197 | if ( |
198 | !$showUnpublished |
199 | && isset($additionalData['public']) |
200 | && !$additionalData['public'] |
201 | ) { |
202 | continue; |
203 | } |
204 | |
205 | $services[] = new Service(id: (int) $request->getId(), name: $request->getName(), maxQuantity: $additionalData['maxQuantity'] ?? 1); |
206 | } |
207 | |
208 | $result = new ServiceList($services); |
209 | |
210 | self::setMappedCache($cacheKey, $result); |
211 | |
212 | return $result; |
213 | } |
214 | |
215 | public static function getServicesAndOffices(bool $showUnpublished = false): OfficeServiceAndRelationList|array |
216 | { |
217 | $cacheKey = self::CACHE_KEY_OFFICES_AND_SERVICES . ($showUnpublished ? '_unpublished' : ''); |
218 | |
219 | if (\App::$cache && ($cachedData = \App::$cache->get($cacheKey))) { |
220 | return $cachedData; |
221 | } |
222 | |
223 | $providerList = ZmsApiClientService::getOffices() ?? new ProviderList(); |
224 | $requestList = ZmsApiClientService::getServices() ?? new RequestList(); |
225 | $relationList = ZmsApiClientService::getRequestRelationList() ?? new RequestRelationList(); |
226 | |
227 | $offices = MapperService::mapOfficesWithScope($providerList, $showUnpublished) ?? new OfficeList(); |
228 | $services = MapperService::mapServicesWithCombinations( |
229 | $requestList, |
230 | $relationList, |
231 | $showUnpublished |
232 | ) ?? new ServiceList(); |
233 | $relations = MapperService::mapRelations($relationList, $showUnpublished) ?? new OfficeServiceRelationList(); |
234 | |
235 | $result = new OfficeServiceAndRelationList($offices, $services, $relations); |
236 | |
237 | self::setMappedCache($cacheKey, $result); |
238 | |
239 | return $result; |
240 | } |
241 | |
242 | /* Todo add method |
243 | * getCombinableServicesByIds |
244 | * |
245 | * |
246 | * |
247 | */ |
248 | |
249 | public static function getScopeByOfficeId(int $officeId): ThinnedScope|array |
250 | { |
251 | $matchingScope = ZmsApiClientService::getScopesByProviderId(\App::$source_name, $officeId)->getIterator()->current(); |
252 | if (!$matchingScope instanceof Scope) { |
253 | return ['errors' => [self::getError('scopeNotFound')]]; |
254 | } |
255 | |
256 | $providerList = ZmsApiClientService::getOffices() ?? new ProviderList(); |
257 | $providerMap = []; |
258 | foreach ($providerList as $provider) { |
259 | $key = $provider->source . '_' . $provider->id; |
260 | $providerMap[$key] = $provider; |
261 | } |
262 | |
263 | $scopeProvider = $matchingScope->getProvider(); |
264 | $providerKey = $scopeProvider ? ($scopeProvider->source . '_' . $scopeProvider->id) : null; |
265 | $finalProvider = $providerKey && isset($providerMap[$providerKey]) |
266 | ? $providerMap[$providerKey] |
267 | : $scopeProvider; |
268 | $result = [ |
269 | 'id' => $matchingScope->id, |
270 | 'provider' => MapperService::providerToThinnedProvider($finalProvider) ?? null, |
271 | 'shortName' => (string) $matchingScope->getShortName() ?? null, |
272 | 'emailFrom' => (string) $matchingScope->getEmailFrom() ?? null, |
273 | 'emailRequired' => (bool) $matchingScope->getEmailRequired() ?? null, |
274 | 'telephoneActivated' => (bool) $matchingScope->getTelephoneActivated() ?? null, |
275 | 'telephoneRequired' => (bool) $matchingScope->getTelephoneRequired() ?? null, |
276 | 'customTextfieldActivated' => (bool) $matchingScope->getCustomTextfieldActivated() ?? null, |
277 | 'customTextfieldRequired' => (bool) $matchingScope->getCustomTextfieldRequired() ?? null, |
278 | 'customTextfieldLabel' => $matchingScope->getCustomTextfieldLabel() ?? null, |
279 | 'customTextfield2Activated' => (bool) $matchingScope->getCustomTextfield2Activated() ?? null, |
280 | 'customTextfield2Required' => (bool) $matchingScope->getCustomTextfield2Required() ?? null, |
281 | 'customTextfield2Label' => $matchingScope->getCustomTextfield2Label() ?? null, |
282 | 'captchaActivatedRequired' => (bool) $matchingScope->getCaptchaActivatedRequired() ?? null, |
283 | 'displayInfo' => $matchingScope->getDisplayInfo() ?? null, |
284 | 'slotsPerAppointment' => ((string) $matchingScope->getSlotsPerAppointment() === '' ? null : (string) $matchingScope->getSlotsPerAppointment()) ?? null |
285 | ]; |
286 | return new ThinnedScope( |
287 | id: (int) $result['id'], |
288 | provider: $result['provider'], |
289 | shortName: $result['shortName'], |
290 | emailFrom: $result['emailFrom'], |
291 | emailRequired: $result['emailRequired'], |
292 | telephoneActivated: $result['telephoneActivated'], |
293 | telephoneRequired: $result['telephoneRequired'], |
294 | customTextfieldActivated: $result['customTextfieldActivated'], |
295 | customTextfieldRequired: $result['customTextfieldRequired'], |
296 | customTextfieldLabel: $result['customTextfieldLabel'], |
297 | customTextfield2Activated: $result['customTextfield2Activated'], |
298 | customTextfield2Required: $result['customTextfield2Required'], |
299 | customTextfield2Label: $result['customTextfield2Label'], |
300 | captchaActivatedRequired: $result['captchaActivatedRequired'], |
301 | displayInfo: $result['displayInfo'], |
302 | slotsPerAppointment: $result['slotsPerAppointment'] |
303 | ); |
304 | } |
305 | |
306 | /* Todo add method |
307 | * getOfficeById |
308 | * |
309 | * |
310 | * |
311 | */ |
312 | |
313 | /** |
314 | * @SuppressWarnings(PHPMD.NPathComplexity) |
315 | * @TODO: Extract providerMap mapping logic into MapperService |
316 | */ |
317 | public static function getOfficeListByServiceId(int $serviceId, bool $showUnpublished = false): OfficeList|array |
318 | { |
319 | $cacheKey = self::CACHE_KEY_OFFICES_BY_SERVICE_PREFIX . $serviceId . ($showUnpublished ? '_unpublished' : ''); |
320 | |
321 | if (\App::$cache && ($cachedData = \App::$cache->get($cacheKey))) { |
322 | return $cachedData; |
323 | } |
324 | |
325 | $providerList = ZmsApiClientService::getOffices() ?? new ProviderList(); |
326 | $requestRelationList = ZmsApiClientService::getRequestRelationList() ?? new RequestRelationList(); |
327 | $providerMap = []; |
328 | foreach ($providerList as $provider) { |
329 | if (!$showUnpublished && isset($provider->data['public']) && !(bool) $provider->data['public']) { |
330 | continue; |
331 | } |
332 | |
333 | $providerMap[$provider->id] = $provider; |
334 | } |
335 | |
336 | $offices = []; |
337 | foreach ($requestRelationList as $relation) { |
338 | if ((int) $relation->request->id === $serviceId) { |
339 | $providerId = $relation->provider->id; |
340 | if (!isset($providerMap[$providerId])) { |
341 | continue; |
342 | } |
343 | |
344 | $provider = $providerMap[$providerId]; |
345 | $scope = null; |
346 | $scopeData = self::getScopeByOfficeId((int) $provider->id); |
347 | if ($scopeData instanceof ThinnedScope) { |
348 | $scope = $scopeData; |
349 | } |
350 | |
351 | $offices[] = new Office( |
352 | id: (int) $provider->id, |
353 | name: $provider->name, |
354 | showAlternativeLocations: $provider->data['showAlternativeLocations'] ?? null, |
355 | displayNameAlternatives: $provider->data['displayNameAlternatives'] ?? [], |
356 | organization: $provider->data['organization'] ?? null, |
357 | organizationUnit: $provider->data['organizationUnit'] ?? null, |
358 | slotTimeInMinutes: $provider->data['slotTimeInMinutes'] ?? null, |
359 | address: $provider->address ?? null, |
360 | geo: $provider->geo ?? null, |
361 | scope: $scope, |
362 | maxSlotsPerAppointment: $scope ? ((string) $scope->getSlotsPerAppointment() === '' ? null : (string) $scope->getSlotsPerAppointment()) : null |
363 | ); |
364 | } |
365 | } |
366 | |
367 | $errors = ValidationService::validateOfficesNotFound($offices); |
368 | if (is_array($errors) && !empty($errors['errors'])) { |
369 | return $errors; |
370 | } |
371 | |
372 | $result = new OfficeList($offices); |
373 | |
374 | self::setMappedCache($cacheKey, $result); |
375 | |
376 | return $result; |
377 | } |
378 | |
379 | /** |
380 | * @SuppressWarnings(PHPMD.NPathComplexity) |
381 | */ |
382 | public static function getScopeById(?int $scopeId): ThinnedScope|array |
383 | { |
384 | $scopeList = ZmsApiClientService::getScopes() ?? new ScopeList(); |
385 | $providerList = ZmsApiClientService::getOffices() ?? new ProviderList(); |
386 | $matchingScope = null; |
387 | foreach ($scopeList as $scope) { |
388 | if ((int) $scope->id === (int) $scopeId) { |
389 | $matchingScope = $scope; |
390 | break; |
391 | } |
392 | } |
393 | |
394 | $tempScopeList = new ScopeList(); |
395 | if ($matchingScope !== null) { |
396 | $tempScopeList->addEntity($matchingScope); |
397 | } |
398 | $errors = ValidationService::validateScopesNotFound($tempScopeList); |
399 | if (is_array($errors) && !empty($errors['errors'])) { |
400 | return $errors; |
401 | } |
402 | |
403 | $providerMap = []; |
404 | foreach ($providerList as $provider) { |
405 | $key = $provider->source . '_' . $provider->id; |
406 | $providerMap[$key] = $provider; |
407 | } |
408 | |
409 | $scopeProvider = $matchingScope->getProvider(); |
410 | $providerKey = $scopeProvider ? ($scopeProvider->source . '_' . $scopeProvider->id) : null; |
411 | $matchingProv = ($providerKey && isset($providerMap[$providerKey])) |
412 | ? $providerMap[$providerKey] |
413 | : $scopeProvider; |
414 | return new ThinnedScope( |
415 | id: (int) $matchingScope->id, |
416 | provider: MapperService::providerToThinnedProvider($matchingProv), |
417 | shortName: (string) $matchingScope->getShortName() ?? null, |
418 | emailFrom: (string) $matchingScope->getEmailFrom() ?? null, |
419 | emailRequired: (bool) $matchingScope->getEmailRequired() ?? null, |
420 | telephoneActivated: (bool) $matchingScope->getTelephoneActivated() ?? null, |
421 | telephoneRequired: (bool) $matchingScope->getTelephoneRequired() ?? null, |
422 | customTextfieldActivated: (bool) $matchingScope->getCustomTextfieldActivated() ?? null, |
423 | customTextfieldRequired: (bool) $matchingScope->getCustomTextfieldRequired() ?? null, |
424 | customTextfieldLabel: $matchingScope->getCustomTextfieldLabel() ?? null, |
425 | customTextfield2Activated: (bool) $matchingScope->getCustomTextfield2Activated() ?? null, |
426 | customTextfield2Required: (bool) $matchingScope->getCustomTextfield2Required() ?? null, |
427 | customTextfield2Label: $matchingScope->getCustomTextfield2Label() ?? null, |
428 | captchaActivatedRequired: (bool) $matchingScope->getCaptchaActivatedRequired() ?? null, |
429 | displayInfo: $matchingScope->getDisplayInfo() ?? null, |
430 | slotsPerAppointment: ((string) $matchingScope->getSlotsPerAppointment() === '' ? null : (string) $matchingScope->getSlotsPerAppointment()) ?? null |
431 | ); |
432 | } |
433 | |
434 | public static function getServicesByOfficeId(int $officeId, bool $showUnpublished = false): ServiceList|array |
435 | { |
436 | $cacheKey = self::CACHE_KEY_SERVICES_BY_OFFICE_PREFIX . $officeId . ($showUnpublished ? '_unpublished' : ''); |
437 | |
438 | if (\App::$cache && ($cachedData = \App::$cache->get($cacheKey))) { |
439 | return $cachedData; |
440 | } |
441 | |
442 | $requestList = ZmsApiClientService::getServices() ?? new RequestList(); |
443 | $requestRelationList = ZmsApiClientService::getRequestRelationList() ?? new RequestRelationList(); |
444 | $requestMap = []; |
445 | foreach ($requestList as $request) { |
446 | $additionalData = $request->getAdditionalData(); |
447 | if ( |
448 | !$showUnpublished |
449 | && isset($additionalData['public']) |
450 | && !$additionalData['public'] |
451 | ) { |
452 | continue; |
453 | } |
454 | |
455 | $requestMap[$request->id] = $request; |
456 | } |
457 | |
458 | $services = []; |
459 | foreach ($requestRelationList as $relation) { |
460 | if ((int) $relation->provider->id === $officeId) { |
461 | $requestId = $relation->request->id; |
462 | if (isset($requestMap[$requestId])) { |
463 | $request = $requestMap[$requestId]; |
464 | $services[] = new Service(id: (int) $request->id, name: $request->name, maxQuantity: $request->getAdditionalData()['maxQuantity'] ?? 1); |
465 | } |
466 | } |
467 | } |
468 | |
469 | $errors = ValidationService::validateServicesNotFound($services); |
470 | if (is_array($errors) && !empty($errors['errors'])) { |
471 | return $errors; |
472 | } |
473 | |
474 | $result = new ServiceList($services); |
475 | |
476 | self::setMappedCache($cacheKey, $result); |
477 | |
478 | return $result; |
479 | } |
480 | |
481 | public static function getServicesProvidedAtOffice(int $officeId): RequestList|array |
482 | { |
483 | $requestRelationList = ZmsApiClientService::getRequestRelationList() ?? new RequestRelationList(); |
484 | $requestRelationArray = []; |
485 | foreach ($requestRelationList as $relation) { |
486 | $requestRelationArray[] = $relation; |
487 | } |
488 | |
489 | $serviceIds = array_filter($requestRelationArray, function ($relation) use ($officeId) { |
490 | |
491 | return $relation->provider->id === $officeId || (string) $relation->provider->id === (string) $officeId; |
492 | }); |
493 | $serviceIds = array_map(function ($relation) { |
494 | |
495 | return $relation->request->id; |
496 | }, $serviceIds); |
497 | $requestList = ZmsApiClientService::getServices() ?? new RequestList(); |
498 | $requestArray = []; |
499 | foreach ($requestList as $request) { |
500 | $requestArray[] = $request; |
501 | } |
502 | |
503 | $filteredRequests = array_filter($requestArray, function ($request) use ($serviceIds) { |
504 | |
505 | return in_array($request->id, $serviceIds); |
506 | }); |
507 | $resultRequestList = new RequestList(); |
508 | foreach ($filteredRequests as $request) { |
509 | $resultRequestList->addEntity($request); |
510 | } |
511 | |
512 | return $resultRequestList; |
513 | } |
514 | |
515 | public static function getBookableFreeDays( |
516 | array $officeIds, |
517 | array $serviceIds, |
518 | array $serviceCounts, |
519 | string $startDate, |
520 | string $endDate, |
521 | ?bool $groupByOffice = false |
522 | ): AvailableDays|AvailableDaysByOffice|array { |
523 | $firstDay = DateTimeFormatHelper::getInternalDateFromISO($startDate); |
524 | $lastDay = DateTimeFormatHelper::getInternalDateFromISO($endDate); |
525 | $services = []; |
526 | $providers = []; |
527 | $serviceNumber = 0; |
528 | foreach ($serviceIds as $serviceId) { |
529 | $services[] = [ |
530 | 'id' => $serviceId, |
531 | 'source' => \App::$source_name, |
532 | 'slotCount' => $serviceCounts[$serviceNumber], |
533 | ]; |
534 | $serviceNumber++; |
535 | } |
536 | |
537 | foreach ($officeIds as $officeId) { |
538 | $providers[] = [ |
539 | 'id' => $officeId, |
540 | 'source' => \App::$source_name, |
541 | ]; |
542 | } |
543 | |
544 | $freeDays = ZmsApiClientService::getFreeDays(new ProviderList($providers), new RequestList($services), $firstDay, $lastDay) ?? new Calendar(); |
545 | $daysCollection = $freeDays->days; |
546 | $formattedDays = []; |
547 | $scopeToProvider = []; |
548 | |
549 | foreach ($freeDays->scopes as $scope) { |
550 | $scopeToProvider[$scope['id']] = $scope['provider']['id']; |
551 | } |
552 | |
553 | foreach ($daysCollection as $day) { |
554 | $day = [ |
555 | 'time' => sprintf('%04d-%02d-%02d', $day->year, $day->month, $day->day), |
556 | 'providerIDs' => isset($day->scopeIDs) ? implode(',', array_map(function ($scopeId) use ($scopeToProvider) { |
557 | return $scopeToProvider[$scopeId]; |
558 | }, explode(',', $day->scopeIDs))) : '' |
559 | ]; |
560 | $formattedDays[] = $day; |
561 | } |
562 | |
563 | $errors = ValidationService::validateAppointmentDaysNotFound($formattedDays); |
564 | if (is_array($errors) && !empty($errors['errors'])) { |
565 | return $errors; |
566 | } |
567 | |
568 | if ($groupByOffice) { |
569 | return new AvailableDaysByOffice($formattedDays); |
570 | } |
571 | |
572 | return new AvailableDays(array_column($formattedDays, 'time')); |
573 | } |
574 | |
575 | public static function getFreeAppointments(int $officeId, array $serviceIds, array $serviceCounts, array $date): ProcessList|array |
576 | { |
577 | $office = [ |
578 | 'id' => $officeId, |
579 | 'source' => \App::$source_name |
580 | ]; |
581 | $requests = []; |
582 | foreach ($serviceIds as $index => $serviceId) { |
583 | $service = [ |
584 | 'id' => $serviceId, |
585 | 'source' => \App::$source_name, |
586 | 'slotCount' => $serviceCounts[$index] |
587 | ]; |
588 | $requests = array_merge($requests, array_fill(0, $service['slotCount'], $service)); |
589 | } |
590 | |
591 | return ZmsApiClientService::getFreeTimeslots(new ProviderList([$office]), new RequestList($requests), $date, $date); |
592 | } |
593 | |
594 | /** |
595 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) |
596 | */ |
597 | private static function processFreeSlots(ProcessList $freeSlots, bool $groupByOffice = false): array |
598 | { |
599 | $errors = ValidationService::validateGetProcessFreeSlots($freeSlots); |
600 | if (is_array($errors) && !empty($errors['errors'])) { |
601 | return $errors; |
602 | } |
603 | |
604 | $currentTimestamp = time(); |
605 | if ($groupByOffice) { |
606 | $grouped = []; |
607 | foreach ($freeSlots as $slot) { |
608 | $officeId = (string)($slot->scope->provider->id ?? ''); |
609 | if (!isset($grouped[$officeId])) { |
610 | $grouped[$officeId] = []; |
611 | } |
612 | if (isset($slot->appointments) && is_iterable($slot->appointments)) { |
613 | foreach ($slot->appointments as $appointment) { |
614 | if (isset($appointment->date)) { |
615 | $timestamp = (int) $appointment->date; |
616 | if ($timestamp > $currentTimestamp) { |
617 | $grouped[$officeId][] = $timestamp; |
618 | } |
619 | } |
620 | } |
621 | } |
622 | } |
623 | // Sort each office's appointments |
624 | foreach ($grouped as &$arr) { |
625 | sort($arr); |
626 | } |
627 | unset($arr); |
628 | // Optionally validate grouped timestamps here if needed |
629 | return $grouped; |
630 | } else { |
631 | $timestamps = []; |
632 | foreach ($freeSlots as $slot) { |
633 | if (isset($slot->appointments) && is_iterable($slot->appointments)) { |
634 | foreach ($slot->appointments as $appointment) { |
635 | if (isset($appointment->date)) { |
636 | $timestamp = (int) $appointment->date; |
637 | if ($timestamp > $currentTimestamp) { |
638 | $timestamps[] = $timestamp; |
639 | } |
640 | } |
641 | } |
642 | } |
643 | } |
644 | sort($timestamps); |
645 | |
646 | $errors = ValidationService::validateGetProcessByIdTimestamps($timestamps); |
647 | if (is_array($errors) && !empty($errors['errors'])) { |
648 | return $errors; |
649 | } |
650 | |
651 | return $timestamps; |
652 | } |
653 | } |
654 | |
655 | public static function getAvailableAppointments( |
656 | string $date, |
657 | array $officeIds, |
658 | array $serviceIds, |
659 | array $serviceCounts, |
660 | ?bool $groupByOffice = false |
661 | ): AvailableAppointments|AvailableAppointmentsByOffice|array { |
662 | $requests = []; |
663 | $providers = []; |
664 | foreach ($serviceIds as $index => $serviceId) { |
665 | $slotCount = isset($serviceCounts[$index]) ? intval($serviceCounts[$index]) : 1; |
666 | for ($i = 0; $i < $slotCount; $i++) { |
667 | $requests[] = [ |
668 | 'id' => $serviceId, |
669 | 'source' => \App::$source_name |
670 | ]; |
671 | } |
672 | } |
673 | |
674 | foreach ($officeIds as $officeId) { |
675 | $providers[] = [ |
676 | 'id' => $officeId, |
677 | 'source' => \App::$source_name |
678 | ]; |
679 | } |
680 | |
681 | $freeSlots = ZmsApiClientService::getFreeTimeslots(new ProviderList($providers), new RequestList($requests), DateTimeFormatHelper::getInternalDateFromISO($date), DateTimeFormatHelper::getInternalDateFromISO($date)) ?? new ProcessList(); |
682 | $result = self::processFreeSlots($freeSlots, $groupByOffice); |
683 | if (isset($result['errors']) && !empty($result['errors'])) { |
684 | return $result; |
685 | } |
686 | |
687 | if ($groupByOffice) { |
688 | return new AvailableAppointmentsByOffice($result); |
689 | } |
690 | |
691 | return new AvailableAppointments($result); |
692 | } |
693 | |
694 | public static function reserveTimeslot(Process $appointmentProcess, array $serviceIds, array $serviceCounts): ThinnedProcess|array |
695 | { |
696 | $errors = ValidationService::validateServiceArrays($serviceIds, $serviceCounts); |
697 | if (!empty($errors)) { |
698 | return $errors; |
699 | } |
700 | $process = ZmsApiClientService::reserveTimeslot($appointmentProcess, $serviceIds, $serviceCounts); |
701 | return MapperService::processToThinnedProcess($process); |
702 | } |
703 | |
704 | public static function getThinnedProcessById(?int $processId, ?string $authKey): ThinnedProcess|array |
705 | { |
706 | |
707 | $process = ZmsApiClientService::getProcessById($processId, $authKey); |
708 | $errors = ValidationService::validateGetProcessNotFound($process); |
709 | if (is_array($errors) && !empty($errors['errors'])) { |
710 | return $errors; |
711 | } |
712 | $thinnedProcess = MapperService::processToThinnedProcess($process); |
713 | |
714 | $providerList = ZmsApiClientService::getOffices() ?? new ProviderList(); |
715 | $providerMap = []; |
716 | foreach ($providerList as $provider) { |
717 | $key = $provider->getSource() . '_' . $provider->id; |
718 | $providerMap[$key] = $provider; |
719 | } |
720 | |
721 | $thinnedScope = null; |
722 | if ($process->scope instanceof Scope) { |
723 | $scopeProvider = $process->scope->getProvider(); |
724 | $providerKey = $scopeProvider ? ($scopeProvider->getSource() . '_' . $scopeProvider->id) : null; |
725 | $matchingProvider = $providerKey && isset($providerMap[$providerKey]) ? $providerMap[$providerKey] : $scopeProvider; |
726 | $thinnedProvider = MapperService::providerToThinnedProvider($matchingProvider); |
727 | $thinnedScope = new ThinnedScope( |
728 | id: (int) $process->scope->id, |
729 | provider: $thinnedProvider, |
730 | shortName: (string) $process->scope->getShortName() ?? null, |
731 | emailFrom: (string) $process->scope->getEmailFrom() ?? null, |
732 | emailRequired: (bool) $process->scope->getEmailRequired() ?? false, |
733 | telephoneActivated: (bool) $process->scope->getTelephoneActivated() ?? false, |
734 | telephoneRequired: (bool) $process->scope->getTelephoneRequired() ?? false, |
735 | customTextfieldActivated: (bool) $process->scope->getCustomTextfieldActivated() ?? false, |
736 | customTextfieldRequired: (bool) $process->scope->getCustomTextfieldRequired() ?? false, |
737 | customTextfieldLabel: $process->scope->getCustomTextfieldLabel() ?? null, |
738 | customTextfield2Activated: (bool) $process->scope->getCustomTextfield2Activated() ?? false, |
739 | customTextfield2Required: (bool) $process->scope->getCustomTextfield2Required() ?? false, |
740 | customTextfield2Label: $process->scope->getCustomTextfield2Label() ?? null, |
741 | captchaActivatedRequired: (bool) $process->scope->getCaptchaActivatedRequired() ?? false, |
742 | displayInfo: $process->scope->getDisplayInfo() ?? null, |
743 | slotsPerAppointment: ((string) $process->scope->getSlotsPerAppointment() === '' ? null : (string) $process->scope->getSlotsPerAppointment()) ?? null |
744 | ); |
745 | } |
746 | |
747 | $thinnedProcess->scope = $thinnedScope; |
748 | return $thinnedProcess; |
749 | } |
750 | |
751 | public static function updateClientData(Process $reservedProcess): Process|array |
752 | { |
753 | $clientUpdateResult = ZmsApiClientService::submitClientData($reservedProcess); |
754 | if (isset($clientUpdateResult['error'])) { |
755 | return $clientUpdateResult; |
756 | } |
757 | return $clientUpdateResult; |
758 | } |
759 | |
760 | public static function preconfirmAppointment(Process $reservedProcess): Process|array |
761 | { |
762 | $clientUpdateResult = ZmsApiClientService::preconfirmProcess($reservedProcess); |
763 | if (isset($clientUpdateResult['error'])) { |
764 | return $clientUpdateResult; |
765 | } |
766 | return $clientUpdateResult; |
767 | } |
768 | |
769 | public static function confirmAppointment(Process $preconfirmedProcess): Process|array |
770 | { |
771 | $clientUpdateResult = ZmsApiClientService::confirmProcess($preconfirmedProcess); |
772 | if (isset($clientUpdateResult['error'])) { |
773 | return $clientUpdateResult; |
774 | } |
775 | return $clientUpdateResult; |
776 | } |
777 | |
778 | public static function cancelAppointment(Process $confirmedProcess): Process|array |
779 | { |
780 | $clientUpdateResult = ZmsApiClientService::cancelAppointment($confirmedProcess); |
781 | if (isset($clientUpdateResult['error'])) { |
782 | return $clientUpdateResult; |
783 | } |
784 | return $clientUpdateResult; |
785 | } |
786 | |
787 | public static function sendPreconfirmationEmail(Process $reservedProcess): Process|array |
788 | { |
789 | $clientUpdateResult = ZmsApiClientService::sendPreconfirmationEmail($reservedProcess); |
790 | if (isset($clientUpdateResult['error'])) { |
791 | return $clientUpdateResult; |
792 | } |
793 | return $clientUpdateResult; |
794 | } |
795 | |
796 | public static function sendConfirmationEmail(Process $preconfirmedProcess): Process|array |
797 | { |
798 | $clientUpdateResult = ZmsApiClientService::sendConfirmationEmail($preconfirmedProcess); |
799 | if (isset($clientUpdateResult['error'])) { |
800 | return $clientUpdateResult; |
801 | } |
802 | return $clientUpdateResult; |
803 | } |
804 | |
805 | public static function sendCancellationEmail(Process $confirmedProcess): Process|array |
806 | { |
807 | $clientUpdateResult = ZmsApiClientService::sendCancellationEmail($confirmedProcess); |
808 | if (isset($clientUpdateResult['error'])) { |
809 | return $clientUpdateResult; |
810 | } |
811 | return $clientUpdateResult; |
812 | } |
813 | } |