Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.62% covered (success)
90.62%
348 / 384
31.25% covered (danger)
31.25%
5 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
MapperService
90.62% covered (success)
90.62%
348 / 384
31.25% covered (danger)
31.25%
5 / 16
218.43
0.00% covered (danger)
0.00%
0 / 1
 resolveAllowDisabledServicesMix
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 mapScopeForProvider
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
10.24
 extractReservationDuration
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
5.07
 extractActivationDuration
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
7.29
 mapOfficesWithScope
91.67% covered (success)
91.67%
55 / 60
0.00% covered (danger)
0.00%
0 / 1
50.39
 mapCombinable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 mapServicesWithCombinations
97.37% covered (success)
97.37%
37 / 38
0.00% covered (danger)
0.00%
0 / 1
15
 mapRelations
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
4.01
 scopeToThinnedScope
94.20% covered (success)
94.20%
65 / 69
0.00% covered (danger)
0.00%
0 / 1
24.11
 processToThinnedProcess
81.82% covered (warning)
81.82%
36 / 44
0.00% covered (danger)
0.00%
0 / 1
42.36
 thinnedProcessToProcess
93.75% covered (success)
93.75%
30 / 32
0.00% covered (danger)
0.00%
0 / 1
7.01
 createScope
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
1 / 1
6
 createRequests
59.09% covered (warning)
59.09%
13 / 22
0.00% covered (danger)
0.00%
0 / 1
5.10
 contactToThinnedContact
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
6
 providerToThinnedProvider
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
8
 generateIcsContent
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace BO\Zmscitizenapi\Services\Core;
6
7use BO\Zmsentities\Helper\ProcessPlainText;
8use BO\Zmscitizenapi\Models\Office;
9use BO\Zmscitizenapi\Models\Combinable;
10use BO\Zmscitizenapi\Models\OfficeServiceRelation;
11use BO\Zmscitizenapi\Models\Service;
12use BO\Zmscitizenapi\Models\ThinnedContact;
13use BO\Zmscitizenapi\Models\ThinnedProcess;
14use BO\Zmscitizenapi\Models\ThinnedProvider;
15use BO\Zmscitizenapi\Models\ThinnedScope;
16use BO\Zmscitizenapi\Models\Collections\OfficeList;
17use BO\Zmscitizenapi\Models\Collections\OfficeServiceRelationList;
18use BO\Zmscitizenapi\Models\Collections\ServiceList;
19use BO\Zmscitizenapi\Models\Collections\ThinnedScopeList;
20use BO\Zmscitizenapi\Utils\ClientIpHelper;
21use BO\Zmsentities\Appointment;
22use BO\Zmsentities\Client;
23use BO\Zmsentities\Contact;
24use BO\Zmsentities\Process;
25use BO\Zmsentities\Provider;
26use BO\Zmsentities\Queue;
27use BO\Zmsentities\Request;
28use BO\Zmsentities\Scope;
29use BO\Zmsentities\Collection\ProviderList;
30use BO\Zmsentities\Collection\RequestList;
31use BO\Zmsentities\Collection\RequestRelationList;
32
33/**
34 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
35 * @TODO: Extract class has ExcessiveClassComplexity 101 vs 100
36 */
37class MapperService
38{
39    private static function resolveAllowDisabledServicesMix(Provider $provider): ?array
40    {
41        if (!isset($provider->data['allowDisabledServicesMix']) || !is_array($provider->data['allowDisabledServicesMix'])) {
42            return null;
43        }
44        return array_map('intval', $provider->data['allowDisabledServicesMix']);
45    }
46
47    public static function mapScopeForProvider(
48        int $providerId,
49        ThinnedScopeList $scopes,
50        ?string $providerSource = null
51    ): ?ThinnedScope {
52        foreach ($scopes->getScopes() as $scope) {
53            if (!$scope instanceof ThinnedScope) {
54                continue;
55            }
56
57            $prov = $scope->provider ?? null;
58            if (!$prov) {
59                continue;
60            }
61
62            $provId  = is_object($prov) ? ($prov->id   ?? null) : ($prov['id']    ?? null);
63            $provSrc = is_object($prov) ? ($prov->source ?? null) : ($prov['source'] ?? null);
64
65            if ((string)$provId !== (string)$providerId) {
66                continue;
67            }
68
69            if ($providerSource === null || $providerSource === '') {
70                return $scope;
71            }
72
73            if ((string)$provSrc === (string)$providerSource) {
74                return $scope;
75            }
76        }
77
78        return null;
79    }
80
81    public static function extractReservationDuration(Scope|ThinnedScope|null $scope): ?int
82    {
83        if ($scope === null) {
84            return null;
85        }
86        if ($scope instanceof ThinnedScope) {
87            $reservationDuration = $scope->getReservationDuration();
88            return $reservationDuration !== null ? (int) $reservationDuration : null;
89        }
90        $reservationDuration = $scope->toProperty()?->preferences?->appointment?->reservationDuration?->get();
91        return $reservationDuration !== null ? (int) $reservationDuration : null;
92    }
93
94    public static function extractActivationDuration(Scope|ThinnedScope|null $scope): ?int
95    {
96        if ($scope === null) {
97            return null;
98        }
99
100        if ($scope instanceof ThinnedScope) {
101            $activationDuration = $scope->getActivationDuration();
102            if ($activationDuration === null || $activationDuration === '') {
103                return null;
104            }
105            return (int) $activationDuration;
106        }
107
108        $activationDuration = $scope->toProperty()?->preferences?->appointment?->activationDuration?->get();
109        if ($activationDuration === null || $activationDuration === '') {
110            return null;
111        }
112        return (int) $activationDuration;
113    }
114
115    /**
116     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
117     * @SuppressWarnings(PHPMD.NPathComplexity)
118     * @TODO: Extract mapping logic into specialized mapper classes for each entity type
119     *
120     */
121    public static function mapOfficesWithScope(ProviderList $providerList, bool $showUnpublished = false): OfficeList
122    {
123        $offices = [];
124        $scopes = ZmsApiFacadeService::getScopes();
125        if (!$scopes instanceof ThinnedScopeList) {
126            return new OfficeList();
127        }
128
129        foreach ($providerList as $provider) {
130            // âœ… Source normalisieren: leerer String -> Fallback auf App::$source_name
131            $providerSource = isset($provider->source) && $provider->source !== ''
132                ? (string)$provider->source
133                : (string)\App::$source_name;
134
135            $providerScope = self::mapScopeForProvider(
136                (int) $provider->id,
137                $scopes,
138                $providerSource
139            );
140
141            if (!$showUnpublished && isset($provider->data['public']) && !(bool) $provider->data['public']) {
142                continue;
143            }
144
145            $offices[] = new Office(
146                id: isset($provider->id) ? (int) $provider->id : 0,
147                name: isset($provider->displayName) ? $provider->displayName : (isset($provider->name) ? $provider->name : null),
148                address: isset($provider->data['address']) ? $provider->data['address'] : null,
149                showAlternativeLocations: isset($provider->data['showAlternativeLocations']) ? $provider->data['showAlternativeLocations'] : null,
150                displayNameAlternatives: $provider->data['displayNameAlternatives'] ?? [],
151                organization: $provider->data['organization'] ?? null,
152                organizationUnit: $provider->data['organizationUnit'] ?? null,
153                slotTimeInMinutes: $provider->data['slotTimeInMinutes'] ?? null,
154                geo: isset($provider->data['geo']) ? $provider->data['geo'] : null,
155                disabledByServices: isset($provider->data['dontShowByServices']) ? $provider->data['dontShowByServices'] : [],
156                priority: isset($provider->data['prio']) ? $provider->data['prio'] : 1,
157                scope: isset($providerScope) && !isset($providerScope['errors']) ? new ThinnedScope(
158                    id: isset($providerScope->id) ? (int) $providerScope->id : 0,
159                    provider: isset($providerScope->provider) ? $providerScope->provider : null,
160                    shortName: isset($providerScope->shortName) ? (string) $providerScope->shortName : null,
161                    emailFrom: isset($providerScope->emailFrom) ? (string) $providerScope->emailFrom : null,
162                    emailRequired: isset($providerScope->emailRequired) ? (bool) $providerScope->emailRequired : null,
163                    telephoneActivated: isset($providerScope->telephoneActivated) ? (bool) $providerScope->telephoneActivated : null,
164                    telephoneRequired: isset($providerScope->telephoneRequired) ? (bool) $providerScope->telephoneRequired : null,
165                    customTextfieldActivated: isset($providerScope->customTextfieldActivated) ? (bool) $providerScope->customTextfieldActivated : null,
166                    customTextfieldRequired: isset($providerScope->customTextfieldRequired) ? (bool) $providerScope->customTextfieldRequired : null,
167                    customTextfieldLabel: isset($providerScope->customTextfieldLabel) ? (string) $providerScope->customTextfieldLabel : null,
168                    customTextfield2Activated: isset($providerScope->customTextfield2Activated) ? (bool) $providerScope->customTextfield2Activated : null,
169                    customTextfield2Required: isset($providerScope->customTextfield2Required) ? (bool) $providerScope->customTextfield2Required : null,
170                    customTextfield2Label: isset($providerScope->customTextfield2Label) ? (string) $providerScope->customTextfield2Label : null,
171                    captchaActivatedRequired: isset($providerScope->captchaActivatedRequired) ? (bool) $providerScope->captchaActivatedRequired : null,
172                    infoForAppointment: isset($providerScope->infoForAppointment)
173                        ? ((string) $providerScope->infoForAppointment === '' ? null : (string) $providerScope->infoForAppointment)
174                        : null,
175                    infoForAllAppointments: isset($providerScope->infoForAllAppointments)
176                        ? ((string) $providerScope->infoForAllAppointments === '' ? null : (string) $providerScope->infoForAllAppointments)
177                        : null,
178                    appointmentsPerMail: isset($providerScope->appointmentsPerMail) ? ((string) $providerScope->appointmentsPerMail === '' ? null : (string) $providerScope->appointmentsPerMail) : null,
179                    slotsPerAppointment: isset($providerScope->slotsPerAppointment) ? ((string) $providerScope->slotsPerAppointment === '' ? null : (string) $providerScope->slotsPerAppointment) : null,
180                    whitelistedMails: isset($providerScope->whitelistedMails) ? ((string) $providerScope->whitelistedMails === '' ? null : (string) $providerScope->whitelistedMails) : null,
181                    reservationDuration: (int) self::extractReservationDuration($providerScope),
182                    activationDuration: self::extractActivationDuration($providerScope),
183                    hint: isset($providerScope->hint) ? (trim((string) $providerScope->hint) === '' ? null : (string) $providerScope->hint) : null
184                ) : null,
185                slotsPerAppointment: isset($providerScope) && !isset($providerScope['errors']) && isset($providerScope->slotsPerAppointment) ? ((string) $providerScope->slotsPerAppointment === '' ? null : (string) $providerScope->slotsPerAppointment) : null,
186                parentId: isset($provider->parent_id) ? (int) $provider->parent_id : null,
187                allowDisabledServicesMix: self::resolveAllowDisabledServicesMix($provider)
188            );
189        }
190
191        return new OfficeList($offices);
192    }
193
194    public static function mapCombinable(array $serviceCombinations): ?Combinable
195    {
196        return !empty($serviceCombinations) ? new Combinable($serviceCombinations) : null;
197    }
198
199    /**
200     * Map services with combinations based on request and relation lists.
201     *
202     * @SuppressWarnings(PHPMD.NPathComplexity)
203     */
204    public static function mapServicesWithCombinations(
205        RequestList $requestList,
206        RequestRelationList $relationList,
207        bool $showUnpublished = false
208    ): ServiceList {
209        /** @var array<string, array<int>> $servicesProviderIds */
210        $servicesProviderIds = [];
211        foreach ($relationList as $relation) {
212            if (!$showUnpublished && !$relation->isPublic()) {
213                continue;
214            }
215
216            $serviceId = $relation->request->id;
217            $servicesProviderIds[$serviceId] ??= [];
218            $servicesProviderIds[$serviceId][] = $relation->provider->id;
219        }
220
221        $services = [];
222        $requestArray = iterator_to_array($requestList);
223        usort($requestArray, function ($a, $b) {
224
225            return $a->getId() <=> $b->getId();
226            // Sorting by service ID (ascending order)
227        });
228        foreach ($requestArray as $service) {
229            if (
230                !$showUnpublished
231                && isset($service->getAdditionalData()['public'])
232                && !$service->getAdditionalData()['public']
233            ) {
234                continue;
235            }
236
237            /** @var array<string, array<int>> $serviceCombinations */
238            $serviceCombinations = [];
239            $combinableData = $service->getAdditionalData()['combinable'] ?? [];
240            foreach ($combinableData as $combinationServiceId) {
241                $commonProviders = array_intersect($servicesProviderIds[$service->getId()] ?? [], $servicesProviderIds[$combinationServiceId] ?? []);
242                $serviceCombinations[$combinationServiceId] = !empty($commonProviders) ? array_values($commonProviders) : [];
243            }
244
245            $combinable = self::mapCombinable($serviceCombinations);
246
247            $extra = $service->getAdditionalData() ?? [];
248            $parentId  = isset($service->parent_id)  ? (int)$service->parent_id  : (isset($extra['parent_id'])  ? (int)$extra['parent_id']  : null);
249            $variantId = isset($service->variant_id) ? (int)$service->variant_id : (isset($extra['variant_id']) ? (int)$extra['variant_id'] : null);
250
251            if (!empty($servicesProviderIds[$service->getId()])) {
252                $services[] = new Service(
253                    id: (int) $service->getId(),
254                    name: $service->getName(),
255                    maxQuantity: $service->getAdditionalData()['maxQuantity'] ?? 1,
256                    combinable: $combinable ?? new Combinable(),
257                    parentId: $parentId,
258                    variantId: $variantId,
259                    showOnStartPage: $service->getAdditionalData()['showOnStartPage'] ?? true,
260                    rootParentId: (int) $service->getRootParentId(),
261                );
262            }
263        }
264
265        return new ServiceList($services);
266    }
267
268
269    public static function mapRelations(
270        RequestRelationList $relationList,
271        bool $showUnpublished = false
272    ): OfficeServiceRelationList {
273        $relations = [];
274        foreach ($relationList as $relation) {
275            if (!$showUnpublished && !$relation->isPublic()) {
276                continue;
277            }
278
279            $relations[] = new OfficeServiceRelation(
280                officeId: (int) $relation->provider->id,
281                serviceId: (int) $relation->request->id,
282                slots: intval($relation->slots),
283                public: (bool) $relation->isPublic(),
284                maxQuantity: (int) $relation->maxQuantity
285            );
286        }
287
288        return new OfficeServiceRelationList($relations);
289    }
290
291    /**
292     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
293     * @SuppressWarnings(PHPMD.NPathComplexity)
294     */
295    public static function scopeToThinnedScope(Scope $scope): ThinnedScope
296    {
297        if (!$scope || !isset($scope->id)) {
298            return new ThinnedScope();
299        }
300
301        $thinnedProvider = null;
302        try {
303            if (isset($scope->provider)) {
304                $provider = $scope->provider;
305                $contact = $provider->contact ?? null;
306                $thinnedProvider = new ThinnedProvider(
307                    id: isset($provider->id) ? (int)$provider->id : null,
308                    name: $provider->name ?? null,
309                    displayName: $provider->displayName ?? null,
310                    source: $provider->source ?? null,
311                    contact: $contact ? self::contactToThinnedContact($contact) : null
312                );
313            }
314        } catch (\BO\Zmsentities\Exception\ScopeMissingProvider $e) {
315            $thinnedProvider = null;
316        }
317
318        return new ThinnedScope(
319            id: (int) ($scope->id ?? 0),
320            provider: $thinnedProvider,
321            shortName: isset($scope->shortName) ? (string) $scope->shortName : null,
322            emailFrom: (string) $scope->getEmailFrom(),
323            emailRequired: $scope->getEmailRequired() === null
324                ? null
325                : (bool) $scope->getEmailRequired(),
326            telephoneActivated: $scope->getTelephoneActivated() === null
327                ? null
328                : (bool) $scope->getTelephoneActivated(),
329            telephoneRequired: $scope->getTelephoneRequired() === null
330                ? null
331                : (bool) $scope->getTelephoneRequired(),
332            customTextfieldActivated: $scope->getCustomTextfieldActivated() === null
333                ? null
334                : (bool) $scope->getCustomTextfieldActivated(),
335            customTextfieldRequired: $scope->getCustomTextfieldRequired() === null
336                ? null
337                : (bool) $scope->getCustomTextfieldRequired(),
338            customTextfieldLabel: $scope->getCustomTextfieldLabel() === null
339                ? null
340                : (string) $scope->getCustomTextfieldLabel(),
341            customTextfield2Activated: $scope->getCustomTextfield2Activated() === null
342                ? null
343                : (bool) $scope->getCustomTextfield2Activated(),
344            customTextfield2Required: $scope->getCustomTextfield2Required() === null
345                ? null
346                : (bool) $scope->getCustomTextfield2Required(),
347            customTextfield2Label: $scope->getCustomTextfield2Label() === null
348                ? null
349                : (string) $scope->getCustomTextfield2Label(),
350            captchaActivatedRequired: $scope->getCaptchaActivatedRequired() === null
351                ? null
352                : (bool) $scope->getCaptchaActivatedRequired(),
353            infoForAppointment: $scope->getInfoForAppointment() === null
354                ? null
355                : (string) $scope->getInfoForAppointment(),
356            infoForAllAppointments: $scope->getInfoForAllAppointments() === null
357                ? null
358                : (string) $scope->getInfoForAllAppointments(),
359            slotsPerAppointment: $scope->getSlotsPerAppointment() === null
360                ? null
361                : (string) $scope->getSlotsPerAppointment(),
362            appointmentsPerMail: $scope->getAppointmentsPerMail() !== null
363                ? (string) $scope->getAppointmentsPerMail()
364                : null,
365            whitelistedMails: $scope->getWhitelistedMails() === null
366                ? null
367                : (string) $scope->getWhitelistedMails(),
368            reservationDuration: MapperService::extractReservationDuration($scope),
369            activationDuration: MapperService::extractActivationDuration($scope),
370            hint: (trim((string) ($scope->getScopeHint() ?? '')) === '') ? null : (string) $scope->getScopeHint()
371        );
372    }
373
374    /**
375     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
376     * @SuppressWarnings(PHPMD.NPathComplexity)
377     * @TODO: Break down process mapping into smaller, focused methods
378     */
379    public static function processToThinnedProcess(Process $myProcess): ThinnedProcess
380    {
381        if (!$myProcess || !isset($myProcess->id)) {
382            return new ThinnedProcess();
383        }
384
385        $subRequestCounts = [];
386        $mainServiceId = null;
387        $mainServiceName = null;
388        $mainServiceCount = 0;
389        $requests = iterator_to_array($myProcess->getRequests());
390        if (count($requests) > 0) {
391            $mainServiceId = $requests[0]->id;
392            foreach ($requests as $request) {
393                if ($request->id === $mainServiceId) {
394                    $mainServiceCount++;
395                    if (!$mainServiceName && isset($request->name)) {
396                        $mainServiceName = $request->name;
397                    }
398                } else {
399                    if (!isset($subRequestCounts[$request->id])) {
400                        $subRequestCounts[$request->id] = [
401                            'id' => (int) $request->id,
402                            'name'  => $request->name,
403                            'count' => 0,
404                        ];
405                    }
406                    $subRequestCounts[$request->id]['count']++;
407                }
408            }
409        }
410
411        // Generate ICS content if process has appointments with time
412        $icsContent = self::generateIcsContent($myProcess);
413
414        return new ThinnedProcess(
415            processId: isset($myProcess->id) ? (int) $myProcess->id : 0,
416            timestamp: (isset($myProcess->appointments[0]) && isset($myProcess->appointments[0]->date)) ? strval($myProcess->appointments[0]->date) : null,
417            authKey: isset($myProcess->authKey) ? $myProcess->authKey : null,
418            captchaToken: isset($myProcess->captchaToken) ? $myProcess->captchaToken : null,
419            familyName: (isset($myProcess->clients[0]) && isset($myProcess->clients[0]->familyName)) ? $myProcess->clients[0]->familyName : null,
420            customTextfield: isset($myProcess->customTextfield) ? $myProcess->customTextfield : null,
421            customTextfield2: isset($myProcess->customTextfield2) ? $myProcess->customTextfield2 : null,
422            email: (isset($myProcess->clients[0]) && isset($myProcess->clients[0]->email)) ? $myProcess->clients[0]->email : null,
423            telephone: (isset($myProcess->clients[0]) && isset($myProcess->clients[0]->telephone)) ? $myProcess->clients[0]->telephone : null,
424            officeName: (isset($myProcess->scope->contact) && isset($myProcess->scope->contact->name)) ? $myProcess->scope->contact->name : null,
425            officeId: (isset($myProcess->scope->provider) && isset($myProcess->scope->provider->id)) ? (int) $myProcess->scope->provider->id : 0,
426            scope: isset($myProcess->scope) ? self::scopeToThinnedScope($myProcess->scope) : null,
427            subRequestCounts: array_values($subRequestCounts),
428            serviceId: isset($mainServiceId) ? (int) $mainServiceId : 0,
429            serviceName: isset($mainServiceName) ? $mainServiceName : null,
430            serviceCount: $mainServiceCount,
431            status: (isset($myProcess->queue) && isset($myProcess->queue->status)) ? $myProcess->queue->status : null,
432            slotCount: (isset($myProcess->appointments[0]) && isset($myProcess->appointments[0]->slotCount)) ? (int) $myProcess->appointments[0]->slotCount : null,
433            displayNumber: ($myProcess->getDisplayNumber() ?: null),
434            icsContent: isset($icsContent) ? $icsContent : null
435        );
436    }
437
438    public static function thinnedProcessToProcess(ThinnedProcess $thinnedProcess): Process
439    {
440        if (!$thinnedProcess || !isset($thinnedProcess->processId)) {
441            return new Process();
442        }
443
444        $processEntity = new Process();
445        $processEntity->id = $thinnedProcess->processId;
446        $processEntity->authKey = $thinnedProcess->authKey ?? null;
447        $customTextfield = $thinnedProcess->customTextfield ?? null;
448        $processEntity->customTextfield = $customTextfield === null ? null : ProcessPlainText::normalize($customTextfield);
449        $customTextfield2 = $thinnedProcess->customTextfield2 ?? null;
450        $processEntity->customTextfield2 = $customTextfield2 === null
451            ? null
452            : ProcessPlainText::normalize($customTextfield2);
453        $processEntity->captchaToken = $thinnedProcess->captchaToken ?? null;
454
455        $client = new Client();
456        $client->familyName = $thinnedProcess->familyName ?? null;
457        $client->email = $thinnedProcess->email ?? null;
458        $client->telephone = $thinnedProcess->telephone ?? null;
459        $processEntity->clients = [$client];
460
461        $appointment = new Appointment();
462        $appointment->slotCount = $thinnedProcess->slotCount ?? null;
463        $appointment->date = $thinnedProcess->timestamp ?? null;
464        $processEntity->appointments = [$appointment];
465        $processEntity->scope = self::createScope($thinnedProcess);
466        $processEntity->requests = self::createRequests($thinnedProcess);
467
468        if (isset($thinnedProcess->status)) {
469            $processEntity->queue = new Queue(['status' => $thinnedProcess->status]);
470            $processEntity->status = $thinnedProcess->status;
471        }
472
473        if (isset($thinnedProcess->displayNumber)) {
474            $processEntity->displayNumber = $thinnedProcess->displayNumber;
475        }
476
477        $processEntity->lastChange = time();
478        $processEntity->createIP = ClientIpHelper::getClientIp();
479        $processEntity->createTimestamp = time();
480        return $processEntity;
481    }
482
483    private static function createScope(ThinnedProcess $thinnedProcess): Scope
484    {
485        $scope = new Scope();
486        if ($thinnedProcess->scope) {
487            $providerSource = $thinnedProcess->scope->provider->source ?? 'dldb';
488
489            $scope->id = $thinnedProcess->scope->id;
490            $scope->source = $providerSource;
491
492            $scope->preferences = [
493                'client' => [
494                    'appointmentsPerMail' => $thinnedProcess->scope->getAppointmentsPerMail() ?? null,
495                    'slotsPerAppointment' => $thinnedProcess->scope->getSlotsPerAppointment() ?? null,
496                    "whitelistedMails" => $thinnedProcess->scope->getWhitelistedMails() ?? null,
497                    'emailFrom' => $thinnedProcess->scope->getEmailFrom() ?? null,
498                    'emailRequired' => $thinnedProcess->scope->getEmailRequired() ?? false,
499                    'telephoneActivated' => $thinnedProcess->scope->getTelephoneActivated() ?? false,
500                    'telephoneRequired' => $thinnedProcess->scope->getTelephoneRequired() ?? false,
501                    'customTextfieldActivated' => $thinnedProcess->scope->getCustomTextfieldActivated() ?? false,
502                    'customTextfieldRequired' => $thinnedProcess->scope->getCustomTextfieldRequired() ?? false,
503                    'customTextfieldLabel' => $thinnedProcess->scope->getCustomTextfieldLabel() ?? null,
504                    'customTextfield2Activated' => $thinnedProcess->scope->getCustomTextfield2Activated() ?? false,
505                    'customTextfield2Required' => $thinnedProcess->scope->getCustomTextfield2Required() ?? false,
506                    'customTextfield2Label' => $thinnedProcess->scope->getCustomTextfield2Label() ?? null
507                ]
508            ];
509        }
510        if (isset($thinnedProcess->officeName)) {
511            $scope->contact = new Contact();
512            $scope->contact->name = $thinnedProcess->officeName;
513        }
514        if (isset($thinnedProcess->officeId)) {
515            $scope->provider = new Provider();
516            $scope->provider->id = $thinnedProcess->officeId;
517            if (isset($thinnedProcess->scope->provider)) {
518                $provider = $thinnedProcess->scope->provider;
519                $scope->provider->name  = $provider->name ?? null;
520                $scope->provider->displayName = $provider->displayName ?? null;
521
522                if (isset($provider->contact)) {
523                    $scope->provider->contact = new Contact();
524                    $scope->provider->contact->street = $provider->contact->street ?? null;
525                    $scope->provider->contact->streetNumber = $provider->contact->streetNumber ?? null;
526                }
527            }
528
529            $scope->provider->source = $thinnedProcess->scope->provider->source ?? null;
530        }
531
532        return $scope;
533    }
534
535    private static function createRequests(ThinnedProcess $thinnedProcess): array
536    {
537        $providerSource = $thinnedProcess->scope->provider->source ?? 'dldb';
538
539        $requests = [];
540        $mainServiceId = $thinnedProcess->serviceId ?? null;
541        $mainServiceName = $thinnedProcess->serviceName ?? null;
542        $mainServiceCount = $thinnedProcess->serviceCount ?? 0;
543
544        for ($i = 0; $i < $mainServiceCount; $i++) {
545            $request = new Request();
546            $request->id = $mainServiceId;
547            $request->name = $mainServiceName;
548            $request->source = $providerSource;
549            $requests[] = $request;
550        }
551
552        foreach ($thinnedProcess->subRequestCounts ?? [] as $subRequest) {
553            $subId = $subRequest['id'] ?? null;
554            $subName = $subRequest['name'] ?? null;
555            $count = (int)($subRequest['count'] ?? 0);
556
557            for ($i = 0; $i < $count; $i++) {
558                $request = new Request();
559                $request->id = $subId;
560                $request->name = $subName;
561                $request->source = $providerSource;
562                $requests[] = $request;
563            }
564        }
565
566        return $requests;
567    }
568
569    public static function contactToThinnedContact($contact): ThinnedContact
570    {
571        if (is_array($contact)) {
572            return new ThinnedContact(
573                city: $contact['city'] ?? null,
574                country: $contact['country'] ?? null,
575                name: $contact['name'] ?? null,
576                postalCode: isset($contact['postalCode']) ? (is_null($contact['postalCode']) ? null : (string)$contact['postalCode']) : null,
577                region: $contact['region'] ?? null,
578                street: $contact['street'] ?? null,
579                streetNumber: $contact['streetNumber'] ?? null
580            );
581        }
582
583        return new ThinnedContact(
584            city: $contact->city ?? null,
585            country: $contact->country ?? null,
586            name: $contact->name ?? null,
587            postalCode: isset($contact->postalCode) ? (is_null($contact->postalCode) ? null : (string)$contact->postalCode) : null,
588            region: $contact->region ?? null,
589            street: $contact->street ?? null,
590            streetNumber: $contact->streetNumber ?? null
591        );
592    }
593
594    public static function providerToThinnedProvider(Provider $provider): ThinnedProvider
595    {
596        return new ThinnedProvider(
597            id: isset($provider->id) ? (int) $provider->id : null,
598            name: isset($provider->name) ? $provider->name : null,
599            displayName: isset($provider->displayName) ? $provider->displayName : null,
600            source: isset($provider->source) ? $provider->source : null,
601            lon: isset($provider->data['geo']['lon']) ? (float) $provider->data['geo']['lon'] : null,
602            lat: isset($provider->data['geo']['lat']) ? (float) $provider->data['geo']['lat'] : null,
603            contact: isset($provider->contact) ? self::contactToThinnedContact($provider->contact) : null
604        );
605    }
606
607    private static function generateIcsContent(Process $process): ?string
608    {
609        if (!isset($process->appointments[0]) || !$process->appointments[0]->hasTime()) {
610            return null;
611        }
612
613        $content = ZmsApiClientService::getIcsContent((int) $process->getId(), (string) $process->getAuthKey());
614        return $content ?: null;
615    }
616}