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