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