Lines 85.80% 393 / 458
Methods 70.09% 75 / 107
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 getDefaults 100.00% 26 / 26 100.00% 1 / 1 1
 createFromScope 100.00% 9 / 9 100.00% 1 / 1 1
 getRequests 100.00% 7 / 7 100.00% 1 / 1 4
 getRequestIds 100.00% 1 / 1 100.00% 1 / 1 1
 getDisplayNumber 100.00% 1 / 1 100.00% 1 / 1 1
 getRequestCSV 100.00% 1 / 1 100.00% 1 / 1 1
 addScope 100.00% 2 / 2 100.00% 1 / 1 1
 addQueue 100.00% 6 / 6 100.00% 1 / 1 1
 addRequests 100.00% 8 / 8 100.00% 1 / 1 3
 updateRequests 100.00% 8 / 8 100.00% 1 / 1 3
 hasScopeAdmin 100.00% 1 / 1 100.00% 1 / 1 1
 sendAdminMailOnConfirmation 0.00% 0 / 1 0.00% 0 / 1 2
 sendAdminMailOnDeleted 0.00% 0 / 1 0.00% 0 / 1 2
 sendAdminMailOnUpdated 0.00% 0 / 1 0.00% 0 / 1 2
 shouldSendAdminMailOnClerkMail 0.00% 0 / 1 0.00% 0 / 1 2
 withUpdatedData 100.00% 11 / 11 100.00% 1 / 1 3
 addAppointmentFromRequest 100.00% 14 / 14 100.00% 1 / 1 4
 addClientFromForm 100.00% 7 / 7 100.00% 1 / 1 5
 setStatus 100.00% 2 / 2 100.00% 1 / 1 1
 getStatus 100.00% 1 / 1 100.00% 1 / 1 1
 getReminderTimestamp 100.00% 2 / 2 100.00% 1 / 1 2
 addReminderTimestamp 100.00% 5 / 5 100.00% 1 / 1 3
 getAppointments 100.00% 8 / 8 100.00% 1 / 1 4
 getClients 87.50% 7 / 8 0.00% 0 / 1 4.03
 hasAppointment 100.00% 1 / 1 100.00% 1 / 1 1
 isWithAppointment 100.00% 4 / 4 100.00% 1 / 1 2
 hasProcessCredentials 100.00% 1 / 1 100.00% 1 / 1 4
 withReassignedCredentials 100.00% 3 / 3 100.00% 1 / 1 1
 hasQueueNumber 100.00% 1 / 1 100.00% 1 / 1 3
 getQueueNumber 0.00% 0 / 1 0.00% 0 / 1 2
 addAppointment 100.00% 2 / 2 100.00% 1 / 1 1
 getScopeId 100.00% 1 / 1 100.00% 1 / 1 1
 isDereferenced 0.00% 0 / 4 0.00% 0 / 1 12
 getCurrentScope 100.00% 1 / 1 100.00% 1 / 1 1
 getAmendment 100.00% 1 / 1 100.00% 1 / 1 1
 getShowUpTime 0.00% 0 / 6 0.00% 0 / 1 6
 getWaitingTime 0.00% 0 / 1 0.00% 0 / 1 2
 getProcessingTime 0.00% 0 / 1 0.00% 0 / 1 2
 getFinishTime 0.00% 0 / 1 0.00% 0 / 1 2
 addAmendment 100.00% 4 / 4 100.00% 1 / 1 3
 getCustomTextfield 0.00% 0 / 1 0.00% 0 / 1 2
 addCustomTextfield 100.00% 5 / 5 100.00% 1 / 1 3
 getCustomTextfield2 0.00% 0 / 1 0.00% 0 / 1 2
 addCustomTextfield2 100.00% 5 / 5 100.00% 1 / 1 3
 addPriority 100.00% 2 / 2 100.00% 1 / 1 3
 getAuthKey 100.00% 1 / 1 100.00% 1 / 1 1
 getPriority 100.00% 1 / 1 100.00% 1 / 1 1
 setRandomAuthKey 100.00% 1 / 1 100.00% 1 / 1 1
 setCallTime 100.00% 2 / 2 100.00% 1 / 1 2
 getCallTimeString 100.00% 1 / 1 100.00% 1 / 1 1
 getCallTime 100.00% 5 / 5 100.00% 1 / 1 2
 getFirstClient 100.00% 5 / 5 100.00% 1 / 1 2
 getFirstAppointment 100.00% 6 / 6 100.00% 1 / 1 2
 setStatusBySettings 85.71% 6 / 7 0.00% 0 / 1 4.05
 setClientsCount 100.00% 4 / 4 100.00% 1 / 1 2
 withoutPersonalData 100.00% 6 / 6 100.00% 1 / 1 2
 withLessData 100.00% 31 / 31 100.00% 1 / 1 21
 toCalendar 100.00% 9 / 9 100.00% 1 / 1 1
 toQueue 92.30% 12 / 13 0.00% 0 / 1 6.02
 hasArrivalTime 100.00% 4 / 4 100.00% 1 / 1 3
 getArrivalTime 72.72% 8 / 11 0.00% 0 / 1 4.32
 setArrivalTime 0.00% 0 / 2 0.00% 0 / 1 6
 getWaitedSeconds 100.00% 1 / 1 100.00% 1 / 1 1
 getWaitedMinutes 100.00% 1 / 1 100.00% 1 / 1 1
 getWaySeconds 0.00% 0 / 1 0.00% 0 / 1 2
 getWayMinutes 0.00% 0 / 1 0.00% 0 / 1 2
 setWasMissed 0.00% 0 / 3 0.00% 0 / 1 2
 getWasMissed 0.00% 0 / 1 0.00% 0 / 1 2
 toDerefencedAmendment 100.00% 8 / 8 100.00% 1 / 1 1
 toDerefencedCustomTextfield 0.00% 0 / 8 0.00% 0 / 1 2
 toDerefencedCustomTextfield2 0.00% 0 / 8 0.00% 0 / 1 2
 __toString 100.00% 15 / 15 100.00% 1 / 1 5
 getExternalUserId 100.00% 1 / 1 100.00% 1 / 1 1
 setExternalUserId 100.00% 2 / 2 100.00% 1 / 1 1
 getParkedBy 0.00% 0 / 1 0.00% 0 / 1 2
 setParkedBy 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __construct 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] exchangeArray 100.00% 4 / 4 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] offsetSet 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] getUnflattenedArray 100.00% 5 / 5 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] getValidator 100.00% 6 / 6 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] isValid 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] testValid 100.00% 7 / 7 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] createExample 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] getExample 100.00% 5 / 5 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] readJsonSchema 66.66% 6 / 9 0.00% 0 / 1 3.33
 [BO\Zmsentities\Schema\Entity] getEntityName 100.00% 4 / 4 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] setJsonCompressLevel 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] jsonSerialize 100.00% 11 / 11 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] __get 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __isset 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __set 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __unset 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __clone 100.00% 3 / 3 100.00% 1 / 1 3
 [BO\Zmsentities\Schema\Entity] addData 100.00% 14 / 14 100.00% 1 / 1 6
 [BO\Zmsentities\Schema\Entity] withData 0.00% 0 / 3 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] hasId 100.00% 1 / 1 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] getId 100.00% 2 / 2 100.00% 1 / 1 3
 [BO\Zmsentities\Schema\Entity] toProperty 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] hasProperty 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] getProperty 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] withProperty 0.00% 0 / 3 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] withCleanedUpFormData 100.00% 6 / 6 100.00% 1 / 1 3
 [BO\Zmsentities\Schema\Entity] getResolveLevel 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] setResolveLevel 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] withResolveLevel 100.00% 9 / 9 100.00% 1 / 1 5
 [BO\Zmsentities\Schema\Entity] withReference 100.00% 5 / 5 100.00% 1 / 1 2
25class Process extends Schema\Entity
26{
27    public const string PRIMARY = 'id';
28    public const string STATUS_FREE = 'free';
29    public const string STATUS_RESERVED = 'reserved';
30    public const string STATUS_CONFIRMED = 'confirmed';
31    public const string STATUS_PRECONFIRMED = 'preconfirmed';
32    public const string STATUS_QUEUED = 'queued';
33    public const string STATUS_CALLED = 'called';
34    public const string STATUS_PROCESSING = 'processing';
35    public const string STATUS_PENDING = 'pending';
36    public const string STATUS_FINISHED = 'finished';
37    public const string STATUS_MISSED = 'missed';
38    public const string STATUS_PARKED = 'parked';
39    public const string STATUS_ARCHIVED = 'archived';
40    public const string STATUS_DELETED = 'deleted';
41    public const string STATUS_ANONYMIZED = 'anonymized';
42    public const string STATUS_BLOCKED = 'blocked';
43    public const string STATUS_CONFLICT = 'conflict';
44    public static $schema = "process.json";
45    /**
46     * @return (Apiclient|Collection\AppointmentList|Collection\ClientList|Collection\RequestList|Queue|Scope|false|int|null|string)[]
47     *
48     */
49    #[\Override]
50    public function getDefaults()
51    {
52        return [
53            'amendment' => '',
54            'customTextfield' => '',
55            'customTextfield2' => '',
56            'appointments' => new Collection\AppointmentList(),
57            'apiclient' => new Apiclient(),
58            'authKey' => '',
59            'captchaToken' => '',
60            'clients' => new Collection\ClientList(),
61            'createIP' => '',
62            'createTimestamp' => time(),
63            'id' => 0,
64            'archiveId' => 0,
65            'queue' => new Queue(),
66            'reminderTimestamp' => 0,
67            'requests' => new Collection\RequestList(),
68            'scope' => new Scope(),
69            'status' => 'free',
70            'displayNumber' => '',
71            'dbstatus' => 'free',
72            'lastChange' => time(),
73            'wasMissed' => false,
74            'priority' => null,
75            'externalUserId' => null,
76            'parkedBy' => null,
77        ];
78    }
79
80    public static function createFromScope(Scope $scope, \DateTimeInterface $dateTime): static
81    {
82        $appointment = new Appointment();
83        $appointment->addScope($scope->id);
84        $appointment->addSlotCount(0);
85        $appointment->addDate(Helper\DateTime::create($dateTime)->modify('00:00:00')->getTimestamp());
86        $process = new static();
87        $process->scope = $scope;
88        $process->setStatus('queued');
89        $process->addAppointment($appointment);
90        return $process;
91    }
92
93    /**
94     * @return Collection\RequestList
95     *
96     */
97    public function getRequests()
98    {
99        if (!$this->requests instanceof Collection\RequestList) {
100            $requestList = new Collection\RequestList();
101            foreach ($this->requests as $request) {
102                $request = ($request instanceof Request) ? $request : new Request($request);
103                $requestList->addEntity($request);
104            }
105            $this->requests = $requestList;
106        }
107        return $this->requests;
108    }
109
110    public function getRequestIds(): mixed
111    {
112        return $this->getRequests()->getIds();
113    }
114
115    public function getDisplayNumber(): mixed
116    {
117        return $this->displayNumber;
118    }
119
120    public function getRequestCSV(): string
121    {
122        return $this->getRequests()->getIdsCsv();
123    }
124
125    public function addScope(mixed $scopeId): static
126    {
127        $this->scope = new Scope(array('id' => $scopeId));
128        return $this;
129    }
130
131    public function addQueue(mixed $number, \DateTimeInterface $dateTime): static
132    {
133        $this->queue = new Queue(array(
134            'number' => $number,
135            'arrivalTime' => $dateTime->getTimestamp(),
136            'priority' => $this->getPriority()
137        ));
138        return $this;
139    }
140
141    public function addRequests(mixed $source, mixed $requestCSV): static
142    {
143        $requestList = $this->getRequests();
144        foreach (explode(',', $requestCSV) as $id) {
145            if (!$requestList->hasRequests($id)) {
146                $this->requests[] = new Request(array(
147                    'source' => $source,
148                    'id' => $id
149                ));
150            }
151        }
152        return $this;
153    }
154
155    public function updateRequests(mixed $source, int|string $requestCSV = ''): static
156    {
157        $this->requests = new Collection\RequestList();
158        if ($requestCSV) {
159            foreach (explode(',', (string) $requestCSV) as $id) {
160                $this->requests->addEntity(new Request(array(
161                            'source' => $source,
162                            'id' => $id
163                        )));
164            }
165        }
166        return $this;
167    }
168
169    public function hasScopeAdmin(): bool
170    {
171        return ('' != $this->toProperty()->scope->contact->email->get());
172    }
173
174    public function sendAdminMailOnConfirmation(): bool
175    {
176        return (bool) ((int) $this->toProperty()->scope->preferences->client->adminMailOnAppointment->get());
177    }
178
179    public function sendAdminMailOnDeleted(): bool
180    {
181        return (bool) ((int) $this->toProperty()->scope->preferences->client->adminMailOnDeleted->get());
182    }
183
184    public function sendAdminMailOnUpdated(): bool
185    {
186        return (bool) ((int) $this->toProperty()->scope->preferences->client->adminMailOnUpdated->get());
187    }
188
189    public function shouldSendAdminMailOnClerkMail(): bool
190    {
191        return (bool) ((int) $this->toProperty()->scope->preferences->client->adminMailOnMailSent->get());
192    }
193
194    public function withUpdatedData(mixed $requestData, \DateTimeInterface $dateTime, mixed $scope = null, string $notice = ''): static
195    {
196        $this->scope = ($scope) ? $scope : $this->scope;
197        $this->addAppointmentFromRequest($requestData, $dateTime);
198        $requestCsv = isset($requestData['requests']) ? implode(',', $requestData['requests']) : 0;
199        $this->updateRequests($scope->getSource(), $requestCsv);
200        $this->addClientFromForm($requestData);
201        $this->addReminderTimestamp($requestData, $dateTime);
202        $this->addAmendment($requestData, $notice);
203        $this->addCustomTextfield($requestData);
204        $this->addCustomTextfield2($requestData);
205        $this->addPriority($requestData);
206        return $this;
207    }
208
209    public function addAppointmentFromRequest(mixed $requestData, \DateTimeInterface $dateTime): static
210    {
211        $this->appointments = null;
212        if (isset($requestData['selecteddate'])) {
213            $dateTime = new \DateTime($requestData['selecteddate']);
214        }
215        $dateTime = Helper\DateTime::create($dateTime);
216        if (isset($requestData['selectedtime'])) {
217            $time = explode('-', $requestData['selectedtime']);
218            $dateTime = $dateTime->setTime((int) $time[0], (int) $time[1]);
219        }
220
221        $appointment = (new Appointment())
222            ->addDate($dateTime->getTimestamp())
223            ->addScope($this->scope['id']);
224        if (isset($requestData['slotCount'])) {
225            $appointment->addSlotCount($requestData['slotCount']);
226        }
227        $this->addAppointment($appointment);
228        return $this;
229    }
230
231    public function addClientFromForm(mixed $requestData): static
232    {
233        $client = new Client();
234        foreach ($requestData as $key => $value) {
235            if (null !== $value && $client->offsetExists($key)) {
236                $client[$key] = (isset($value['value'])) ? $value['value'] : $value;
237            }
238        }
239        $this->clients = array();
240        $this->clients[] = $client;
241        return $this;
242    }
243
244    public function setStatus(string $status): static
245    {
246        $this->status = $status;
247        return $this;
248    }
249
250    public function getStatus(): mixed
251    {
252        return $this->status;
253    }
254
255    public function getReminderTimestamp(): mixed
256    {
257        $timestamp = $this->toProperty()->reminderTimestamp->get();
258        return ($timestamp) ? $timestamp : 0;
259    }
260
261    public function addReminderTimestamp(mixed $input, \DateTimeInterface $dateTime): static
262    {
263        $this->reminderTimestamp = (
264            Property::__keyExists('headsUpTime', $input) &&
265            $input['headsUpTime'] > 0
266        ) ? $dateTime->getTimestamp() - $input['headsUpTime'] : 0;
267        return $this;
268    }
269
270    /**
271     * @return \BO\Zmsentities\Collection\AppointmentList
272     *
273     */
274    public function getAppointments()
275    {
276        $appointments = $this['appointments'];
277        if (!$appointments instanceof Collection\AppointmentList) {
278            $appointments = new Collection\AppointmentList($appointments);
279            foreach ($appointments as $index => $appointment) {
280                if (!$appointment instanceof Appointment) {
281                    $appointments[$index] = new Appointment($appointment);
282                }
283            }
284            $this['appointments'] = $appointments;
285        }
286        return $appointments;
287    }
288
289    /**
290     * @return \BO\Zmsentities\Collection\ClientList
291     *
292     */
293    public function getClients()
294    {
295        $clients = $this['clients'];
296        if (!$clients instanceof Collection\ClientList) {
297            $clients = new Collection\ClientList($clients);
298            foreach ($clients as $index => $client) {
299                if (!$client instanceof Client) {
300                    $clients[$index] = new Client($client);
301                }
302            }
303            $this['clients'] = $clients;
304        }
305        return $clients;
306    }
307
308    public function hasAppointment(mixed $date, mixed $scopeId): bool
309    {
310        return $this->getAppointments()->hasDateScope($date, $scopeId);
311    }
312
313    /**
314     * check if process is with appointment and not only queued
315     * return Boolean
316     */
317    public function isWithAppointment(): bool
318    {
319        $appointment = $this->getFirstAppointment();
320        if ($appointment->hasTime()) {
321            return true;
322        }
323        return (1 == $this->toProperty()->queue->withAppointment->get());
324    }
325
326    public function hasProcessCredentials(): bool
327    {
328        return (isset($this['id']) && isset($this['authKey']) && $this['id'] && $this['authKey']);
329    }
330
331    public function withReassignedCredentials(mixed $process): static
332    {
333        $this->id = $process->getId();
334        $this->authKey = $process->getAuthKey();
335        return $this;
336    }
337
338    public function hasQueueNumber(): bool
339    {
340        return (isset($this['queue']) && isset($this['queue']['number']) && $this['queue']['number']);
341    }
342
343    public function getQueueNumber(): mixed
344    {
345        return $this['queue']['number'];
346    }
347
348    public function addAppointment(Appointment $newappointment): static
349    {
350        $this->appointments[] = $newappointment;
351        return $this;
352    }
353
354    /**
355     * Reminder: A process might have multiple scopes. Each appointment can
356     * have his own scope. The scope in $this->scope is the current/next scope.
357     * This function returns the original scope ID and ignores internal scope
358     * which are used for processing like to pick up documents
359     *
360     */
361    public function getScopeId(): mixed
362    {
363        //as current scope - see zmsbackend Query/Process EntityMapping
364        return $this->toProperty()->scope->id->get();
365    }
366
367    public function isDereferenced(): bool
368    {
369        if ($this->authKey === 'deref!0') {
370            return true;
371        }
372        $client = $this->getFirstClient();
373        return isset($client->familyName) && $client->familyName === 'dereferenced';
374    }
375
376    public function getCurrentScope(): Scope
377    {
378        return $this->getProperty('scope');
379    }
380
381    public function getAmendment(): mixed
382    {
383        return $this->toProperty()->amendment->get();
384    }
385
386    /*public function getShowUpTime()
387    {
388        return $this->toProperty()->showUpTime->get();
389    }*/
390
391    public function getShowUpTime(string|\DateTimeInterface $default = 'now', mixed $timezone = null): \BO\Zmsentities\Helper\DateTime
392    {
393        $showUpTime = $this->toProperty()->showUpTime->get();
394        $showDateTime = Helper\DateTime::create($default, $timezone);
395        if ($showUpTime) {
396            list($hours, $minutes, $seconds) = explode(':', $showUpTime);
397            $showDateTime = $showDateTime->setTime(intval($hours), intval($minutes), intval($seconds));
398        }
399        return $showDateTime;
400    }
401
402    public function getWaitingTime(): mixed
403    {
404        return $this->toProperty()->queue->waitingTime->get();
405    }
406
407    public function getProcessingTime(): mixed
408    {
409        return $this->toProperty()->processingTime->get();
410    }
411
412    public function getFinishTime(): mixed
413    {
414        return $this->toProperty()->finishTime->get();
415    }
416
417    public function addAmendment(mixed $input, string $notice = ''): static
418    {
419        $this->amendment = $notice;
420        $this->amendment .= (isset($input['amendment']) && $input['amendment']) ? $input['amendment'] : '';
421        $this->amendment = trim(ProcessPlainText::normalize($this->amendment));
422        return $this;
423    }
424
425    public function getCustomTextfield(): mixed
426    {
427        return $this->toProperty()->customTextfield->get();
428    }
429
430    public function addCustomTextfield(mixed $input): static
431    {
432        $this->customTextfield = (
433            isset($input['customTextfield']) && $input['customTextfield']
434        ) ? $input['customTextfield'] : '';
435        $this->customTextfield = trim(ProcessPlainText::normalize($this->customTextfield));
436        return $this;
437    }
438
439    public function getCustomTextfield2(): mixed
440    {
441        return $this->toProperty()->customTextfield2->get();
442    }
443
444    public function addCustomTextfield2(mixed $input): static
445    {
446        $this->customTextfield2 = (
447            isset($input['customTextfield2']) && $input['customTextfield2']
448        ) ? $input['customTextfield2'] : '';
449        $this->customTextfield2 = trim(ProcessPlainText::normalize($this->customTextfield2));
450        return $this;
451    }
452
453    public function addPriority(mixed $input): static
454    {
455        $this->priority = isset($input['priority']) && $input['priority'] ? $input['priority'] : null;
456
457        return $this;
458    }
459
460    public function getAuthKey(): mixed
461    {
462        return $this->toProperty()->authKey->get();
463    }
464
465    public function getPriority(): int
466    {
467        return (int) $this->toProperty()->priority->get();
468    }
469
470    public function setRandomAuthKey(): void
471    {
472        $this->authKey = bin2hex(random_bytes(32));
473    }
474
475    public function setCallTime(mixed $dateTime = null): static
476    {
477        $this->queue['callTime'] = ($dateTime) ? $dateTime->getTimestamp() : 0;
478        return $this;
479    }
480
481    public function getCallTimeString(): mixed
482    {
483        return $this->getCallTime()->format('H:i:s');
484    }
485
486    public function getCallTime(string|\DateTimeInterface $default = 'now', mixed $timezone = null): \BO\Zmsentities\Helper\DateTime
487    {
488        $callTime = $this->toProperty()->queue->callTime->get();
489        $callDateTime = Helper\DateTime::create($default, $timezone);
490        if ($callTime) {
491            $callDateTime = $callDateTime->setTimestamp($callTime);
492        }
493        return $callDateTime;
494    }
495
496    public function getFirstClient(): Client
497    {
498        $client = $this->getClients()->getFirst();
499        if (!$client) {
500            $client = new Client();
501            $this->clients->addEntity($client);
502        }
503        return $client;
504    }
505
506    public function getFirstAppointment(): Appointment
507    {
508        $appointment = $this->getAppointments()->getFirst();
509        if (!$appointment) {
510            $appointment = new Appointment();
511            $appointment->scope = $this->scope;
512            $this->appointments->addEntity($appointment);
513        }
514        return $appointment;
515    }
516
517    public function setStatusBySettings(): static
518    {
519        $scope = new Scope($this->scope);
520        if ('called' == $this->status && $this->queue['callCount'] > $scope->getPreference('queue', 'callCountMax')) {
521            $this->status = 'missed';
522        } elseif ('parked' == $this->status) {
523            $this->status = 'parked';
524        } else {
525            $this->status = 'confirmed';
526        }
527        return $this;
528    }
529
530    public function setClientsCount(mixed $count): static
531    {
532        $clientList = $this->getClients();
533        while ($clientList->count() < $count) {
534            $clientList->addEntity(new Client());
535        }
536        return $this;
537    }
538
539
540    public function withoutPersonalData(): static
541    {
542        $entity = clone $this;
543        if ($this->toProperty()->clients->isAvailable()) {
544            $client = $entity->getFirstClient();
545            unset($client['familyName']);
546            unset($client['email']);
547        }
548        return $entity;
549    }
550
551    /**
552     * Reduce data of dereferenced entities to a required minimum
553     *
554     * @return static
555     */
556    #[\Override]
557    public function withLessData(array $keepArray = [])
558    {
559        $entity = clone $this;
560        if (!in_array('availability', $keepArray)) {
561            foreach ($entity['appointments'] as $appointment) {
562                if ($appointment->toProperty()->scope->isAvailable()) {
563                    $scopeId = $appointment['scope']['id'];
564                    $appointment['scope'] = [
565                        'id' => $scopeId,
566                        'provider' => $appointment['scope']['provider'] ?? [],
567                        'shortName' => $appointment['scope']['shortName'] ?? ''
568                    ];
569                }
570                if ($appointment->toProperty()->availability->isAvailable()) {
571                    unset($appointment['availability']);
572                }
573            }
574        }
575
576        if (!in_array('createTimestamp', $keepArray)) {
577            unset($entity['createTimestamp']);
578        }
579        unset($entity['createIP']);
580        if ($entity->toProperty()->scope->status->isAvailable()) {
581            unset($entity['scope']['status']);
582        }
583
584        if ($entity->status == 'free') {
585// delete keys
586            foreach (['authKey', 'queue', 'requests',] as $key) {
587                if (!in_array($key, $keepArray) && $entity->toProperty()->$key->isAvailable()) {
588                    unset($entity[$key]);
589                }
590            }
591            // delete if empty
592            foreach (['amendment', 'id', 'authKey', 'archiveId', 'reminderTimestamp',] as $key) {
593                if (!in_array($key, $keepArray) && $entity->toProperty()->$key->isAvailable() && !$entity[$key]) {
594                    unset($entity[$key]);
595                }
596            }
597            if (!in_array('provider', $keepArray) && $entity->toProperty()->scope->provider->data->isAvailable()) {
598                unset($entity['scope']['provider']['data']);
599            }
600        }
601
602        if (!in_array('dayoff', $keepArray) && $entity->toProperty()->scope->dayoff->isAvailable()) {
603            unset($entity['scope']['dayoff']);
604        }
605        if (!in_array('scope', $keepArray) && $entity->toProperty()->scope->preferences->isAvailable()) {
606            unset($entity['scope']['preferences']);
607        }
608        return $entity;
609    }
610
611    public function toCalendar(): Calendar
612    {
613        $calendar = new Calendar();
614        $dateTime = $this->getFirstAppointment()->toDateTime();
615        $day = new Day();
616        $day->setDateTime($dateTime);
617        $calendar->firstDay = $day;
618        $calendar->lastDay = $day;
619        $calendar->requests = clone $this->getRequests();
620        $calendar->scopes = new Collection\ScopeList([$this->scope]);
621        return $calendar;
622    }
623
624    public function toQueue(\DateTimeInterface $dateTime): mixed
625    {
626        $queue = new Queue($this->queue);
627        $queue->withAppointment = ($this->getFirstAppointment()->hasTime()) ? true : false;
628        $queue->waitingTime = ($queue->waitingTime) ? $queue->waitingTime : 0;
629        $queue->wayTime = ($queue->wayTime) ? $queue->wayTime : 0;
630        if ($queue->withAppointment) {
631            $queue->number = !empty($this->getDisplayNumber())
632                ? $this->getDisplayNumber()
633                : $this->id;
634        } else {
635            $queue->number = $this->toProperty()->queue->number->get();
636        }
637        $queue->displayNumber = $this->getDisplayNumber();
638        $queue->arrivalTime = $this->getArrivalTime($dateTime)->getTimestamp();
639        $queue->priority = $this->priority;
640        return $queue->setProcess($this);
641    }
642
643    public function hasArrivalTime(): bool
644    {
645        if ($this->isWithAppointment()) {
646            $arrivalTime = $this->getFirstAppointment()->date;
647        } else {
648            $arrivalTime = $this->toProperty()->queue->arrivalTime->get();
649        }
650        return ($arrivalTime) ? true : false;
651    }
652
653    /**
654     * @param \DateTimeInterface|string $default
655     *
656     */
657    public function getArrivalTime(string|\DateTimeInterface $default = 'now', mixed $timezone = null): \BO\Zmsentities\Helper\DateTime
658    {
659        $queueArrivalTime = $this->toProperty()->queue->arrivalTime->get();
660
661        if ($queueArrivalTime) {
662            // Falls der Queue-Wert vorhanden ist â€“ auch wenn ein Termin existiert â€“ verwende diesen (dabei ist handelt es sich, um verpasste Termine)
663            $arrivalTime = $queueArrivalTime;
664        } elseif ($this->isWithAppointment()) {
665            $arrivalTime = $this->getFirstAppointment()->date;
666        } else {
667            $arrivalTime = 0;
668        }
669
670        $arrivalTime = (int)$arrivalTime;
671        $arrivalDateTime = Helper\DateTime::create($default, $timezone);
672        if ($arrivalTime) {
673            $arrivalDateTime = $arrivalDateTime->setTimestamp($arrivalTime);
674        }
675        return $arrivalDateTime;
676    }
677
678    public function setArrivalTime(?\DateTimeInterface $dateTime = null): static
679    {
680        $this->queue['arrivalTime'] = ($dateTime) ? $dateTime->getTimestamp() : 0;
681        return $this;
682    }
683
684    /**
685     * Calculate real waiting time, only available after called
686     */
687    public function getWaitedSeconds(string|\DateTimeInterface $defaultTime = 'now'): mixed
688    {
689        return $this->getCallTime($defaultTime)->getTimestamp() - $this->getArrivalTime($defaultTime)->getTimestamp();
690    }
691
692    public function getWaitedMinutes(string|\DateTimeInterface $defaultTime = 'now'): mixed
693    {
694        return $this->getWaitedSeconds($defaultTime) / 60;
695    }
696
697    public function getWaySeconds(string|\DateTimeInterface $defaultTime = 'now'): mixed
698    {
699        return $this->getShowUpTime($defaultTime)->getTimestamp() - $this->getCallTime($defaultTime)->getTimestamp();
700    }
701
702    public function getWayMinutes(string|\DateTimeInterface $defaultTime = 'now'): mixed
703    {
704        return $this->getWaySeconds($defaultTime) / 60;
705    }
706
707    public function setWasMissed(bool $bool): static
708    {
709        $this->wasMissed = $bool;
710        $this->status = self::STATUS_MISSED;
711        return $this;
712    }
713
714    public function getWasMissed(): bool
715    {
716        return (bool) $this->wasMissed;
717    }
718
719    public function toDerefencedAmendment(): string|null
720    {
721        $lastChange = (new \DateTimeImmutable())->setTimestamp($this->createTimestamp)->format('c');
722        return var_export(array(
723                'BuergerID' => $this->id,
724                'StandortID' => $this->scope['id'],
725                'Anmerkung' => null,
726                'IPTimeStamp' => $this->createTimestamp,
727                'LastChange' => $lastChange,
728            ), true);
729    }
730
731    public function toDerefencedCustomTextfield(): string|null
732    {
733        $lastChange = (new \DateTimeImmutable())->setTimestamp($this->createTimestamp)->format('c');
734        return var_export(array(
735                'BuergerID' => $this->id,
736                'StandortID' => $this->scope['id'],
737                'CustomTextfield' => null,
738                'IPTimeStamp' => $this->createTimestamp,
739                'LastChange' => $lastChange,
740            ), true);
741    }
742
743    public function toDerefencedCustomTextfield2(): string|null
744    {
745        $lastChange = (new \DateTimeImmutable())->setTimestamp($this->createTimestamp)->format('c');
746        return var_export(array(
747                'BuergerID' => $this->id,
748                'StandortID' => $this->scope['id'],
749                'CustomTextfield2' => null,
750                'IPTimeStamp' => $this->createTimestamp,
751                'LastChange' => $lastChange,
752            ), true);
753    }
754
755    public function __toString()
756    {
757        $string = "process#";
758        $string .= $this->getDisplayNumber() ?: $this->id ?: $this->archiveId;
759        $string .= ":" . $this->authKey;
760        $string .= " (" . $this->status . ")";
761        $string .= " " . $this->getFirstAppointment()->toDateTime()->format('c');
762        $string .= " " . ($this->isWithAppointment() ? "appoint" : "arrival:" . $this->getArrivalTime()->format('c'));
763        $string .= " " . $this->getFirstAppointment()->slotCount . "slots";
764        $string .= "*" . count($this->appointments);
765        foreach ($this->getRequests() as $request) {
766            $string .= " " . $request['source'] . "." . $request['id'];
767        }
768        $string .= " scope." . $this['scope']['id'];
769        $string .= " ~" . base_convert($this['lastChange'], 10, 35);
770        $string .= " client:" . $this['apiclient']['shortname'];
771        $string .= " token:" . ($this['captchaToken'] ?? '(none)');
772        return $string;
773    }
774
775    public function getExternalUserId(): mixed
776    {
777        return $this->toProperty()->externalUserId->get();
778    }
779
780    public function setExternalUserId(mixed $externalUserId): static
781    {
782        $this->externalUserId = $externalUserId;
783        return $this;
784    }
785
786    public function getParkedBy(): mixed
787    {
788        return $this->toProperty()->parkedBy->get();
789    }
790
791    public function setParkedBy(mixed $name): void
792    {
793        $this->parkedBy = $name;
794    }
795}

Inherited from BO\Zmsentities\Schema\Entity

59    public function __construct(mixed $input = null, int $flags = \ArrayObject::ARRAY_AS_PROPS, string $iterator_class = "ArrayIterator")
60    {
61        /** @var class-string<\ArrayIterator> $iterator_class */
62        parent::__construct($this->getDefaults(), $flags, $iterator_class);
63        if ($input) {
64            $input = $this->getUnflattenedArray($input);
65            $this->addData($input);
66        }
67    }
70    public function exchangeArray(object|array $input): array
71    {
72        parent::exchangeArray($this->getDefaults());
73        $input = $this->getUnflattenedArray($input);
74        $this->addData($input);
75        return $this->getArrayCopy();
76    }
82    public function offsetSet(mixed $key, mixed $value): void
83    {
84        /** @psalm-suppress PossiblyNullArgument */
85        parent::offsetSet($key, $value);
86    }
88    public function getUnflattenedArray(mixed $input): mixed
89    {
90        if (!$input instanceof UnflattedArray) {
91            $input = new UnflattedArray($input);
92            $input->getUnflattenedArray();
93        }
94        $input = $input->getValue();
95        return $input;
96    }
112    public function getValidator(string $locale = 'de_DE', int $resolveLevel = 0): Validator
113    {
114        $jsonSchema = self::readJsonSchema()->withResolvedReferences($resolveLevel);
115        $data = (new Schema($this))->withoutRefs();
116        if (Property::__keyExists('$schema', $data)) {
117            unset($data['$schema']);
118        }
119        $validator = new Validator($data->toJsonObject(true), $jsonSchema, $locale);
120        return $validator;
121    }
128    public function isValid(int $resolveLevel = 0): bool
129    {
130        $validator = $this->getValidator('de_DE', $resolveLevel = 0);
131        return $validator->isValid();
132    }
140    public function testValid(string $locale = 'de_DE', int $resolveLevel = 0): bool
141    {
142        $validator = $this->getValidator($locale, $resolveLevel);
143        if (!$validator->isValid()) {
144            $exception = new \BO\Zmsentities\Exception\SchemaValidation();
145            $exception->setSchemaName($this->getEntityName());
146            $exception->setValidationError($validator->getErrors());
147            throw $exception;
148        }
149        return true;
150    }
157    public static function createExample()
158    {
159        $object = new static();
160        return $object->getExample();
161    }
168    public static function getExample()
169    {
170        $class = get_called_class();
171        $jsonSchema = self::readJsonSchema();
172        if ($jsonSchema->offsetExists('example')) {
173            return new $class($jsonSchema['example']);
174        }
175        return new $class();
176    }
178    protected static function readJsonSchema(): mixed
179    {
180        $class = get_called_class();
181        if (!array_key_exists($class, self::$schemaCache)) {
182            $schemaFile = $class::$schema;
183            if (!is_string($schemaFile)) {
184                throw new \BO\Zmsentities\Exception\SchemaMissingJsonFile(
185                    'Missing JSON-Schema file for ' . $class
186                );
187            }
188            self::$schemaCache[$class] = Loader::asArray($schemaFile);
189        }
190        return self::$schemaCache[$class];
191    }
193    public function getEntityName(): string
194    {
195        $entity = get_class($this);
196        $entity = preg_replace('#.*[\\\]#', '', $entity) ?? '';
197        $entity = strtolower($entity);
198        return $entity;
199    }
201    public function setJsonCompressLevel(mixed $jsonCompressLevel): static
202    {
203        $this->jsonCompressLevel = $jsonCompressLevel;
204        return $this;
205    }
208    public function jsonSerialize(): mixed
209    {
210        $schema = array(
211            '$schema' => 'https://schema.berlin.de/queuemanagement/' . $this->getEntityName() . '.json'
212        );
213        $schema = array_merge($schema, $this->getArrayCopy());
214        if ($this instanceof \BO\Zmsentities\Helper\NoSanitize) {
215            $serialize = $schema;
216        } else {
217            $schema = new Schema($schema);
218            $schema->setDefaults($this->getDefaults());
219            $schema->setJsonCompressLevel($this->jsonCompressLevel);
220            $serialize = $schema->toJsonObject();
221        }
222        return $serialize;
223    }
235    public function __get(string $name): mixed
236    {
237        return $this->offsetGet($name);
238    }
240    public function __isset(string $name): bool
241    {
242        return $this->offsetExists($name);
243    }
245    public function __set(string $name, mixed $value): void
246    {
247        $this->offsetSet($name, $value);
248    }
250    public function __unset(string $name): void
251    {
252        $this->offsetUnset($name);
253    }
255    public function __clone()
256    {
257        foreach ($this as $key => $property) {
258            if (is_object($property)) {
259                $this[$key] = clone $property;
260            }
261        }
262    }
268    public function addData(array|object $mergeData): static
269    {
270        foreach ($mergeData as $key => $item) {
271            if (isset($this[$key])) {
272                if ($this[$key] instanceof Entity) {
273                    $this[$key]->setResolveLevel(($this->getResolveLevel() ?? 0) - 1);
274                    $this[$key]->addData($item);
275                } elseif ($this[$key] instanceof \BO\Zmsentities\Collection\Base) {
276                    $this[$key]->exchangeArray([]);
277                    $this[$key]->setResolveLevel(($this->getResolveLevel() ?? 0) - 1);
278                    $this[$key]->addData($item);
279                } elseif (is_array($this[$key])) {
280                    $this[$key] = array_replace_recursive($this[$key], $item);
281                } else {
282                    $this[$key] = $item;
283                }
284            } else {
285                $this[$key] = $item;
286            }
287        }
288        return $this;
289    }
294    public function withData(mixed $mergeData): static
295    {
296        $entity = clone $this;
297        $entity->addData($mergeData);
298        return $entity;
299    }
301    public function hasId(): bool
302    {
303        return (false !== $this->getId()) ? true : false;
304    }
306    public function getId(): mixed
307    {
308        $idName = $this::PRIMARY;
309        return ($this->offsetExists($idName) && $this[$idName]) ? $this[$idName] : false;
310    }
317    public function toProperty()
318    {
319        return new \BO\Zmsentities\Helper\Property($this);
320    }
322    public function hasProperty(mixed $propertyName): mixed
323    {
324        return $this->toProperty()->{$propertyName}->isAvailable();
325    }
327    public function getProperty(string $propertyName, mixed $default = ''): mixed
328    {
329        return $this->toProperty()->{$propertyName}->get($default);
330    }
335    public function withProperty(mixed $propertyName, mixed $newValue): static
336    {
337        $entity = clone $this;
338        $entity[$propertyName] = $newValue;
339        return $entity;
340    }
357    public function withCleanedUpFormData()
358    {
359        $entity = clone $this;
360        if (isset($entity['save'])) {
361            unset($entity['save']);
362        }
363        if (isset($entity['removeImage'])) {
364            unset($entity['removeImage']);
365        }
366        return $entity;
367    }
372    public function getResolveLevel()
373    {
374        return $this->resolveLevel;
375    }
381    public function setResolveLevel($resolveLevel)
382    {
383        $this->resolveLevel = $resolveLevel;
384        return $this;
385    }
393    public function withResolveLevel($resolveLevel): self|array
394    {
395        if ($resolveLevel >= 0) {
396            $entity = clone $this;
397            foreach ($entity as $key => $value) {
398                if ($value instanceof Entity || $value instanceof \BO\Zmsentities\Collection\Base) {
399                    $entity[$key] = $value->withResolveLevel($resolveLevel - 1);
400                } else {
401                    $entity[$key] = $value;
402                }
403            }
404            $entity->setResolveLevel($resolveLevel);
405            return $entity;
406        } else {
407            return $this->withReference();
408        }
409    }
417    public function withReference($additionalData = []): self|array
418    {
419        if (isset($this[$this::PRIMARY])) {
420            $additionalData['$ref'] =
421                $this::$schemaRefPrefix . $this->getEntityName() . '/' . $this[$this::PRIMARY] . '/';
422            return $additionalData;
423        } else {
424            return $this->withResolveLevel(0);
425        }
426    }