Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.99% covered (success)
98.99%
196 / 198
89.47% covered (warning)
89.47%
17 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
Cluster
98.99% covered (success)
98.99%
196 / 198
89.47% covered (warning)
89.47%
17 / 19
67
0.00% covered (danger)
0.00%
0 / 1
 readEntity
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
8
 readResolvedReferences
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 readEntityWithOpenedScopeStatus
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 readList
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 readByScopeId
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 readByDepartmentId
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 readQueueList
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 readOpenedScopeList
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 readEnabledScopeList
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 readScopeWithShortestWaitingTime
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
7
 readWithScopeWorkstationCount
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 writeImageData
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
4.00
 readImageData
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 deleteImage
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 deleteEntity
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 writeEntity
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 updateEntity
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 writeAssignedScopes
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
 removeCache
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
9.01
1<?php
2
3namespace BO\Zmsdb;
4
5use BO\Zmsentities\Cluster as Entity;
6use BO\Zmsentities\Collection\ClusterList as Collection;
7
8/**
9 *
10 * @SuppressWarnings(Public)
11 * @SuppressWarnings(Coupling)
12 * @SuppressWarnings(Complexity)
13 *
14 */
15class Cluster extends Base
16{
17    public function readEntity($itemId, $resolveReferences = 0, $disableCache = false)
18    {
19        $cacheKey = "cluster-$itemId-$resolveReferences";
20
21        $cluster = null;
22        if (!$disableCache && \App::$cache && \App::$cache->has($cacheKey)) {
23            $cluster = \App::$cache->get($cacheKey);
24        }
25
26        if (empty($cluster)) {
27            $query = new Query\Cluster(Query\Base::SELECT);
28            $query
29                ->addEntityMapping()
30                ->addResolvedReferences($resolveReferences)
31                ->addConditionClusterId($itemId);
32            $cluster = $this->fetchOne($query, new Entity());
33            if (! $cluster->hasId()) {
34                return null;
35            }
36
37            if (\App::$cache) {
38                \App::$cache->set($cacheKey, $cluster);
39                if (\App::$log) {
40                    \App::$log->info('Cluster cache set', ['cache_key' => $cacheKey]);
41                }
42            }
43        }
44
45        return $this->readResolvedReferences($cluster, $resolveReferences, $disableCache);
46    }
47
48    #[\Override]
49    public function readResolvedReferences(
50        \BO\Zmsentities\Schema\Entity $entity,
51        $resolveReferences,
52        $disableCache = false
53    ) {
54        $entity['scopes'] = (new Scope())->readByClusterId($entity->id, $resolveReferences, $disableCache);
55
56        return $entity;
57    }
58
59    public function readEntityWithOpenedScopeStatus($itemId, \DateTimeInterface $now, $resolveReferences = 0)
60    {
61        $entity = $this->readEntity($itemId, $resolveReferences);
62        foreach ($entity->scopes as $scope) {
63            $scope->setStatusAvailability('isOpened', (new Scope())->readIsOpened($scope->getId(), $now));
64        }
65        return $entity;
66    }
67
68    public function readList($resolveReferences = 0)
69    {
70        $clusterList = new Collection();
71        $query = new Query\Cluster(Query\Base::SELECT);
72        $query
73            ->addResolvedReferences($resolveReferences)
74            ->addEntityMapping();
75        $result = $this->fetchList($query, new Entity());
76        if (count($result)) {
77            foreach ($result as $entity) {
78                if ($entity instanceof Entity) {
79                    $entity = $this->readResolvedReferences($entity, $resolveReferences);
80                    $clusterList->addEntity($entity);
81                }
82            }
83        }
84        return $clusterList;
85    }
86
87    public function readByScopeId($scopeId, $resolveReferences = 0)
88    {
89        $query = new Query\Cluster(Query\Base::SELECT);
90        $query
91            ->addEntityMapping()
92            ->addResolvedReferences($resolveReferences)
93            ->addConditionScopeId($scopeId);
94        $entity = $this->fetchOne($query, new Entity());
95        if (! $entity->hasId()) {
96            return null;
97        }
98        $entity = $this->readResolvedReferences($entity, $resolveReferences);
99        return $entity;
100    }
101
102    public function readByDepartmentId($departmentId, $resolveReferences = 0, $disableCache = false)
103    {
104        $clusterList = new Collection();
105        $query = new Query\Cluster(Query\Base::SELECT);
106        $query
107            ->addEntityMapping()
108            ->addResolvedReferences($resolveReferences)
109            ->addConditionDepartmentId($departmentId);
110        $result = $this->fetchList($query, new Entity());
111        if (count($result)) {
112            foreach ($result as $entity) {
113                if ($entity instanceof Entity && !$clusterList->hasEntity($entity->id)) {
114                    $entity = $this->readResolvedReferences($entity, $resolveReferences, $disableCache);
115                    $clusterList->addEntity($entity);
116                }
117            }
118        }
119        return $clusterList;
120    }
121
122    public function readQueueList(
123        $clusterId,
124        \DateTimeInterface $dateTime,
125        $resolveReferences = 0,
126        $withEntities = []
127    ) {
128        $cluster = $this->readEntity($clusterId, 1);
129
130        return (new Scope())
131            ->readScopesQueueListWithWaitingTime($cluster->scopes, $dateTime, $resolveReferences, $withEntities)
132            ->withSortedWaitingTime();
133    }
134
135    public function readOpenedScopeList($clusterId, \DateTimeInterface $dateTime)
136    {
137        $scopeList = new \BO\Zmsentities\Collection\ScopeList();
138        $cluster = $this->readEntity($clusterId, 1);
139        if ($cluster && $cluster->toProperty()->scopes->get()) {
140            foreach ($cluster->scopes as $scope) {
141                $availabilityList = (new Availability())->readOpeningHoursListByDate($scope['id'], $dateTime, 2);
142                if ($availabilityList->isOpened($dateTime)) {
143                    $scope->setStatusAvailability('isOpened', true);
144                    $scopeList->addEntity($scope);
145                }
146            }
147        }
148        return $scopeList;
149    }
150
151    public function readEnabledScopeList($clusterId, \DateTimeInterface $dateTime)
152    {
153        $scopeList = new \BO\Zmsentities\Collection\ScopeList();
154        foreach ($this->readOpenedScopeList($clusterId, $dateTime) as $scope) {
155            if ((new Scope())->readIsGivenNumberInContingent($scope['id'])) {
156                $scopeList->addEntity($scope);
157            }
158        }
159        return $scopeList;
160    }
161
162    public function readScopeWithShortestWaitingTime($clusterId, \DateTimeInterface $dateTime)
163    {
164        $scopeList = $this->readOpenedScopeList($clusterId, $dateTime)->getArrayCopy();
165        $nextScope = array_shift($scopeList);
166        $preferedScope = null;
167        $preferedWaitingTime = 0;
168        while ($nextScope) {
169            $scope = (new Scope())->readWithWorkstationCount($nextScope->id, $dateTime);
170            $queueList = (new Scope())->readQueueListWithWaitingTime($scope, $dateTime);
171            $data = $scope->getWaitingTimeFromQueueList($queueList, $dateTime);
172            if (
173                $scope->getCalculatedWorkstationCount() > 0 &&
174                $data &&
175                ($data['waitingTimeEstimate'] <= $preferedWaitingTime || 0 == $preferedWaitingTime)
176            ) {
177                $preferedWaitingTime = $data['waitingTimeEstimate'];
178                $preferedScope = $scope;
179            }
180            $nextScope = array_shift($scopeList);
181        }
182        if (! $preferedScope) {
183            throw new Exception\Cluster\ScopesWithoutWorkstationCount();
184        }
185        return $preferedScope;
186    }
187
188    public function readWithScopeWorkstationCount($clusterId, $dateTime, $resolveReferences = 0)
189    {
190        $scopeQuery = new Scope();
191        $scopeList = new \BO\Zmsentities\Collection\ScopeList();
192        $cluster = $this->readEntity($clusterId, $resolveReferences);
193        if ($cluster->toProperty()->scopes->get()) {
194            foreach ($cluster->scopes as $scope) {
195                $entity = $scopeQuery->readWithWorkstationCount($scope->id, $dateTime, $resolveReferences = 0);
196                if ($entity) {
197                    $scopeList->addEntity($entity);
198                }
199            }
200        }
201        $cluster->scopes = $scopeList;
202        return $cluster;
203    }
204
205    public function writeImageData($clusterId, \BO\Zmsentities\Mimepart $entity)
206    {
207        if ($entity->mime && $entity->content) {
208            $this->deleteImage($clusterId);
209            $extension = $entity->getExtension();
210            if ($extension == 'jpeg') {
211                $extension = 'jpg'; //compatibility ZMS1
212            }
213            $imageName = 'c_' . $clusterId . '_bild.' . $extension;
214            $this->perform(
215                (new Query\Scope(Query\Base::REPLACE))->getQueryWriteImageData(),
216                array(
217                'imagename' => $imageName,
218                'imagedata' => $entity->content
219                )
220            );
221        }
222        $entity->id = $clusterId;
223        return $entity;
224    }
225
226    public function readImageData($clusterId)
227    {
228        $imageName = 'c_' . $clusterId . '_bild';
229        $imageData = new \BO\Zmsentities\Mimepart();
230        $fileData = $this->getReader()->fetchAll(
231            (new Query\Scope(Query\Base::SELECT))->getQueryReadImageData(),
232            ['imagename' => "$imageName%"]
233        );
234        if ($fileData) {
235            $imageData->content = $fileData[0]['imagecontent'];
236            $imageData->mime = pathinfo($fileData[0]['imagename'])['extension'];
237        }
238        return $imageData;
239    }
240
241    public function deleteImage($clusterId)
242    {
243        $imageName = 'c_' . $clusterId . '_bild';
244        $result = $this->perform(
245            (new Query\Scope(Query\Base::DELETE))->getQueryDeleteImage(),
246            array(
247            'imagename' => "$imageName%"
248            )
249        );
250        return $result;
251    }
252
253    public function deleteEntity($itemId)
254    {
255        $result = false;
256        $cluster = $this->readEntity($itemId);
257        $query =  new Query\Cluster(Query\Base::DELETE);
258        $query->addConditionClusterId($itemId);
259        if ($this->deleteItem($query)) {
260            $result = $this->perform(
261                (new Query\Cluster(Query\Base::DELETE))->getQueryDeleteAssignedScopes(),
262                ['clusterId' => $itemId]
263            );
264        }
265
266        $this->removeCache($cluster);
267
268        return $result;
269    }
270
271    public function writeEntity(\BO\Zmsentities\Cluster $entity)
272    {
273        $query = new Query\Cluster(Query\Base::INSERT);
274        $values = $query->reverseEntityMapping($entity);
275        $query->addValues($values);
276        $this->writeItem($query);
277        $lastInsertId = $this->getWriter()->lastInsertId();
278        if ($entity->toProperty()->scopes->isAvailable()) {
279            $this->writeAssignedScopes($lastInsertId, $entity->scopes);
280        }
281
282        $this->removeCache($entity);
283
284        return $this->readEntity($lastInsertId, 1, true);
285    }
286
287    public function updateEntity($clusterId, \BO\Zmsentities\Cluster $entity)
288    {
289        $query = new Query\Cluster(Query\Base::UPDATE);
290        $query->addConditionClusterId($clusterId);
291        $values = $query->reverseEntityMapping($entity);
292        $query->addValues($values);
293        $this->writeItem($query);
294        if ($entity->toProperty()->scopes->isAvailable()) {
295            $this->writeAssignedScopes($clusterId, $entity->scopes);
296        }
297
298        $this->removeCache($entity);
299
300        return $this->readEntity($clusterId, 1, true);
301    }
302
303    protected function writeAssignedScopes($clusterId, $scopeList)
304    {
305        $cluster = $this->readEntity($clusterId);
306        $this->perform(
307            (new Query\Cluster(Query\Base::DELETE))->getQueryDeleteAssignedScopes(),
308            ['clusterId' => $clusterId]
309        );
310        foreach ($scopeList as $scope) {
311            if (0 < $scope['id']) {
312                $this->perform(
313                    (new Query\Cluster(Query\Base::REPLACE))->getQueryWriteAssignedScopes(),
314                    array(
315                    'clusterId' => $clusterId,
316                    'scopeId' => $scope['id']
317                    )
318                );
319            }
320        }
321
322        $this->removeCache($cluster);
323    }
324
325    public function removeCache($cluster)
326    {
327        if (!\App::$cache || !isset($cluster->id)) {
328            return;
329        }
330
331        $invalidatedKeys = [];
332
333        // Invalidate cluster entity cache for all resolveReferences levels (0, 1, 2)
334        for ($resolveReferences = 0; $resolveReferences <= 2; $resolveReferences++) {
335            $key = "cluster-{$cluster->id}-{$resolveReferences}";
336            if (\App::$cache->has($key)) {
337                \App::$cache->delete($key);
338                $invalidatedKeys[] = $key;
339            }
340        }
341
342        // Invalidate scopeReadByClusterId cache for all resolveReferences levels (0, 1, 2)
343        for ($resolveReferences = 0; $resolveReferences <= 2; $resolveReferences++) {
344            $key = "scopeReadByClusterId-{$cluster->id}-{$resolveReferences}";
345            if (\App::$cache->has($key)) {
346                \App::$cache->delete($key);
347                $invalidatedKeys[] = $key;
348            }
349        }
350
351        if (!empty($invalidatedKeys) && \App::$log) {
352            \App::$log->info('Cluster cache invalidated', [
353                'cluster_id' => $cluster->id,
354                'invalidated_keys' => $invalidatedKeys
355            ]);
356        }
357    }
358}