Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.91% |
190 / 209 |
|
40.00% |
4 / 10 |
CRAP | |
0.00% |
0 / 1 |
MapperService | |
90.91% |
190 / 209 |
|
40.00% |
4 / 10 |
120.26 | |
0.00% |
0 / 1 |
mapScopeForProvider | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
5 | |||
mapOfficesWithScope | |
94.29% |
33 / 35 |
|
0.00% |
0 / 1 |
27.14 | |||
mapCombinable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
mapServicesWithCombinations | |
96.00% |
24 / 25 |
|
0.00% |
0 / 1 |
10 | |||
mapRelations | |
91.67% |
11 / 12 |
|
0.00% |
0 / 1 |
4.01 | |||
scopeToThinnedScope | |
86.96% |
20 / 23 |
|
0.00% |
0 / 1 |
13.38 | |||
processToThinnedProcess | |
80.56% |
29 / 36 |
|
0.00% |
0 / 1 |
38.06 | |||
thinnedProcessToProcess | |
92.31% |
60 / 65 |
|
0.00% |
0 / 1 |
10.05 | |||
contactToThinnedContact | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
providerToThinnedProvider | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
7 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace BO\Zmscitizenapi\Services\Core; |
6 | |
7 | use BO\Zmscitizenapi\Helper\ClientIpHelper; |
8 | use BO\Zmscitizenapi\Models\Office; |
9 | use BO\Zmscitizenapi\Models\Combinable; |
10 | use BO\Zmscitizenapi\Models\OfficeServiceRelation; |
11 | use BO\Zmscitizenapi\Models\Service; |
12 | use BO\Zmscitizenapi\Models\ThinnedContact; |
13 | use BO\Zmscitizenapi\Models\ThinnedProcess; |
14 | use BO\Zmscitizenapi\Models\ThinnedProvider; |
15 | use BO\Zmscitizenapi\Models\ThinnedScope; |
16 | use BO\Zmscitizenapi\Models\Collections\OfficeList; |
17 | use BO\Zmscitizenapi\Models\Collections\OfficeServiceRelationList; |
18 | use BO\Zmscitizenapi\Models\Collections\ServiceList; |
19 | use BO\Zmscitizenapi\Models\Collections\ThinnedScopeList; |
20 | use BO\Zmsentities\Appointment; |
21 | use BO\Zmsentities\Client; |
22 | use BO\Zmsentities\Contact; |
23 | use BO\Zmsentities\Process; |
24 | use BO\Zmsentities\Provider; |
25 | use BO\Zmsentities\Request; |
26 | use BO\Zmsentities\Scope; |
27 | use BO\Zmsentities\Collection\ProviderList; |
28 | use BO\Zmsentities\Collection\RequestList; |
29 | use BO\Zmsentities\Collection\RequestRelationList; |
30 | |
31 | /** |
32 | * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) |
33 | * @TODO: Extract class has ExcessiveClassComplexity 101 vs 100 |
34 | */ |
35 | class MapperService |
36 | { |
37 | public static function mapScopeForProvider(int $providerId, ?ThinnedScopeList $scopes): ThinnedScope |
38 | { |
39 | if (!$scopes) { |
40 | return new ThinnedScope(); |
41 | } |
42 | |
43 | $matchingScope = new ThinnedScope(); |
44 | foreach ($scopes->getScopes() as $scope) { |
45 | if ($scope->provider && $scope->provider->id === $providerId) { |
46 | $matchingScope = $scope; |
47 | break; |
48 | } |
49 | } |
50 | |
51 | return $matchingScope; |
52 | } |
53 | |
54 | /** |
55 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) |
56 | * @SuppressWarnings(PHPMD.NPathComplexity) |
57 | * @TODO: Extract mapping logic into specialized mapper classes for each entity type |
58 | */ |
59 | public static function mapOfficesWithScope(ProviderList $providerList, bool $showUnpublished = false): OfficeList |
60 | { |
61 | $offices = []; |
62 | $scopes = ZmsApiFacadeService::getScopes(); |
63 | if (!$scopes instanceof ThinnedScopeList) { |
64 | return new OfficeList(); |
65 | } |
66 | |
67 | foreach ($providerList as $provider) { |
68 | $providerScope = self::mapScopeForProvider((int) $provider->id, $scopes); |
69 | |
70 | if (!$showUnpublished && isset($provider->data['public']) && !(bool) $provider->data['public']) { |
71 | continue; |
72 | } |
73 | |
74 | $offices[] = new Office( |
75 | id: isset($provider->id) ? (int) $provider->id : 0, |
76 | name: isset($provider->displayName) ? $provider->displayName : (isset($provider->name) ? $provider->name : null), |
77 | address: isset($provider->data['address']) ? $provider->data['address'] : null, |
78 | showAlternativeLocations: isset($provider->data['showAlternativeLocations']) ? $provider->data['showAlternativeLocations'] : null, |
79 | displayNameAlternatives: $provider->data['displayNameAlternatives'] ?? [], |
80 | organization: $provider->data['organization'] ?? null, |
81 | organizationUnit: $provider->data['organizationUnit'] ?? null, |
82 | slotTimeInMinutes: $provider->data['slotTimeInMinutes'] ?? null, |
83 | geo: isset($provider->data['geo']) ? $provider->data['geo'] : null, |
84 | disabledByServices: isset($provider->data['dontShowByServices']) ? $provider->data['dontShowByServices'] : [], |
85 | scope: isset($providerScope) && !isset($providerScope['errors']) ? new ThinnedScope( |
86 | id: isset($providerScope->id) ? (int) $providerScope->id : 0, |
87 | provider: isset($providerScope->provider) ? $providerScope->provider : null, |
88 | shortName: isset($providerScope->shortName) ? $providerScope->shortName : null, |
89 | emailFrom: isset($providerScope->emailFrom) ? $providerScope->emailFrom : null, |
90 | emailRequired: isset($providerScope->emailRequired) ? (bool) $providerScope->emailRequired : null, |
91 | telephoneActivated: isset($providerScope->telephoneActivated) ? (bool) $providerScope->telephoneActivated : null, |
92 | telephoneRequired: isset($providerScope->telephoneRequired) ? (bool) $providerScope->telephoneRequired : null, |
93 | customTextfieldActivated: isset($providerScope->customTextfieldActivated) ? (bool) $providerScope->customTextfieldActivated : null, |
94 | customTextfieldRequired: isset($providerScope->customTextfieldRequired) ? (bool) $providerScope->customTextfieldRequired : null, |
95 | customTextfieldLabel: isset($providerScope->customTextfieldLabel) ? $providerScope->customTextfieldLabel : null, |
96 | captchaActivatedRequired: isset($providerScope->captchaActivatedRequired) ? (bool) $providerScope->captchaActivatedRequired : null, |
97 | displayInfo: isset($providerScope->displayInfo) ? $providerScope->displayInfo : null |
98 | ) : null |
99 | ); |
100 | } |
101 | |
102 | return new OfficeList($offices); |
103 | } |
104 | |
105 | public static function mapCombinable(array $serviceCombinations): ?Combinable |
106 | { |
107 | return !empty($serviceCombinations) ? new Combinable($serviceCombinations) : null; |
108 | } |
109 | |
110 | /** |
111 | * Map services with combinations based on request and relation lists. |
112 | * |
113 | * @param RequestList $requestList |
114 | * @param RequestRelationList $relationList |
115 | * @return ServiceList |
116 | */ |
117 | public static function mapServicesWithCombinations( |
118 | RequestList $requestList, |
119 | RequestRelationList $relationList, |
120 | bool $showUnpublished = false |
121 | ): ServiceList { |
122 | /** @var array<string, array<int>> $servicesProviderIds */ |
123 | $servicesProviderIds = []; |
124 | foreach ($relationList as $relation) { |
125 | if (!$showUnpublished && !$relation->isPublic()) { |
126 | continue; |
127 | } |
128 | |
129 | $serviceId = $relation->request->id; |
130 | $servicesProviderIds[$serviceId] ??= []; |
131 | $servicesProviderIds[$serviceId][] = $relation->provider->id; |
132 | } |
133 | |
134 | /** @var Service[] $services */ |
135 | $services = []; |
136 | $requestArray = iterator_to_array($requestList); |
137 | usort($requestArray, function ($a, $b) { |
138 | |
139 | return $a->getId() <=> $b->getId(); |
140 | // Sorting by service ID (ascending order) |
141 | }); |
142 | foreach ($requestArray as $service) { |
143 | if ( |
144 | !$showUnpublished |
145 | && isset($service->getAdditionalData()['public']) |
146 | && !$service->getAdditionalData()['public'] |
147 | ) { |
148 | continue; |
149 | } |
150 | |
151 | /** @var array<string, array<int>> $serviceCombinations */ |
152 | $serviceCombinations = []; |
153 | $combinableData = $service->getAdditionalData()['combinable'] ?? []; |
154 | foreach ($combinableData as $combinationServiceId) { |
155 | $commonProviders = array_intersect($servicesProviderIds[$service->getId()] ?? [], $servicesProviderIds[$combinationServiceId] ?? []); |
156 | $serviceCombinations[$combinationServiceId] = !empty($commonProviders) ? array_values($commonProviders) : []; |
157 | } |
158 | |
159 | $combinable = self::mapCombinable($serviceCombinations); |
160 | $services[] = new Service(id: (int) $service->getId(), name: $service->getName(), maxQuantity: $service->getAdditionalData()['maxQuantity'] ?? 1, combinable: $combinable ?? new Combinable()); |
161 | } |
162 | |
163 | return new ServiceList($services); |
164 | } |
165 | |
166 | |
167 | public static function mapRelations( |
168 | RequestRelationList $relationList, |
169 | bool $showUnpublished = false |
170 | ): OfficeServiceRelationList { |
171 | $relations = []; |
172 | foreach ($relationList as $relation) { |
173 | if (!$showUnpublished && !$relation->isPublic()) { |
174 | continue; |
175 | } |
176 | |
177 | $relations[] = new OfficeServiceRelation( |
178 | officeId: (int) $relation->provider->id, |
179 | serviceId: (int) $relation->request->id, |
180 | slots: intval($relation->slots), |
181 | public: (bool) $relation->isPublic(), |
182 | maxQuantity: (int) $relation->maxQuantity |
183 | ); |
184 | } |
185 | |
186 | return new OfficeServiceRelationList($relations); |
187 | } |
188 | |
189 | public static function scopeToThinnedScope(Scope $scope): ThinnedScope |
190 | { |
191 | if (!$scope || !isset($scope->id)) { |
192 | return new ThinnedScope(); |
193 | } |
194 | |
195 | $thinnedProvider = null; |
196 | try { |
197 | if (isset($scope->provider)) { |
198 | $provider = $scope->provider; |
199 | $contact = $provider->contact ?? null; |
200 | $thinnedProvider = new ThinnedProvider(id: isset($provider->id) ? (int) $provider->id : null, name: $provider->name ?? null, source: $provider->source ?? null, contact: $contact ? self::contactToThinnedContact($contact) : null); |
201 | } |
202 | } catch (\BO\Zmsentities\Exception\ScopeMissingProvider $e) { |
203 | $thinnedProvider = null; |
204 | } |
205 | |
206 | return new ThinnedScope( |
207 | id: (int) ($scope->id ?? 0), |
208 | provider: $thinnedProvider, |
209 | shortName: $scope->shortName ?? null, |
210 | emailFrom: (string) $scope->getEmailFrom() ?? null, |
211 | emailRequired: isset($scope->data['emailRequired']) ? (bool) $scope->data['emailRequired'] : null, |
212 | telephoneActivated: isset($scope->data['telephoneActivated']) ? (bool) $scope->data['telephoneActivated'] : null, |
213 | telephoneRequired: isset($scope->data['telephoneRequired']) ? (bool) $scope->data['telephoneRequired'] : null, |
214 | customTextfieldActivated: isset($scope->data['customTextfieldActivated']) ? (bool) $scope->data['customTextfieldActivated'] : null, |
215 | customTextfieldRequired: isset($scope->data['customTextfieldRequired']) ? (bool) $scope->data['customTextfieldRequired'] : null, |
216 | customTextfieldLabel: $scope->data['customTextfieldLabel'] ?? null, |
217 | captchaActivatedRequired: isset($scope->data['captchaActivatedRequired']) ? (bool) $scope->data['captchaActivatedRequired'] : null, |
218 | displayInfo: $scope->data['displayInfo'] ?? null |
219 | ); |
220 | } |
221 | |
222 | /** |
223 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) |
224 | * @SuppressWarnings(PHPMD.NPathComplexity) |
225 | * @TODO: Break down process mapping into smaller, focused methods |
226 | */ |
227 | public static function processToThinnedProcess(Process $myProcess): ThinnedProcess |
228 | { |
229 | if (!$myProcess || !isset($myProcess->id)) { |
230 | return new ThinnedProcess(); |
231 | } |
232 | |
233 | $subRequestCounts = []; |
234 | $mainServiceId = null; |
235 | $mainServiceCount = 0; |
236 | $requests = $myProcess->getRequests() ?? []; |
237 | if ($requests) { |
238 | $requests = is_array($requests) ? $requests : iterator_to_array($requests); |
239 | if (count($requests) > 0) { |
240 | $mainServiceId = $requests[0]->id; |
241 | foreach ($requests as $request) { |
242 | if ($request->id === $mainServiceId) { |
243 | $mainServiceCount++; |
244 | } else { |
245 | if (!isset($subRequestCounts[$request->id])) { |
246 | $subRequestCounts[$request->id] = [ |
247 | 'id' => (int) $request->id, |
248 | 'count' => 0, |
249 | ]; |
250 | } |
251 | $subRequestCounts[$request->id]['count']++; |
252 | } |
253 | } |
254 | } |
255 | } |
256 | |
257 | return new ThinnedProcess( |
258 | processId: isset($myProcess->id) ? (int) $myProcess->id : 0, |
259 | timestamp: (isset($myProcess->appointments[0]) && isset($myProcess->appointments[0]->date)) ? strval($myProcess->appointments[0]->date) : null, |
260 | authKey: isset($myProcess->authKey) ? $myProcess->authKey : null, |
261 | captchaToken: isset($myProcess->captchaToken) ? $myProcess->captchaToken : null, |
262 | familyName: (isset($myProcess->clients[0]) && isset($myProcess->clients[0]->familyName)) ? $myProcess->clients[0]->familyName : null, |
263 | customTextfield: isset($myProcess->customTextfield) ? $myProcess->customTextfield : null, |
264 | email: (isset($myProcess->clients[0]) && isset($myProcess->clients[0]->email)) ? $myProcess->clients[0]->email : null, |
265 | telephone: (isset($myProcess->clients[0]) && isset($myProcess->clients[0]->telephone)) ? $myProcess->clients[0]->telephone : null, |
266 | officeName: (isset($myProcess->scope->contact) && isset($myProcess->scope->contact->name)) ? $myProcess->scope->contact->name : null, |
267 | officeId: (isset($myProcess->scope->provider) && isset($myProcess->scope->provider->id)) ? (int) $myProcess->scope->provider->id : 0, |
268 | scope: isset($myProcess->scope) ? self::scopeToThinnedScope($myProcess->scope) : null, |
269 | subRequestCounts: isset($subRequestCounts) ? array_values($subRequestCounts) : [], |
270 | serviceId: isset($mainServiceId) ? (int) $mainServiceId : 0, |
271 | serviceCount: isset($mainServiceCount) ? $mainServiceCount : 0, |
272 | status: (isset($myProcess->queue) && isset($myProcess->queue->status)) ? $myProcess->queue->status : null |
273 | ); |
274 | } |
275 | |
276 | public static function thinnedProcessToProcess(ThinnedProcess $thinnedProcess): Process |
277 | { |
278 | if (!$thinnedProcess || !isset($thinnedProcess->processId)) { |
279 | return new Process(); |
280 | } |
281 | |
282 | $processEntity = new Process(); |
283 | $processEntity->id = $thinnedProcess->processId; |
284 | $processEntity->authKey = $thinnedProcess->authKey ?? null; |
285 | $processEntity->customTextfield = $thinnedProcess->customTextfield ?? null; |
286 | $processEntity->captchaToken = $thinnedProcess->captchaToken ?? null; |
287 | |
288 | $client = new Client(); |
289 | $client->familyName = $thinnedProcess->familyName ?? null; |
290 | $client->email = $thinnedProcess->email ?? null; |
291 | $client->telephone = $thinnedProcess->telephone ?? null; |
292 | $processEntity->clients = [$client]; |
293 | $appointment = new Appointment(); |
294 | $appointment->date = $thinnedProcess->timestamp ?? null; |
295 | $processEntity->appointments = [$appointment]; |
296 | // Set scope with all required fields |
297 | $scope = new Scope(); |
298 | if ($thinnedProcess->scope) { |
299 | $scope->id = $thinnedProcess->scope->id; |
300 | $scope->source = \App::$source_name; |
301 | |
302 | // Set preferences as array structure |
303 | $scope->preferences = [ |
304 | 'client' => [ |
305 | 'emailFrom' => $thinnedProcess->scope->getEmailFrom() ?? null, |
306 | 'emailRequired' => $thinnedProcess->scope->getEmailRequired() ?? false, |
307 | 'telephoneActivated' => $thinnedProcess->scope->getTelephoneActivated() ?? false, |
308 | 'telephoneRequired' => $thinnedProcess->scope->getTelephoneRequired() ?? false, |
309 | 'customTextfieldActivated' => $thinnedProcess->scope->getCustomTextfieldActivated() ?? false, |
310 | 'customTextfieldRequired' => $thinnedProcess->scope->getCaptchaActivatedRequired() ?? false, |
311 | 'customTextfieldLabel' => $thinnedProcess->scope->getCustomTextfieldLabel() ?? null |
312 | ], |
313 | 'notifications' => [ |
314 | 'enabled' => true |
315 | ] |
316 | ]; |
317 | } |
318 | if (isset($thinnedProcess->officeName)) { |
319 | $scope->contact = new Contact(); |
320 | $scope->contact->name = $thinnedProcess->officeName; |
321 | } |
322 | if (isset($thinnedProcess->officeId)) { |
323 | $scope->provider = new Provider(); |
324 | $scope->provider->id = $thinnedProcess->officeId; |
325 | $scope->provider->source = \App::$source_name; |
326 | } |
327 | $processEntity->scope = $scope; |
328 | if (isset($thinnedProcess->status)) { |
329 | $processEntity->queue = new \stdClass(); |
330 | $processEntity->queue->status = $thinnedProcess->status; |
331 | $processEntity->status = $thinnedProcess->status; |
332 | } |
333 | |
334 | $mainServiceId = $thinnedProcess->serviceId ?? null; |
335 | $mainServiceCount = $thinnedProcess->serviceCount ?? 0; |
336 | $subRequestCounts = $thinnedProcess->subRequestCounts ?? []; |
337 | $requests = []; |
338 | for ($i = 0; $i < $mainServiceCount; $i++) { |
339 | $request = new Request(); |
340 | $request->id = $mainServiceId; |
341 | $request->source = \App::$source_name; |
342 | $requests[] = $request; |
343 | } |
344 | foreach ($subRequestCounts as $subRequest) { |
345 | for ($i = 0; $i < ($subRequest['count'] ?? 0); $i++) { |
346 | $request = new Request(); |
347 | $request->id = $subRequest['id']; |
348 | $request->source = \App::$source_name; |
349 | $requests[] = $request; |
350 | } |
351 | } |
352 | $processEntity->requests = $requests; |
353 | $processEntity->lastChange = time(); |
354 | $processEntity->createIP = ClientIpHelper::getClientIp(); |
355 | $processEntity->createTimestamp = time(); |
356 | return $processEntity; |
357 | } |
358 | |
359 | /** |
360 | * Converts a raw or existing contact object/array into a ThinnedContact model. |
361 | * |
362 | * @param object|array $contact |
363 | * @return ThinnedContact |
364 | */ |
365 | public static function contactToThinnedContact($contact): ThinnedContact |
366 | { |
367 | if (is_array($contact)) { |
368 | return new ThinnedContact(city: $contact['city'] ?? null, country: $contact['country'] ?? null, name: $contact['name'] ?? null, postalCode: $contact['postalCode'] ?? null, region: $contact['region'] ?? null, street: $contact['street'] ?? null, streetNumber: $contact['streetNumber'] ?? null); |
369 | } |
370 | |
371 | return new ThinnedContact(city: $contact->city ?? null, country: $contact->country ?? null, name: $contact->name ?? null, postalCode: $contact->postalCode ?? null, region: $contact->region ?? null, street: $contact->street ?? null, streetNumber: $contact->streetNumber ?? null); |
372 | } |
373 | |
374 | /** |
375 | * Convert a Provider object to a ThinnedProvider. |
376 | * |
377 | * @param Provider $provider |
378 | * @return ThinnedProvider |
379 | */ |
380 | public static function providerToThinnedProvider(Provider $provider): ThinnedProvider |
381 | { |
382 | return new ThinnedProvider(id: isset($provider->id) ? (int) $provider->id : null, name: isset($provider->name) ? $provider->name : null, source: isset($provider->source) ? $provider->source : null, lon: isset($provider->data['geo']['lon']) ? (float) $provider->data['geo']['lon'] : null, lat: isset($provider->data['geo']['lat']) ? (float) $provider->data['geo']['lat'] : null, contact: isset($provider->contact) ? self::contactToThinnedContact($provider->contact) : null); |
383 | } |
384 | } |