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