| 14 | | class Process extends \BO\Zmsbackend\Query\Base implements \BO\Zmsbackend\Query\MappingInterface |
| 15 | | { |
| 16 | | |
| 17 | | |
| 18 | | |
| 19 | | const string TABLE = 'buerger'; |
| 20 | | |
| 21 | | const string QUERY_DEREFERENCED = "UPDATE `buerger` process LEFT JOIN `standort` s USING(StandortID) |
| 22 | | SET |
| 23 | | process.Anmerkung = ?, |
| 24 | | process.custom_text_field = ?, |
| 25 | | process.custom_text_field2 = ?, |
| 26 | | process.StandortID = 0, |
| 27 | | process.AbholortID = 0, |
| 28 | | process.Abholer = 0, |
| 29 | | process.Name = 'dereferenced', |
| 30 | | process.IPadresse = '', |
| 31 | | process.IPTimeStamp = 0, |
| 32 | | process.NutzerID = 0, |
| 33 | | process.vorlaeufigeBuchung = 0, |
| 34 | | process.bestaetigt = 1, |
| 35 | | process.absagecode = 'deref!0', |
| 36 | | process.EMail = '', |
| 37 | | process.NutzerID = 0, |
| 38 | | process.priority = null, |
| 39 | | process.status = 'blocked' |
| 40 | | WHERE |
| 41 | | (process.BuergerID = ? AND process.absagecode = ?) |
| 42 | | OR process.istFolgeterminvon = ? |
| 43 | | "; |
| 44 | | |
| 45 | | const string QUERY_CANCELED = " |
| 46 | | UPDATE `buerger` process LEFT JOIN `standort` s USING(StandortID) |
| 47 | | SET |
| 48 | | process.Anmerkung = CONCAT( |
| 49 | | 'Abgesagter Termin gebucht am: ', |
| 50 | | FROM_UNIXTIME(process.IPTimeStamp,'%d.%m.%Y, %H:%i'),' Uhr | ', |
| 51 | | IFNULL(process.Anmerkung,'') |
| 52 | | ), |
| 53 | | process.Name = '(abgesagt)', |
| 54 | | process.IPadresse = '', |
| 55 | | process.IPTimeStamp = :canceledTimestamp + (IFNULL(s.loeschdauer, 15) * 60), |
| 56 | | process.NutzerID = 0, |
| 57 | | process.vorlaeufigeBuchung = 1, |
| 58 | | process.status = 'deleted', |
| 59 | | process.absagecode = :newAuthKey |
| 60 | | WHERE |
| 61 | | (process.BuergerID = :processId AND process.absagecode = :authKey) |
| 62 | | OR process.istFolgeterminvon = :processId |
| 63 | | "; |
| 64 | | |
| 65 | | const string QUERY_DELETE = "DELETE FROM `buerger` |
| 66 | | WHERE |
| 67 | | BuergerID = ? |
| 68 | | OR istFolgeterminvon = ? |
| 69 | | "; |
| 70 | | |
| 71 | | const string QUERY_REASSIGN_PROCESS_CREDENTIALS = "UPDATE `buerger` process |
| 72 | | SET |
| 73 | | process.BuergerID = :newProcessId, |
| 74 | | process.absagecode = :newAuthKey |
| 75 | | WHERE BuergerID = :processId |
| 76 | | "; |
| 77 | | |
| 78 | | const string QUERY_REASSIGN_PROCESS_REQUESTS = "UPDATE `buergeranliegen` requests |
| 79 | | SET |
| 80 | | requests.BuergerID = :newProcessId |
| 81 | | WHERE BuergerID = :processId |
| 82 | | "; |
| 83 | | |
| 84 | | const string QUERY_REASSIGN_FOLLWING_PROCESS = "UPDATE `buerger` process |
| 85 | | SET process.istFolgeterminvon = :newProcessId |
| 86 | | WHERE istFolgeterminvon = :processId |
| 87 | | "; |
| 88 | | |
| 89 | | const string QUERY_UPDATE_FOLLOWING_PROCESS = "UPDATE buerger |
| 90 | | SET vorlaeufigeBuchung = :reserved |
| 91 | | WHERE istFolgeterminvon = :processID |
| 92 | | "; |
| 93 | | |
| 94 | | public function getQueryNewProcessId(): string |
| 95 | | { |
| 96 | | $random = rand(20, 999); |
| 97 | | return 'SELECT pseq.processId AS `nextid` |
| 98 | | FROM process_sequence pseq |
| 99 | | WHERE pseq.processId = ( |
| 100 | | SELECT ps.processID FROM `process_sequence` ps LEFT JOIN `' . self::getTablename() . '` p |
| 101 | | ON ps.processId = p.BuergerID |
| 102 | | WHERE p.`BuergerID` IS NULL |
| 103 | | LIMIT ' . $random . ',1) |
| 104 | | FOR UPDATE'; |
| 105 | | } |
| 106 | | |
| 107 | | public function getLockProcessId(): string |
| 108 | | { |
| 109 | | return 'SELECT p.`BuergerID` FROM `' . self::getTablename() . '` p WHERE p.`BuergerID` = :processId FOR UPDATE'; |
| 110 | | } |
| 111 | | |
| 112 | | public function getLockAssignedWorkstationId(): string |
| 113 | | { |
| 114 | | return 'SELECT p.`NutzerID` |
| 115 | | FROM `' . self::getTablename() . '` p |
| 116 | | WHERE p.`BuergerID` = :processId |
| 117 | | FOR UPDATE'; |
| 118 | | } |
| 119 | | |
| 120 | | #[\Override] |
| 121 | | public function addJoin() |
| 122 | | { |
| 123 | | $joins = []; |
| 124 | | |
| 125 | | if ($this->shouldLoadEntity('availability')) { |
| 126 | | $joins[] = $this->addJoinAvailability(); |
| 127 | | } |
| 128 | | |
| 129 | | if ($this->shouldLoadEntity('scope')) { |
| 130 | | $joins[] = $this->addJoinScope(); |
| 131 | | } |
| 132 | | |
| 133 | | return $joins; |
| 134 | | } |
| 135 | | |
| 136 | | |
| 137 | | |
| 138 | | |
| 139 | | protected function addJoinAvailability(): \BO\Zmsbackend\Availability\Repository\Availability |
| 140 | | { |
| 141 | | $this->leftJoin( |
| 142 | | new \BO\Zmsbackend\Query\Alias(\BO\Zmsbackend\Availability\Repository\Availability::TABLE, 'availability'), |
| 143 | | \BO\Zmsbackend\Availability\Repository\Availability::getJoinExpression('`process`', '`availability`') |
| 144 | | ); |
| 145 | | $joinQuery = new \BO\Zmsbackend\Availability\Repository\Availability($this, $this->getPrefixed('appointments__0__availability__')); |
| 146 | | return $joinQuery; |
| 147 | | } |
| 148 | | |
| 149 | | |
| 150 | | |
| 151 | | |
| 152 | | protected function addJoinScope(): \BO\Zmsbackend\Query\Scope |
| 153 | | { |
| 154 | | $this->leftJoin( |
| 155 | | new \BO\Zmsbackend\Query\Alias(\BO\Zmsbackend\Query\Scope::TABLE, 'scope'), |
| 156 | | self::expression( |
| 157 | | 'IF(`process`.`AbholortID`, |
| 158 | | `process`.`AbholortID`, |
| 159 | | `process`.`StandortID` |
| 160 | | )' |
| 161 | | ), |
| 162 | | '=', |
| 163 | | 'scope.StandortID' |
| 164 | | ); |
| 165 | | $joinQuery = new \BO\Zmsbackend\Query\Scope($this, $this->getPrefixed('scope__')); |
| 166 | | return $joinQuery; |
| 167 | | } |
| 168 | | |
| 169 | | public function addConditionDate($date): static |
| 170 | | { |
| 171 | | $this->query->where('process.Datum', '=', $date); |
| 172 | | return $this; |
| 173 | | } |
| 174 | | |
| 175 | | |
| 176 | | |
| 177 | | |
| 178 | | public function addConditionDisplayNumber($displayNumber): static |
| 179 | | { |
| 180 | | $this->query->where('process.displayNumber', '=', $displayNumber); |
| 181 | | return $this; |
| 182 | | } |
| 183 | | |
| 184 | | |
| 185 | | |
| 186 | | |
| 187 | | |
| 188 | | |
| 189 | | protected function calculateStatus(): string|null |
| 190 | | { |
| 191 | | if ($this->query->value('Name') === '(abgesagt)') { |
| 192 | | return 'deleted'; |
| 193 | | } |
| 194 | | |
| 195 | | if ( |
| 196 | | $this->query->value('StandortID') == 0 |
| 197 | | && $this->query->value('AbholortID') == 0 |
| 198 | | ) { |
| 199 | | return 'blocked'; |
| 200 | | } |
| 201 | | |
| 202 | | if ( |
| 203 | | $this->query->value('vorlaeufigeBuchung') == 1 |
| 204 | | && $this->query->value('bestaetigt') == 0 |
| 205 | | ) { |
| 206 | | return 'reserved'; |
| 207 | | } |
| 208 | | |
| 209 | | if ($this->query->value('nicht_erschienen') != 0) { |
| 210 | | return 'missed'; |
| 211 | | } |
| 212 | | |
| 213 | | if ($this->query->value('parked') != 0) { |
| 214 | | return 'parked'; |
| 215 | | } |
| 216 | | |
| 217 | | if ( |
| 218 | | $this->query->value('Abholer') != 0 |
| 219 | | && $this->query->value('AbholortID') != 0 |
| 220 | | && $this->query->value('NutzerID') == 0 |
| 221 | | ) { |
| 222 | | return 'pending'; |
| 223 | | } |
| 224 | | |
| 225 | | if ( |
| 226 | | $this->query->value('AbholortID') == 0 |
| 227 | | && $this->query->value('aufruferfolgreich') != 0 |
| 228 | | && $this->query->value('NutzerID') != 0 |
| 229 | | ) { |
| 230 | | return 'processing'; |
| 231 | | } |
| 232 | | |
| 233 | | if ( |
| 234 | | $this->query->value('aufrufzeit') != "00:00:00" |
| 235 | | && $this->query->value('NutzerID') != 0 |
| 236 | | && $this->query->value('AbholortID') == 0 |
| 237 | | ) { |
| 238 | | return 'called'; |
| 239 | | } |
| 240 | | |
| 241 | | if ($this->query->value('Uhrzeit') == "00:00:00") { |
| 242 | | return 'queued'; |
| 243 | | } |
| 244 | | |
| 245 | | if ( |
| 246 | | $this->query->value('vorlaeufigeBuchung') == 0 |
| 247 | | && $this->query->value('bestaetigt') == 0 |
| 248 | | ) { |
| 249 | | return 'preconfirmed'; |
| 250 | | } |
| 251 | | |
| 252 | | if ( |
| 253 | | $this->query->value('vorlaeufigeBuchung') == 0 |
| 254 | | && $this->query->value('bestaetigt') == 1 |
| 255 | | ) { |
| 256 | | return 'confirmed'; |
| 257 | | } |
| 258 | | |
| 259 | | return null; |
| 260 | | } |
| 261 | | |
| 262 | | |
| 263 | | |
| 264 | | |
| 265 | | |
| 266 | | #[\Override] |
| 267 | | public function getEntityMapping() |
| 268 | | { |
| 269 | | $status_expression = self::expression( |
| 270 | | 'CASE |
| 271 | | WHEN process.status = "called" AND process.aufrufzeit != "00:00:00" AND process.NutzerID != 0 AND process.AbholortID = 0 |
| 272 | | THEN "called" |
| 273 | | WHEN process.status = "called" AND process.Uhrzeit = "00:00:00" |
| 274 | | THEN "queued" |
| 275 | | WHEN process.status = "called" AND process.vorlaeufigeBuchung = 0 AND process.bestaetigt = 1 |
| 276 | | THEN "confirmed" |
| 277 | | ELSE process.status |
| 278 | | END' |
| 279 | | ); |
| 280 | | |
| 281 | | return array_filter([ |
| 282 | | 'amendment' => 'process.Anmerkung', |
| 283 | | 'id' => 'process.BuergerID', |
| 284 | | 'appointments__0__date' => self::expression( |
| 285 | | 'CONCAT(`process`.`Datum`, " ", `process`.`Uhrzeit`)' |
| 286 | | ), |
| 287 | | 'scope__id' => self::expression( |
| 288 | | 'IF(`process`.`AbholortID`, |
| 289 | | `process`.`AbholortID`, |
| 290 | | `process`.`StandortID` |
| 291 | | )' |
| 292 | | ), |
| 293 | | 'appointments__0__scope__id' => 'process.StandortID', |
| 294 | | |
| 295 | | 'appointments__0__slotCount' => self::expression('process.hatFolgetermine + 1'), |
| 296 | | 'authKey' => 'process.absagecode', |
| 297 | | 'clients__0__email' => 'process.EMail', |
| 298 | | 'clients__0__emailSendCount' => 'process.EMailverschickt', |
| 299 | | 'clients__0__familyName' => 'process.Name', |
| 300 | | 'clients__0__surveyAccepted' => 'process.zustimmung_kundenbefragung', |
| 301 | | 'clients__0__telephone' => self::expression( |
| 302 | | 'IF(`process`.`telefonnummer_fuer_rueckfragen`!="", |
| 303 | | `process`.`telefonnummer_fuer_rueckfragen`, |
| 304 | | `process`.`Telefonnummer` |
| 305 | | )' |
| 306 | | ), |
| 307 | | 'customTextfield' => 'process.custom_text_field', |
| 308 | | 'customTextfield2' => 'process.custom_text_field2', |
| 309 | | 'createIP' => 'process.IPAdresse', |
| 310 | | 'priority' => 'process.priority', |
| 311 | | 'createTimestamp' => 'process.IPTimeStamp', |
| 312 | | 'lastChange' => 'process.updateTimestamp', |
| 313 | | 'showUpTime' => 'process.showUpTime', |
| 314 | | 'processingTime' => 'process.processing_time', |
| 315 | | 'timeoutTime' => 'process.timeoutTime', |
| 316 | | 'finishTime' => 'process.finishTime', |
| 317 | | 'status' => $status_expression, |
| 318 | | 'queue__status' => $status_expression, |
| 319 | | 'queue__arrivalTime' => self::expression( |
| 320 | | 'CONCAT( |
| 321 | | `process`.`Datum`, |
| 322 | | " ", |
| 323 | | IF(`process`.`wsm_aufnahmezeit`, `process`.`wsm_aufnahmezeit`, `process`.`Uhrzeit`) |
| 324 | | )' |
| 325 | | ), |
| 326 | | 'queue__callCount' => 'process.AnzahlAufrufe', |
| 327 | | 'queue__callTime' => 'process.aufrufzeit', |
| 328 | | 'queue__lastCallTime' => 'process.Timestamp', |
| 329 | | 'displayNumber' => self::expression( |
| 330 | | 'COALESCE( |
| 331 | | `process`.`displayNumber`, |
| 332 | | IF(`process`.`wartenummer`, |
| 333 | | `process`.`wartenummer`, |
| 334 | | `process`.`BuergerID` |
| 335 | | ) |
| 336 | | )' |
| 337 | | ), |
| 338 | | 'queue__number' => self::expression( |
| 339 | | 'IF(`process`.`wartenummer`, |
| 340 | | `process`.`wartenummer`, |
| 341 | | `process`.`BuergerID` |
| 342 | | )' |
| 343 | | ), |
| 344 | | 'queue__destination' => $this->shouldLoadEntity('processuser') |
| 345 | | ? 'processuser.Arbeitsplatznr' |
| 346 | | : '', |
| 347 | | 'queue__destinationHint' => $this->shouldLoadEntity('processuser') |
| 348 | | ? 'processuser.aufrufzusatz' |
| 349 | | : '', |
| 350 | | 'queue__waitingTime' => 'process.waiting_time', |
| 351 | | 'queue__wayTime' => 'process.way_time', |
| 352 | | 'queue__withAppointment' => self::expression( |
| 353 | | 'IF(`process`.`wartenummer`, |
| 354 | | "0", |
| 355 | | "1" |
| 356 | | )' |
| 357 | | ), |
| 358 | | 'reminderTimestamp' => 'process.Erinnerungszeitpunkt', |
| 359 | | '__clientsCount' => 'process.AnzahlPersonen', |
| 360 | | 'wasMissed' => 'process.wasMissed', |
| 361 | | 'externalUserId' => 'process.external_user_id', |
| 362 | | 'isTicketprinter' => 'process.is_ticketprinter', |
| 363 | | 'parkedBy' => 'process.parkedBy', |
| 364 | | ], 'strlen'); |
| 365 | | } |
| 366 | | |
| 367 | | public function addCountValue(): static |
| 368 | | { |
| 369 | | $this->query->select([ |
| 370 | | 'processCount' => self::expression('COUNT(*)'), |
| 371 | | ]); |
| 372 | | return $this; |
| 373 | | } |
| 374 | | |
| 375 | | |
| 376 | | |
| 377 | | |
| 378 | | public function addConditionHasTelephone(): static |
| 379 | | { |
| 380 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $condition) { |
| 381 | | $condition |
| 382 | | ->andWith('process.telefonnummer_fuer_rueckfragen', '!=', '') |
| 383 | | ->orWith('process.Telefonnummer', '!=', ''); |
| 384 | | }); |
| 385 | | return $this; |
| 386 | | } |
| 387 | | |
| 388 | | public function addConditionProcessDeleteInterval(\DateTimeInterface $expirationDate): static |
| 389 | | { |
| 390 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($expirationDate) { |
| 391 | | $query->andWith( |
| 392 | | self::expression( |
| 393 | | 'CONCAT(`process`.`Datum`, " ", `process`.`Uhrzeit`)' |
| 394 | | ), |
| 395 | | '<=', |
| 396 | | $expirationDate->format('Y-m-d H:i:s') |
| 397 | | ); |
| 398 | | }); |
| 399 | | $this->query->orderBy('appointments__0__date', 'ASC'); |
| 400 | | return $this; |
| 401 | | } |
| 402 | | |
| 403 | | public function addConditionProcessDeleteBeforeDate(\DateTimeInterface $date): static |
| 404 | | { |
| 405 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($date) { |
| 406 | | $query->andWith( |
| 407 | | self::expression( |
| 408 | | 'CONCAT(`process`.`Datum`, " ", `process`.`Uhrzeit`)' |
| 409 | | ), |
| 410 | | '<', |
| 411 | | $date->format('Y-m-d') . ' 00:00:00' |
| 412 | | ); |
| 413 | | }); |
| 414 | | $this->query->orderBy('appointments__0__date', 'ASC'); |
| 415 | | return $this; |
| 416 | | } |
| 417 | | |
| 418 | | |
| 419 | | |
| 420 | | |
| 421 | | public function addConditionProcessExpiredIPTimeStamp(\DateTimeInterface $expirationDate): static |
| 422 | | { |
| 423 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($expirationDate) { |
| 424 | | $query->andWith('process.IPTimeStamp', '<=', $expirationDate->getTimestamp()); |
| 425 | | }); |
| 426 | | $this->query->orderBy('appointments__0__date', 'ASC'); |
| 427 | | return $this; |
| 428 | | } |
| 429 | | |
| 430 | | |
| 431 | | |
| 432 | | |
| 433 | | public function addConditionProcessReminderInterval(\DateTimeInterface $dateTime): static |
| 434 | | { |
| 435 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($dateTime) { |
| 436 | | $query |
| 437 | | ->andWith('process.Erinnerungszeitpunkt', '<=', $dateTime->getTimestamp()) |
| 438 | | ->andWith('process.Erinnerungszeitpunkt', '>=', $dateTime->modify("-5 Minutes")->getTimestamp()); |
| 439 | | }); |
| 440 | | $this->query->orderBy('reminderTimestamp', 'ASC'); |
| 441 | | return $this; |
| 442 | | } |
| 443 | | |
| 444 | | public function addConditionProcessMailReminder( |
| 445 | | \DateTimeInterface $now, |
| 446 | | \DateTimeInterface $lastRun, |
| 447 | | int $defaultReminderInMinutes |
| 448 | | ): static { |
| 449 | | $this->query |
| 450 | | ->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($now, $lastRun, $defaultReminderInMinutes) { |
| 451 | | $query |
| 452 | | ->andWith( |
| 453 | | self::expression( |
| 454 | | 'CONCAT(`process`.`Datum`, " ", `process`.`Uhrzeit`)' |
| 455 | | ), |
| 456 | | '>', |
| 457 | | $lastRun->format('Y-m-d H:i:s') |
| 458 | | ) |
| 459 | | ->andWith( |
| 460 | | self::expression( |
| 461 | | 'CONCAT(`process`.`Datum`, " ", `process`.`Uhrzeit`)' |
| 462 | | ), |
| 463 | | '>', |
| 464 | | $now->format('Y-m-d H:i:s') |
| 465 | | ) |
| 466 | | ->andWith( |
| 467 | | 'scopemail.send_reminder', |
| 468 | | '=', |
| 469 | | 1 |
| 470 | | ) |
| 471 | | ->andWith( |
| 472 | | 'process.EMail', |
| 473 | | '<>', |
| 474 | | "" |
| 475 | | ) |
| 476 | | ->andWith( |
| 477 | | 'process.EMailverschickt', |
| 478 | | '=', |
| 479 | | 0 |
| 480 | | ) |
| 481 | | ->andWith( |
| 482 | | self::expression( |
| 483 | | 'DATE_SUB(CONCAT(`process`.`Datum`, " ", `process`.`Uhrzeit`), INTERVAL ' |
| 484 | | . 'IFNULL(scopemail.send_reminder_minutes_before, ' . $defaultReminderInMinutes |
| 485 | | . ') MINUTE)' |
| 486 | | ), |
| 487 | | '<=', |
| 488 | | $now->format('Y-m-d H:i:s') |
| 489 | | ); |
| 490 | | }); |
| 491 | | $this->query->orderBy('appointments__0__date', 'ASC'); |
| 492 | | return $this; |
| 493 | | } |
| 494 | | |
| 495 | | public function addConditionProcessId(int $processId): static |
| 496 | | { |
| 497 | | $this->query->where('process.BuergerID', '=', $processId); |
| 498 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $condition) { |
| 499 | | $condition |
| 500 | | ->andWith('process.istFolgeterminvon', 'IS', null) |
| 501 | | ->orWith('process.istFolgeterminvon', '=', 0); |
| 502 | | }); |
| 503 | | return $this; |
| 504 | | } |
| 505 | | |
| 506 | | public function addConditionProcessIdFollow($processId): static |
| 507 | | { |
| 508 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $condition) use ($processId) { |
| 509 | | $condition |
| 510 | | ->andWith('process.BuergerID', '=', $processId) |
| 511 | | ->orWith('process.istFolgeterminvon', '=', $processId); |
| 512 | | }); |
| 513 | | return $this; |
| 514 | | } |
| 515 | | |
| 516 | | |
| 517 | | |
| 518 | | |
| 519 | | public function addConditionIgnoreSlots(): static |
| 520 | | { |
| 521 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $condition) { |
| 522 | | $condition |
| 523 | | ->andWith('process.istFolgeterminvon', 'IS', null) |
| 524 | | ->orWith('process.istFolgeterminvon', '=', 0); |
| 525 | | }); |
| 526 | | return $this; |
| 527 | | } |
| 528 | | |
| 529 | | public function addConditionScopeId(int $scopeId): static |
| 530 | | { |
| 531 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($scopeId) { |
| 532 | | $query |
| 533 | | ->andWith('process.StandortID', '=', $scopeId) |
| 534 | | ->orWith('process.AbholortID', '=', $scopeId); |
| 535 | | }); |
| 536 | | return $this; |
| 537 | | } |
| 538 | | |
| 539 | | public function addConditionScopeIds(array|int $scopeIds) |
| 540 | | { |
| 541 | | if (count($scopeIds) === 0) { |
| 542 | | $this->query->where(self::expression('1'), '=', 0); |
| 543 | | return $this; |
| 544 | | } |
| 545 | | |
| 546 | | if (count($scopeIds) === 1) { |
| 547 | | return $this->addConditionScopeId($scopeIds[0]); |
| 548 | | } |
| 549 | | |
| 550 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($scopeIds) { |
| 551 | | $query |
| 552 | | ->andWith('process.StandortID', 'IN', $scopeIds) |
| 553 | | ->orWith('process.AbholortID', 'IN', $scopeIds); |
| 554 | | }); |
| 555 | | |
| 556 | | return $this; |
| 557 | | } |
| 558 | | |
| 559 | | |
| 560 | | |
| 561 | | |
| 562 | | public function addConditionQueueNumber(int $queueNumber, int $queueLimit = 10000): static |
| 563 | | { |
| 564 | | ($queueLimit > $queueNumber) |
| 565 | | ? $this->query->where('process.wartenummer', '=', $queueNumber) |
| 566 | | : $this->query->where('process.BuergerID', '=', $queueNumber); |
| 567 | | return $this; |
| 568 | | } |
| 569 | | |
| 570 | | public function addConditionWorkstationId($workstationId): static |
| 571 | | { |
| 572 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($workstationId) { |
| 573 | | $query->andWith('process.NutzerID', '=', $workstationId); |
| 574 | | $query->andWith('process.StandortID', '>', 0); |
| 575 | | }); |
| 576 | | return $this; |
| 577 | | } |
| 578 | | |
| 579 | | public function addConditionTime(\DateTimeInterface|null $dateTime): static |
| 580 | | { |
| 581 | | $this->query->where('process.Datum', '=', $dateTime->format('Y-m-d')); |
| 582 | | return $this; |
| 583 | | } |
| 584 | | |
| 585 | | |
| 586 | | |
| 587 | | |
| 588 | | |
| 589 | | |
| 590 | | public function addConditionTimeframe(\DateTimeInterface $startDate, \DateTimeInterface $endDate): static |
| 591 | | { |
| 592 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $condition) use ($startDate, $endDate) { |
| 593 | | $condition |
| 594 | | ->andWith('process.Datum', '<=', $endDate->format('Y-m-d')) |
| 595 | | ->andWith('process.Datum', '>=', $startDate->format('Y-m-d')); |
| 596 | | }); |
| 597 | | return $this; |
| 598 | | } |
| 599 | | |
| 600 | | public function addConditionAuthKey($authKey): static |
| 601 | | { |
| 602 | | $authKey = urldecode($authKey); |
| 603 | | $this->query->where('process.absagecode', '=', $authKey); |
| 604 | | return $this; |
| 605 | | } |
| 606 | | |
| 607 | | |
| 608 | | |
| 609 | | |
| 610 | | public function addConditionAssigned() |
| 611 | | { |
| 612 | | $this->query->where('process.StandortID', '!=', "0"); |
| 613 | | return $this; |
| 614 | | } |
| 615 | | |
| 616 | | public function addConditionStatus(string $status): static |
| 617 | | { |
| 618 | | $this->query->where('process.status', '=', $status); |
| 619 | | return $this; |
| 620 | | } |
| 621 | | |
| 622 | | |
| 623 | | |
| 624 | | |
| 625 | | public function addConditionIsReserved(): static |
| 626 | | { |
| 627 | | $this->query->where('process.name', 'NOT IN', array( |
| 628 | | 'dereferenced', |
| 629 | | '(abgesagt)' |
| 630 | | )) |
| 631 | | ->where('process.vorlaeufigeBuchung', '=', 1) |
| 632 | | ->where('process.StandortID', '>', 0); |
| 633 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $condition) { |
| 634 | | $condition |
| 635 | | ->andWith('process.istFolgeterminvon', 'IS', null) |
| 636 | | ->orWith('process.istFolgeterminvon', '=', 0); |
| 637 | | }); |
| 638 | | return $this; |
| 639 | | } |
| 640 | | |
| 641 | | public function addConditionSearch($queryString, bool $orWhere = false): static |
| 642 | | { |
| 643 | | $queryString = trim((string) $queryString); |
| 644 | | $terms = $this->parseSearchTerms($queryString); |
| 645 | | |
| 646 | | if ($terms === []) { |
| 647 | | return $this; |
| 648 | | } |
| 649 | | |
| 650 | | $condition = function ( |
| 651 | | \BO\Zmsbackend\Query\Builder\ConditionBuilder $query |
| 652 | | ) use ($terms) { |
| 653 | | foreach ($terms as $term) { |
| 654 | | $query->andWith( |
| 655 | | function ( |
| 656 | | \BO\Zmsbackend\Query\Builder\ConditionBuilder $inner |
| 657 | | ) use ($term) { |
| 658 | | $this->appendGeneralSearchTermGroup( |
| 659 | | $inner, |
| 660 | | $term['value'], |
| 661 | | $term['quoted'] |
| 662 | | ); |
| 663 | | } |
| 664 | | ); |
| 665 | | } |
| 666 | | }; |
| 667 | | |
| 668 | | if ($orWhere) { |
| 669 | | $this->query->orWhere($condition); |
| 670 | | } else { |
| 671 | | $this->query->where($condition); |
| 672 | | } |
| 673 | | |
| 674 | | return $this; |
| 675 | | } |
| 676 | | |
| 677 | | |
| 678 | | |
| 679 | | |
| 680 | | public function addConditionProcessIdOrSearch($queryString): static |
| 681 | | { |
| 682 | | $queryString = trim((string) $queryString); |
| 683 | | $processId = (int) $queryString; |
| 684 | | $terms = $this->parseSearchTerms($queryString); |
| 685 | | |
| 686 | | $this->query->where(function ( |
| 687 | | \BO\Zmsbackend\Query\Builder\ConditionBuilder $group |
| 688 | | ) use ( |
| 689 | | $processId, |
| 690 | | $terms |
| 691 | | ) { |
| 692 | | $group->andWith('process.BuergerID', '=', $processId); |
| 693 | | |
| 694 | | if ($terms === []) { |
| 695 | | return; |
| 696 | | } |
| 697 | | |
| 698 | | $group->orWith(function ( |
| 699 | | \BO\Zmsbackend\Query\Builder\ConditionBuilder $search |
| 700 | | ) use ($terms) { |
| 701 | | foreach ($terms as $term) { |
| 702 | | $search->andWith( |
| 703 | | function ( |
| 704 | | \BO\Zmsbackend\Query\Builder\ConditionBuilder $inner |
| 705 | | ) use ($term) { |
| 706 | | $this->appendGeneralSearchTermGroup( |
| 707 | | $inner, |
| 708 | | $term['value'], |
| 709 | | $term['quoted'] |
| 710 | | ); |
| 711 | | } |
| 712 | | ); |
| 713 | | } |
| 714 | | }); |
| 715 | | }); |
| 716 | | |
| 717 | | return $this; |
| 718 | | } |
| 719 | | |
| 720 | | private function appendGeneralSearchTermGroup(\BO\Zmsbackend\Query\Builder\ConditionBuilder $query, string $term, bool $quoted = false): void |
| 721 | | { |
| 722 | | $likeContains = '%' . $this->escapeLikeValue($term) . '%'; |
| 723 | | |
| 724 | | $useNameWordBoundary = !$quoted |
| 725 | | && !$this->isNumericSearchQuery($term) && mb_strlen($term) <= 3; |
| 726 | | |
| 727 | | if ($quoted) { |
| 728 | | $this->appendNamePartLikeGroup( |
| 729 | | $query, |
| 730 | | $term, |
| 731 | | true |
| 732 | | ); |
| 733 | | } elseif ($useNameWordBoundary) { |
| 734 | | $this->appendNamePartLikeGroup($query, $term, true, true); |
| 735 | | } else { |
| 736 | | $query->orWith('process.Name', 'LIKE', $likeContains); |
| 737 | | } |
| 738 | | |
| 739 | | $query->orWith('process.EMail', 'LIKE', $likeContains); |
| 740 | | $query->orWith('process.Telefonnummer', 'LIKE', $likeContains); |
| 741 | | $query->orWith('process.telefonnummer_fuer_rueckfragen', 'LIKE', $likeContains); |
| 742 | | $query->orWith('process.displayNumber', 'LIKE', $likeContains); |
| 743 | | $query->orWith('process.custom_text_field', 'LIKE', $likeContains); |
| 744 | | $query->orWith('process.custom_text_field2', 'LIKE', $likeContains); |
| 745 | | } |
| 746 | | |
| 747 | | public function addConditionUpcomingOnly(\DateTimeInterface $now): static |
| 748 | | { |
| 749 | | $today = $now->format('Y-m-d'); |
| 750 | | $time = $now->format('H:i:s'); |
| 751 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $condition) use ($today, $time) { |
| 752 | | $condition |
| 753 | | ->andWith('process.Datum', '>', $today) |
| 754 | | ->orWith(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $inner) use ($today, $time) { |
| 755 | | $inner |
| 756 | | ->andWith('process.Datum', '=', $today) |
| 757 | | ->andWith('process.Uhrzeit', '>=', $time); |
| 758 | | }); |
| 759 | | }); |
| 760 | | return $this; |
| 761 | | } |
| 762 | | |
| 763 | | |
| 764 | | public function addOrderByAppointmentDate(): static |
| 765 | | { |
| 766 | | $this->query->orderBy('process.Datum', 'ASC'); |
| 767 | | $this->query->orderBy('process.Uhrzeit', 'ASC'); |
| 768 | | $this->query->orderBy('process.BuergerID', 'ASC'); |
| 769 | | return $this; |
| 770 | | } |
| 771 | | |
| 772 | | |
| 773 | | public function addOrderBySearchRelevance(string $queryString): static |
| 774 | | { |
| 775 | | $queryString = trim($queryString); |
| 776 | | if ($queryString === '') { |
| 777 | | return $this; |
| 778 | | } |
| 779 | | |
| 780 | | $terms = $this->parseSearchTerms($queryString); |
| 781 | | if ($terms === []) { |
| 782 | | return $this; |
| 783 | | } |
| 784 | | $fullQuery = $this->escapeLikePatternForSqlLiteral($queryString); |
| 785 | | $fullQueryExact = $this->escapeSqlStringLiteral($queryString); |
| 786 | | |
| 787 | | $allTermsAsWordsConditions = []; |
| 788 | | $allTermsInNameConditions = []; |
| 789 | | $anyTermInNameConditions = []; |
| 790 | | |
| 791 | | foreach ($terms as $term) { |
| 792 | | $escapedTerm = $this->escapeLikePatternForSqlLiteral($term['value']); |
| 793 | | $exactTerm = $this->escapeSqlStringLiteral($term['value']); |
| 794 | | $allTermsAsWordsConditions[] = "(" |
| 795 | | . "process.Name = '{$exactTerm}'" |
| 796 | | . " OR process.Name LIKE '{$escapedTerm} %'" |
| 797 | | . " OR process.Name LIKE '% {$escapedTerm}'" |
| 798 | | . " OR process.Name LIKE '% {$escapedTerm} %'" |
| 799 | | . ")"; |
| 800 | | $allTermsInNameConditions[] = "process.Name LIKE '%{$escapedTerm}%'"; |
| 801 | | $anyTermInNameConditions[] = "process.Name LIKE '%{$escapedTerm}%'"; |
| 802 | | } |
| 803 | | $allTermsAsWords = implode(' AND ', $allTermsAsWordsConditions); |
| 804 | | $allTermsInName = implode(' AND ', $allTermsInNameConditions); |
| 805 | | $anyTermInName = implode(' OR ', $anyTermInNameConditions); |
| 806 | | |
| 807 | | $this->query->orderBy(self::expression( |
| 808 | | "CASE |
| 809 | | WHEN process.Name LIKE '{$fullQueryExact}' THEN 0 |
| 810 | | WHEN process.Name LIKE '{$fullQuery} %' THEN 1 |
| 811 | | WHEN {$allTermsAsWords} THEN 2 |
| 812 | | WHEN {$allTermsInName} THEN 3 |
| 813 | | WHEN {$anyTermInName} THEN 4 |
| 814 | | ELSE 5 |
| 815 | | END" |
| 816 | | )); |
| 817 | | |
| 818 | | $this->query->orderBy('process.Datum', 'ASC'); |
| 819 | | $this->query->orderBy('process.Uhrzeit', 'ASC'); |
| 820 | | $this->query->orderBy('process.BuergerID', 'ASC'); |
| 821 | | |
| 822 | | return $this; |
| 823 | | } |
| 824 | | |
| 825 | | |
| 826 | | |
| 827 | | |
| 828 | | private function parseSearchTerms(string $queryString): array |
| 829 | | { |
| 830 | | if ($queryString === '') { |
| 831 | | return []; |
| 832 | | } |
| 833 | | |
| 834 | | if (!preg_match_all('/"([^"]+)"|(\S+)/u', $queryString, $matches, PREG_SET_ORDER)) { |
| 835 | | return [['value' => $queryString, 'quoted' => false]]; |
| 836 | | } |
| 837 | | |
| 838 | | $terms = []; |
| 839 | | foreach ($matches as $match) { |
| 840 | | $value = trim($match[1] !== '' ? $match[1] : $match[2]); |
| 841 | | if ($value === '') { |
| 842 | | continue; |
| 843 | | } |
| 844 | | $terms[] = [ |
| 845 | | 'value' => $value, |
| 846 | | 'quoted' => $match[1] !== '', |
| 847 | | ]; |
| 848 | | } |
| 849 | | |
| 850 | | return $terms; |
| 851 | | } |
| 852 | | |
| 853 | | private function appendNamePartLikeGroup( |
| 854 | | \BO\Zmsbackend\Query\Builder\ConditionBuilder $query, |
| 855 | | string $term, |
| 856 | | bool $wordBoundaryOnly = false, |
| 857 | | bool $includeWordPrefix = false |
| 858 | | ): void { |
| 859 | | $escaped = $this->escapeLikeValue($term); |
| 860 | | $query->orWith('process.Name', 'LIKE', $escaped . ' %'); |
| 861 | | $query->orWith('process.Name', 'LIKE', '% ' . $escaped); |
| 862 | | $query->orWith('process.Name', 'LIKE', '% ' . $escaped . ' %'); |
| 863 | | $query->orWith('process.Name', '=', $term); |
| 864 | | if ($wordBoundaryOnly) { |
| 865 | | if ($includeWordPrefix) { |
| 866 | | $query->orWith('process.Name', 'LIKE', $escaped . '%'); |
| 867 | | $query->orWith('process.Name', 'LIKE', '% ' . $escaped . '%'); |
| 868 | | } |
| 869 | | } else { |
| 870 | | $query->orWith('process.Name', 'LIKE', '%' . $escaped . '%'); |
| 871 | | } |
| 872 | | } |
| 873 | | |
| 874 | | private function isNumericSearchQuery(string $queryString): bool |
| 875 | | { |
| 876 | | return (bool) preg_match('#^\d+$#', $queryString); |
| 877 | | } |
| 878 | | |
| 879 | | private function escapeLikeValue(string $value): string |
| 880 | | { |
| 881 | | return str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $value); |
| 882 | | } |
| 883 | | |
| 884 | | private function escapeLikePatternForSqlLiteral(string $value): string |
| 885 | | { |
| 886 | | return $this->escapeSqlStringLiteral($this->escapeLikeValue($value)); |
| 887 | | } |
| 888 | | |
| 889 | | private function escapeSqlStringLiteral(string $value): string |
| 890 | | { |
| 891 | | return str_replace("'", "''", $value); |
| 892 | | } |
| 893 | | |
| 894 | | public function addConditionName($name, bool $exactMatching = false): static |
| 895 | | { |
| 896 | | if ($exactMatching) { |
| 897 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($name) { |
| 898 | | $query->andWith('process.Name', '=', $name); |
| 899 | | }); |
| 900 | | } else { |
| 901 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($name) { |
| 902 | | $query->andWith('process.Name', 'LIKE', "%$name%"); |
| 903 | | }); |
| 904 | | } |
| 905 | | return $this; |
| 906 | | } |
| 907 | | |
| 908 | | public function addConditionMail(string $mailAddress, bool $exactMatching = false): static |
| 909 | | { |
| 910 | | if ($exactMatching) { |
| 911 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($mailAddress) { |
| 912 | | $query->andWith('process.Email', '=', $mailAddress); |
| 913 | | }); |
| 914 | | } else { |
| 915 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($mailAddress) { |
| 916 | | $query->andWith('process.Email', 'LIKE', "%$mailAddress%"); |
| 917 | | }); |
| 918 | | } |
| 919 | | return $this; |
| 920 | | } |
| 921 | | |
| 922 | | |
| 923 | | |
| 924 | | |
| 925 | | public function addConditionCustomTextfield($customText, bool $exactMatching = false): static |
| 926 | | { |
| 927 | | if ($exactMatching) { |
| 928 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($customText) { |
| 929 | | $query->andWith('process.custom_text_field', '=', $customText); |
| 930 | | }); |
| 931 | | } else { |
| 932 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($customText) { |
| 933 | | $query->andWith('process.custom_text_field', 'LIKE', "%$customText%"); |
| 934 | | }); |
| 935 | | } |
| 936 | | return $this; |
| 937 | | } |
| 938 | | |
| 939 | | |
| 940 | | |
| 941 | | |
| 942 | | public function addConditionCustomTextfield2($customText2, bool $exactMatching = false): static |
| 943 | | { |
| 944 | | if ($exactMatching) { |
| 945 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($customText2) { |
| 946 | | $query->andWith('process.custom_text_field2', '=', $customText2); |
| 947 | | }); |
| 948 | | } else { |
| 949 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($customText2) { |
| 950 | | $query->andWith('process.custom_text_field2', 'LIKE', "%$customText2%"); |
| 951 | | }); |
| 952 | | } |
| 953 | | return $this; |
| 954 | | } |
| 955 | | |
| 956 | | public function addConditionAmendment($amendment): static |
| 957 | | { |
| 958 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($amendment) { |
| 959 | | $query->andWith('process.Anmerkung', 'LIKE', "%$amendment%"); |
| 960 | | }); |
| 961 | | return $this; |
| 962 | | } |
| 963 | | |
| 964 | | |
| 965 | | |
| 966 | | |
| 967 | | public function addConditionRequestId($requestId): static |
| 968 | | { |
| 969 | | $this->leftJoin( |
| 970 | | new \BO\Zmsbackend\Query\Alias("buergeranliegen", 'buergeranliegen'), |
| 971 | | 'buergeranliegen.BuergerID', |
| 972 | | '=', |
| 973 | | 'process.BuergerID' |
| 974 | | ); |
| 975 | | $this->query->where('buergeranliegen.AnliegenID', '=', $requestId); |
| 976 | | return $this; |
| 977 | | } |
| 978 | | |
| 979 | | public function addConditionScopeNameSearch(string $scopeName): static |
| 980 | | { |
| 981 | | $likeValue = '%' . $this->escapeLikeValue($scopeName) . '%'; |
| 982 | | $this->leftJoin( |
| 983 | | new \BO\Zmsbackend\Query\Alias(\BO\Zmsbackend\Query\Scope::TABLE, 'search_scope'), |
| 984 | | self::expression( |
| 985 | | 'IF(`process`.`AbholortID`, `process`.`AbholortID`, `process`.`StandortID`)' |
| 986 | | ), |
| 987 | | '=', |
| 988 | | 'search_scope.StandortID' |
| 989 | | ); |
| 990 | | $this->leftJoin( |
| 991 | | new \BO\Zmsbackend\Query\Alias(\BO\Zmsbackend\Provider\Repository\Provider::TABLE, 'search_scope_provider'), |
| 992 | | self::expression( |
| 993 | | 'search_scope.InfoDienstleisterID = search_scope_provider.id |
| 994 | | AND search_scope.source = search_scope_provider.source' |
| 995 | | ) |
| 996 | | ); |
| 997 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($likeValue) { |
| 998 | | $query->orWith('search_scope.Bezeichnung', 'LIKE', $likeValue); |
| 999 | | $query->orWith('search_scope.standortinfozeile', 'LIKE', $likeValue); |
| 1000 | | $query->orWith('search_scope.standortkuerzel', 'LIKE', $likeValue); |
| 1001 | | $query->orWith('search_scope_provider.name', 'LIKE', $likeValue); |
| 1002 | | }); |
| 1003 | | |
| 1004 | | return $this; |
| 1005 | | } |
| 1006 | | |
| 1007 | | public function addConditionServiceNameSearch(string $serviceName): static |
| 1008 | | { |
| 1009 | | $likeValue = '%' . $this->escapeLikeValue($serviceName) . '%'; |
| 1010 | | $this->leftJoin( |
| 1011 | | new \BO\Zmsbackend\Query\Alias('buergeranliegen', 'search_request_link'), |
| 1012 | | 'search_request_link.BuergerID', |
| 1013 | | '=', |
| 1014 | | 'process.BuergerID' |
| 1015 | | ); |
| 1016 | | $this->leftJoin( |
| 1017 | | new \BO\Zmsbackend\Query\Alias(\BO\Zmsbackend\Request\Repository\Request::TABLE, 'search_request'), |
| 1018 | | self::expression( |
| 1019 | | 'search_request_link.AnliegenID = search_request.id |
| 1020 | | AND search_request_link.source = search_request.source' |
| 1021 | | ) |
| 1022 | | ); |
| 1023 | | $this->query->where('search_request.name', 'LIKE', $likeValue); |
| 1024 | | |
| 1025 | | return $this; |
| 1026 | | } |
| 1027 | | |
| 1028 | | |
| 1029 | | |
| 1030 | | |
| 1031 | | public function addConditionDeallocate(\DateTimeInterface $now): static |
| 1032 | | { |
| 1033 | | $this->query->where(function (\BO\Zmsbackend\Query\Builder\ConditionBuilder $query) use ($now) { |
| 1034 | | $query |
| 1035 | | ->andWith('process.Name', '=', '(abgesagt)') |
| 1036 | | ->andWith('process.IPTimeStamp', '<', $now->getTimestamp()); |
| 1037 | | }); |
| 1038 | | $this->query->orderBy('process.IPTimeStamp', 'ASC'); |
| 1039 | | return $this; |
| 1040 | | } |
| 1041 | | |
| 1042 | | public function addValuesNewProcess(ProcessEntity $process, $parentProcess = 0, int $childProcessCount = 0): void |
| 1043 | | { |
| 1044 | | $values = [ |
| 1045 | | 'BuergerID' => $process->id, |
| 1046 | | 'IPTimeStamp' => $process->createTimestamp, |
| 1047 | | 'absagecode' => $process->authKey, |
| 1048 | | 'hatFolgetermine' => $childProcessCount, |
| 1049 | | 'istFolgeterminvon' => $parentProcess, |
| 1050 | | 'displayNumber' => $parentProcess === 0 && empty($process->queue['number']) |
| 1051 | | ? $this->getNewDisplayNumber($process) |
| 1052 | | : null, |
| 1053 | | 'wartenummer' => $process->queue['number'] |
| 1054 | | ]; |
| 1055 | | if ($process->toProperty()->apiclient->apiClientID->isAvailable()) { |
| 1056 | | $values['apiClientID'] = $process->apiclient->apiClientID; |
| 1057 | | } |
| 1058 | | if (isset($process->isTicketprinter) && $process->isTicketprinter) { |
| 1059 | | $values['is_ticketprinter'] = 1; |
| 1060 | | } |
| 1061 | | $this->addValues($values); |
| 1062 | | } |
| 1063 | | |
| 1064 | | public function getNewDisplayNumber(ProcessEntity $process) |
| 1065 | | { |
| 1066 | | if (empty($process->scope->getPreference('queue', 'displayNumberPrefix'))) { |
| 1067 | | return $process->id; |
| 1068 | | } |
| 1069 | | |
| 1070 | | $newDisplayNumber = $process->scope->getPreference('queue', 'displayNumberPrefix') . str_pad( |
| 1071 | | (new ScopeEntity())->readDisplayNumberUpdated($process->scope->id), |
| 1072 | | 4, |
| 1073 | | '0', |
| 1074 | | STR_PAD_LEFT |
| 1075 | | ); |
| 1076 | | |
| 1077 | | if ( |
| 1078 | | $this->checkIfDisplayNumberOnSameDateExists( |
| 1079 | | $process->scope->id, |
| 1080 | | $newDisplayNumber, |
| 1081 | | \DateTime::createFromFormat('U', (int) $process->getFirstAppointment()->date) |
| 1082 | | ) |
| 1083 | | ) { |
| 1084 | | return $this->getNewDisplayNumber($process); |
| 1085 | | } |
| 1086 | | |
| 1087 | | return $newDisplayNumber; |
| 1088 | | } |
| 1089 | | |
| 1090 | | public function checkIfDisplayNumberOnSameDateExists($scopeId, string $displayNumber, \DateTime|false $date): bool |
| 1091 | | { |
| 1092 | | $processWithDisplayNumber = (new \BO\Zmsbackend\Process\Service\Process())->readProcessWithSameDayAndDisplayNumber( |
| 1093 | | $scopeId, |
| 1094 | | $displayNumber, |
| 1095 | | $date->format('Y-m-d') |
| 1096 | | ); |
| 1097 | | |
| 1098 | | return !empty($processWithDisplayNumber->getId()); |
| 1099 | | } |
| 1100 | | |
| 1101 | | public function addValuesUpdateProcess( |
| 1102 | | ProcessEntity $process, |
| 1103 | | \DateTimeInterface $dateTime, |
| 1104 | | int $parentProcess = 0, |
| 1105 | | $previousStatus = null |
| 1106 | | ): void { |
| 1107 | | $this->addValuesIPAdress($process); |
| 1108 | | if (0 === $parentProcess) { |
| 1109 | | $this->addValuesClientData($process); |
| 1110 | | $this->addProcessingTimeData($process, $dateTime, $previousStatus); |
| 1111 | | $this->addValuesQueueData($process); |
| 1112 | | $this->addValuesWaitingTimeData($process, $previousStatus); |
| 1113 | | $this->addValuesWayTimeData($process); |
| 1114 | | } |
| 1115 | | if ($process->isWithAppointment()) { |
| 1116 | | $this->addValuesFollowingProcessData($process, $parentProcess); |
| 1117 | | } |
| 1118 | | $this->addValuesWasMissed($process); |
| 1119 | | $this->addValuesPriority($process); |
| 1120 | | $this->addValuesStatusData($process, $dateTime); |
| 1121 | | $this->addValuesExternalUserId($process); |
| 1122 | | } |
| 1123 | | |
| 1124 | | public function addValuesIPAdress(ProcessEntity $process): void |
| 1125 | | { |
| 1126 | | $data = array(); |
| 1127 | | $data['IPAdresse'] = $process->createIP; |
| 1128 | | $this->addValues($data); |
| 1129 | | } |
| 1130 | | |
| 1131 | | public function addValuesFollowingProcessData(ProcessEntity $process, $parentProcess): void |
| 1132 | | { |
| 1133 | | $data = array(); |
| 1134 | | if (0 === $parentProcess) { |
| 1135 | | $data['hatFolgetermine'] = (1 <= $process->getFirstAppointment()->getSlotCount()) ? |
| 1136 | | $process->getFirstAppointment()->getSlotCount() - 1 : |
| 1137 | | 0; |
| 1138 | | } else { |
| 1139 | | $data['Name'] = '(Folgetermin)'; |
| 1140 | | } |
| 1141 | | $this->addValues($data); |
| 1142 | | } |
| 1143 | | |
| 1144 | | public function addValuesAppointmentData( |
| 1145 | | ProcessEntity $process |
| 1146 | | ): void { |
| 1147 | | $data = array(); |
| 1148 | | $appointment = $process->getFirstAppointment(); |
| 1149 | | $datetime = $appointment->toDateTime(); |
| 1150 | | $data['Datum'] = $datetime->format('Y-m-d'); |
| 1151 | | $data['Uhrzeit'] = $datetime->format('H:i:s'); |
| 1152 | | $this->addValues($data); |
| 1153 | | } |
| 1154 | | |
| 1155 | | public function addValuesScopeData( |
| 1156 | | ProcessEntity $process |
| 1157 | | ): void { |
| 1158 | | $data = array(); |
| 1159 | | $data['StandortID'] = $process->getScopeId(); |
| 1160 | | $this->addValues($data); |
| 1161 | | } |
| 1162 | | |
| 1163 | | public function addValuesStatusData(ProcessEntity $process, \DateTimeInterface $dateTime): void |
| 1164 | | { |
| 1165 | | $data = array(); |
| 1166 | | $data['vorlaeufigeBuchung'] = ($process->status == 'reserved') ? 1 : 0; |
| 1167 | | $data['aufruferfolgreich'] = ($process->status == 'processing') ? 1 : 0; |
| 1168 | | if ($process->status == 'called') { |
| 1169 | | $data['parked'] = 0; |
| 1170 | | $data['nicht_erschienen'] = 0; |
| 1171 | | } |
| 1172 | | if ($process->status == 'pending') { |
| 1173 | | $data['AbholortID'] = $process->scope->id; |
| 1174 | | $data['Abholer'] = 1; |
| 1175 | | $data['nicht_erschienen'] = 0; |
| 1176 | | $data['parked'] = 0; |
| 1177 | | } |
| 1178 | | if ($process->status == 'queued') { |
| 1179 | | $data['nicht_erschienen'] = 0; |
| 1180 | | $data['parked'] = 0; |
| 1181 | | if ( |
| 1182 | | $process->hasArrivalTime() && |
| 1183 | | (isset($process->queue->withAppointment) && $process->queue->withAppointment) |
| 1184 | | ) { |
| 1185 | | $data['wsm_aufnahmezeit'] = $dateTime->format('H:i:s'); |
| 1186 | | } |
| 1187 | | } |
| 1188 | | if ($process->status == 'missed') { |
| 1189 | | $data['nicht_erschienen'] = 1; |
| 1190 | | } |
| 1191 | | if ($process->status == 'parked') { |
| 1192 | | $data['parked'] = 1; |
| 1193 | | } |
| 1194 | | if ($process->status == 'confirmed') { |
| 1195 | | $data['bestaetigt'] = 1; |
| 1196 | | } |
| 1197 | | if ($process->status == 'preconfirmed') { |
| 1198 | | $data['bestaetigt'] = 0; |
| 1199 | | } |
| 1200 | | $data['status'] = $process->status; |
| 1201 | | $data['parkedBy'] = $process->getParkedBy(); |
| 1202 | | |
| 1203 | | $this->addValues($data); |
| 1204 | | } |
| 1205 | | |
| 1206 | | protected function addValuesClientData(ProcessEntity $process): void |
| 1207 | | { |
| 1208 | | $data = array(); |
| 1209 | | $client = $process->getFirstClient(); |
| 1210 | | if ($client && $client->hasFamilyName()) { |
| 1211 | | $data['Name'] = $client->familyName; |
| 1212 | | } |
| 1213 | | if ($client && $client->hasEmail()) { |
| 1214 | | $data['EMail'] = $client->email; |
| 1215 | | } |
| 1216 | | if ($client && $client->offsetExists('telephone')) { |
| 1217 | | $data['telefonnummer_fuer_rueckfragen'] = $client->telephone; |
| 1218 | | $data['Telefonnummer'] = $client->telephone; |
| 1219 | | } |
| 1220 | | if ($client && $client->offsetExists('emailSendCount')) { |
| 1221 | | $data['EMailverschickt'] = ('-1' == $client->emailSendCount) ? 0 : $client->emailSendCount; |
| 1222 | | } |
| 1223 | | $data['Anmerkung'] = $process->getAmendment(); |
| 1224 | | $data['custom_text_field'] = $process->getCustomTextfield(); |
| 1225 | | $data['custom_text_field2'] = $process->getCustomTextfield2(); |
| 1226 | | $data['zustimmung_kundenbefragung'] = ($client->surveyAccepted) ? 1 : 0; |
| 1227 | | $data['Erinnerungszeitpunkt'] = $process->getReminderTimestamp(); |
| 1228 | | $data['AnzahlPersonen'] = $process->getClients()->count(); |
| 1229 | | $this->addValues($data); |
| 1230 | | } |
| 1231 | | |
| 1232 | | public function addValueDisplayNumber(ProcessEntity $process): void |
| 1233 | | { |
| 1234 | | $data['displayNumber'] = $process->displayNumber; |
| 1235 | | $this->addValues($data); |
| 1236 | | } |
| 1237 | | |
| 1238 | | protected function addProcessingTimeData(ProcessEntity $process, \DateTimeInterface $dateTime, $previousStatus = null): void |
| 1239 | | { |
| 1240 | | $data = array(); |
| 1241 | | $timeoutTime = null; |
| 1242 | | $finishTime = null; |
| 1243 | | |
| 1244 | | if ( |
| 1245 | | isset($previousStatus) && |
| 1246 | | (($process->status == 'called' && $previousStatus == 'called') || |
| 1247 | | ($process->status == 'processing' && $previousStatus == 'processing')) |
| 1248 | | ) { |
| 1249 | | $timeoutTime = $dateTime->format('Y-m-d H:i:s'); |
| 1250 | | $data['timeoutTime'] = $timeoutTime; |
| 1251 | | } elseif ($process->status == 'processing' && ($process->showUpTime === null || $process->showUpTime === '')) { |
| 1252 | | |
| 1253 | | $showUpTime = $dateTime->format('Y-m-d H:i:s'); |
| 1254 | | $data['showUpTime'] = $showUpTime; |
| 1255 | | } elseif ($process->status == 'finished') { |
| 1256 | | $finishTime = $dateTime->format('Y-m-d H:i:s'); |
| 1257 | | $data['finishTime'] = $finishTime; |
| 1258 | | } elseif ( |
| 1259 | | isset($previousStatus) |
| 1260 | | && in_array($previousStatus, ['called', 'processing'], true) |
| 1261 | | && in_array($process->status, ['queued', 'parked'], true) |
| 1262 | | ) { |
| 1263 | | |
| 1264 | | |
| 1265 | | $data['showUpTime'] = null; |
| 1266 | | $data['timeoutTime'] = null; |
| 1267 | | } |
| 1268 | | |
| 1269 | | |
| 1270 | | if (isset($finishTime) && isset($process->showUpTime)) { |
| 1271 | | $showUpDateTime = new \DateTime($process->showUpTime); |
| 1272 | | $finishTime = new \DateTime($finishTime); |
| 1273 | | |
| 1274 | | $processingTimeStr = $process->getProcessingTime(); |
| 1275 | | $previousProcessingTimeInSeconds = 0; |
| 1276 | | |
| 1277 | | if (!empty($processingTimeStr)) { |
| 1278 | | |
| 1279 | | list($hours, $minutes, $seconds) = explode(':', $processingTimeStr); |
| 1280 | | |
| 1281 | | $previousProcessingTimeInSeconds = (int)$hours * 3600 + (int)$minutes * 60 + (int)$seconds; |
| 1282 | | } |
| 1283 | | |
| 1284 | | $interval = $showUpDateTime->diff($finishTime); |
| 1285 | | $totalSeconds = ($interval->days * 24 * 60 * 60) + ($interval->h * 60 * 60) + ($interval->i * 60) + $interval->s; |
| 1286 | | |
| 1287 | | $totalSeconds += $previousProcessingTimeInSeconds; |
| 1288 | | |
| 1289 | | $hours = intdiv($totalSeconds, 3600); |
| 1290 | | $minutes = intdiv($totalSeconds % 3600, 60); |
| 1291 | | $seconds = $totalSeconds % 60; |
| 1292 | | |
| 1293 | | $data['processing_time'] = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds); |
| 1294 | | } elseif (isset($timeoutTime) && isset($process->showUpTime)) { |
| 1295 | | $showUpDateTime = new \DateTime($process->showUpTime); |
| 1296 | | $timeoutDateTime = new \DateTime($timeoutTime); |
| 1297 | | $processingTimeStr = $process->getProcessingTime(); |
| 1298 | | |
| 1299 | | $previousProcessingTimeInSeconds = 0; |
| 1300 | | if (!empty($processingTimeStr)) { |
| 1301 | | |
| 1302 | | list($hours, $minutes, $seconds) = explode(':', $processingTimeStr); |
| 1303 | | |
| 1304 | | $previousProcessingTimeInSeconds = (int)$hours * 3600 + (int)$minutes * 60 + (int)$seconds; |
| 1305 | | } |
| 1306 | | $interval = $showUpDateTime->diff($timeoutDateTime); |
| 1307 | | $totalSeconds = ($interval->days * 24 * 60 * 60) + ($interval->h * 60 * 60) + ($interval->i * 60) + $interval->s; |
| 1308 | | |
| 1309 | | $totalSeconds += $previousProcessingTimeInSeconds; |
| 1310 | | |
| 1311 | | $hours = intdiv($totalSeconds, 3600); |
| 1312 | | $minutes = intdiv($totalSeconds % 3600, 60); |
| 1313 | | $seconds = $totalSeconds % 60; |
| 1314 | | |
| 1315 | | $data['processing_time'] = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds); |
| 1316 | | } |
| 1317 | | |
| 1318 | | $this->addValues($data); |
| 1319 | | } |
| 1320 | | |
| 1321 | | protected function addValuesQueueData(ProcessEntity $process): void |
| 1322 | | { |
| 1323 | | $data = array(); |
| 1324 | | $appointmentTime = $process->getFirstAppointment()->toDateTime()->format('H:i:s'); |
| 1325 | | |
| 1326 | | if (isset($process->queue['callCount'])) { |
| 1327 | | $data['AnzahlAufrufe'] = (int) $process->queue['callCount']; |
| 1328 | | } |
| 1329 | | if (isset($process->queue['callTime']) && $process->queue['callTime']) { |
| 1330 | | $data['aufrufzeit'] = (new \DateTimeImmutable()) |
| 1331 | | ->setTimestamp($process->queue['callTime'])->format('H:i:s'); |
| 1332 | | } |
| 1333 | | if (isset($process->queue['lastCallTime']) && $process->queue['lastCallTime']) { |
| 1334 | | $data['Timestamp'] = (new \DateTimeImmutable()) |
| 1335 | | ->setTimestamp($process->queue['lastCallTime'])->format('H:i:s'); |
| 1336 | | } |
| 1337 | | if (isset($process->queue['arrivalTime']) && $process->queue['arrivalTime']) { |
| 1338 | | $data['wsm_aufnahmezeit'] = (new \DateTimeImmutable()) |
| 1339 | | ->setTimestamp($process->queue['arrivalTime'])->format('H:i:s'); |
| 1340 | | } |
| 1341 | | if (isset($data['wsm_aufnahmezeit']) && $data['wsm_aufnahmezeit'] == $appointmentTime) { |
| 1342 | | |
| 1343 | | $data['wsm_aufnahmezeit'] = 0; |
| 1344 | | } |
| 1345 | | $this->addValues($data); |
| 1346 | | } |
| 1347 | | |
| 1348 | | protected function addValuesWaitingTimeData(ProcessEntity $process, $previousStatus = null): void |
| 1349 | | { |
| 1350 | | $data = array(); |
| 1351 | | $hasWaitingTimeAlready = ( |
| 1352 | | isset($process->queue->waitingTime) |
| 1353 | | && $process->queue->waitingTime |
| 1354 | | && $process->queue->waitingTime !== '00:00:00' |
| 1355 | | ); |
| 1356 | | |
| 1357 | | if ( |
| 1358 | | !$hasWaitingTimeAlready |
| 1359 | | && ( |
| 1360 | | ( |
| 1361 | | |
| 1362 | | in_array($previousStatus, ['queued', 'missed', 'confirmed']) |
| 1363 | | && $process->status == 'called' |
| 1364 | | && ( |
| 1365 | | ( |
| 1366 | | isset($process->queue->callCount) |
| 1367 | | && $process->queue->callCount <= 0 |
| 1368 | | ) |
| 1369 | | || ( |
| 1370 | | ($previousStatus === 'queued' || $previousStatus === 'confirmed') |
| 1371 | | && ( |
| 1372 | | !isset($process->queue->callTime) |
| 1373 | | || (int) $process->queue->callTime === 0 |
| 1374 | | ) |
| 1375 | | ) |
| 1376 | | ) |
| 1377 | | ) |
| 1378 | | || |
| 1379 | | ( |
| 1380 | | |
| 1381 | | |
| 1382 | | $previousStatus == 'missed' |
| 1383 | | && $process->status == 'queued' |
| 1384 | | && !empty($process->wasMissed) |
| 1385 | | && isset($process->queue) |
| 1386 | | && isset($process->queue->waitingTime) |
| 1387 | | ) |
| 1388 | | ) |
| 1389 | | ) { |
| 1390 | | $wartezeitInSeconds = $process->getWaitedSeconds(); |
| 1391 | | $wartezeitInSeconds = $wartezeitInSeconds > 0 ? $wartezeitInSeconds : 0; |
| 1392 | | |
| 1393 | | |
| 1394 | | $hours = intdiv($wartezeitInSeconds, 3600); |
| 1395 | | $minutes = intdiv($wartezeitInSeconds % 3600, 60); |
| 1396 | | $seconds = $wartezeitInSeconds % 60; |
| 1397 | | |
| 1398 | | $data['waiting_time'] = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds); |
| 1399 | | } |
| 1400 | | |
| 1401 | | $this->addValues($data); |
| 1402 | | } |
| 1403 | | |
| 1404 | | |
| 1405 | | protected function addValuesWayTimeData(ProcessEntity $process): void |
| 1406 | | { |
| 1407 | | $data = array(); |
| 1408 | | if ($process->status == 'processing') { |
| 1409 | | $wayTimeInSeconds = max(0, (int) $process->getWaySeconds()); |
| 1410 | | $hours = intdiv($wayTimeInSeconds, 3600); |
| 1411 | | $minutes = intdiv($wayTimeInSeconds % 3600, 60); |
| 1412 | | $seconds = $wayTimeInSeconds % 60; |
| 1413 | | $data['way_time'] = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds); |
| 1414 | | } |
| 1415 | | $this->addValues($data); |
| 1416 | | } |
| 1417 | | |
| 1418 | | protected function addValuesWasMissed(ProcessEntity $process): static |
| 1419 | | { |
| 1420 | | $data = [ |
| 1421 | | 'wasMissed' => $process->wasMissed ? 1 : 0, |
| 1422 | | ]; |
| 1423 | | |
| 1424 | | $this->addValues($data); |
| 1425 | | return $this; |
| 1426 | | } |
| 1427 | | |
| 1428 | | protected function addValuesPriority(ProcessEntity $process): static |
| 1429 | | { |
| 1430 | | $data = [ |
| 1431 | | 'priority' => $process->priority, |
| 1432 | | ]; |
| 1433 | | |
| 1434 | | $this->addValues($data); |
| 1435 | | return $this; |
| 1436 | | } |
| 1437 | | |
| 1438 | | protected function addValuesExternalUserId(ProcessEntity $process): static |
| 1439 | | { |
| 1440 | | $data = [ |
| 1441 | | 'external_user_id' => $process->externalUserId, |
| 1442 | | ]; |
| 1443 | | |
| 1444 | | $this->addValues($data); |
| 1445 | | return $this; |
| 1446 | | } |
| 1447 | | |
| 1448 | | #[\Override] |
| 1449 | | public function postProcess($data) |
| 1450 | | { |
| 1451 | | $data[$this->getPrefixed("appointments__0__date")] = |
| 1452 | | strtotime($data[$this->getPrefixed("appointments__0__date")]); |
| 1453 | | if ('00:00:00' != $data[$this->getPrefixed("queue__callTime")]) { |
| 1454 | | $time = explode(':', $data[$this->getPrefixed("queue__callTime")]); |
| 1455 | | $data[$this->getPrefixed("queue__callTime")] = (new \DateTimeImmutable()) |
| 1456 | | ->setTimestamp($data[$this->getPrefixed("appointments__0__date")]) |
| 1457 | | ->setTime($time[0], $time[1], $time[2]) |
| 1458 | | ->getTimestamp(); |
| 1459 | | } else { |
| 1460 | | $data[$this->getPrefixed("queue__callTime")] = 0; |
| 1461 | | } |
| 1462 | | if ('00:00:00' != $data[$this->getPrefixed("queue__lastCallTime")]) { |
| 1463 | | $time = explode(':', $data[$this->getPrefixed("queue__lastCallTime")]); |
| 1464 | | $data[$this->getPrefixed("queue__lastCallTime")] = (new \DateTimeImmutable()) |
| 1465 | | ->setTimestamp($data[$this->getPrefixed("appointments__0__date")]) |
| 1466 | | ->setTime($time[0], $time[1], $time[2]) |
| 1467 | | ->getTimestamp(); |
| 1468 | | } else { |
| 1469 | | $data[$this->getPrefixed("queue__lastCallTime")] = 0; |
| 1470 | | } |
| 1471 | | $data[$this->getPrefixed("queue__arrivalTime")] = |
| 1472 | | strtotime($data[$this->getPrefixed("queue__arrivalTime")]); |
| 1473 | | if ( |
| 1474 | | isset($data[$this->getPrefixed('scope__provider__data')]) |
| 1475 | | && $data[$this->getPrefixed('scope__provider__data')] |
| 1476 | | ) { |
| 1477 | | $data[$this->getPrefixed('scope__provider__data')] = |
| 1478 | | json_decode($data[$this->getPrefixed('scope__provider__data')], true); |
| 1479 | | } |
| 1480 | | if (isset($data[$this->getPrefixed('__clientsCount')])) { |
| 1481 | | $clientsCount = $data[$this->getPrefixed('__clientsCount')]; |
| 1482 | | unset($data[$this->getPrefixed('__clientsCount')]); |
| 1483 | | while (--$clientsCount > 0) { |
| 1484 | | $data[$this->getPrefixed('clients__' . $clientsCount . '__familyName')] = 'Unbekannt'; |
| 1485 | | } |
| 1486 | | } |
| 1487 | | $data[$this->getPrefixed("lastChange")] = |
| 1488 | | (new \DateTimeImmutable($data[$this->getPrefixed("lastChange")] . |
| 1489 | | \BO\Zmsbackend\Connection\Select::$connectionTimezone))->getTimestamp(); |
| 1490 | | return $data; |
| 1491 | | } |
| 1492 | | |
| 1493 | | |
| 1494 | | |
| 1495 | | |
| 1496 | | public function removeDuplicates(): static |
| 1497 | | { |
| 1498 | | $this->query->groupBy('process.BuergerID'); |
| 1499 | | return $this; |
| 1500 | | } |
| 1501 | | |
| 1502 | | |
| 1503 | | |
| 1504 | | |
| 1505 | | #[\Override] |
| 1506 | | protected function addRequiredJoins() |
| 1507 | | { |
| 1508 | | if ($this->shouldLoadEntity('processuser')) { |
| 1509 | | $this->leftJoin( |
| 1510 | | new \BO\Zmsbackend\Query\Alias(\BO\Zmsbackend\Useraccount\Repository\Useraccount::TABLE, 'processuser'), |
| 1511 | | 'process.NutzerID', |
| 1512 | | '=', |
| 1513 | | 'processuser.NutzerID' |
| 1514 | | ); |
| 1515 | | } |
| 1516 | | |
| 1517 | | if ($this->shouldLoadEntity('processscope')) { |
| 1518 | | $this->leftJoin( |
| 1519 | | new \BO\Zmsbackend\Query\Alias(\BO\Zmsbackend\Query\Scope::TABLE, 'processscope'), |
| 1520 | | 'process.StandortID', |
| 1521 | | '=', |
| 1522 | | 'processscope.StandortID' |
| 1523 | | ); |
| 1524 | | } |
| 1525 | | } |
| 1526 | | |
| 1527 | | public function addConditionExternalUserId(string $externalUserId): static |
| 1528 | | { |
| 1529 | | $this->query->where('process.external_user_id', '=', $externalUserId); |
| 1530 | | return $this; |
| 1531 | | } |
| 1532 | | } |