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