Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.55% covered (success)
96.55%
168 / 174
93.75% covered (success)
93.75%
15 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
Availability
96.55% covered (success)
96.55%
168 / 174
93.75% covered (success)
93.75%
15 / 16
25
0.00% covered (danger)
0.00%
0 / 1
 addRequiredJoins
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getEntityMapping
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
1 / 1
2
 getReferenceMapping
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 addConditionAvailabilityId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addConditionScopeId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addConditionAppointmentHours
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 addConditionOpeningHours
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 addConditionDoubleTypes
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 addConditionSkipOld
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 addConditionOnlyOld
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 addConditionTimeframe
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 addConditionDate
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 addConditionAppointmentTime
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 reverseEntityMapping
100.00% covered (success)
100.00%
44 / 44
100.00% covered (success)
100.00%
1 / 1
6
 getJoinExpression
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
1
 postProcess
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3namespace BO\Zmsdb\Query;
4
5/**
6 * @SuppressWarnings(Public)
7 */
8class Availability extends Base implements MappingInterface
9{
10    /**
11     * @var String TABLE mysql table reference
12     */
13    const TABLE = 'oeffnungszeit';
14
15    const TEMPORARY_DELETE = 'DELETE FROM oeffnungszeit WHERE kommentar = "--temporary--"';
16
17    const QUERY_GET_LOCK = '
18        SELECT OeffnungszeitID FROM oeffnungszeit WHERE OeffnungszeitID = :availabilityId FOR UPDATE
19    ';
20
21    public function addRequiredJoins()
22    {
23         $this->leftJoin(
24             new Alias(Scope::TABLE, 'availabilityscope'),
25             'availability.StandortID',
26             '=',
27             'availabilityscope.StandortID'
28         );
29    }
30
31    public function getEntityMapping($type = null)
32    {
33        $mapping = [
34            'id' => 'availability.OeffnungszeitID',
35            'scope__id' => 'availability.StandortID',
36            'bookable__startInDays' => self::expression(
37                'CAST(
38                    IF(`availability`.`Offen_ab` = "0" OR `availability`.`Offen_ab`, `availability`.`Offen_ab`, `availabilityscope`.`Termine_ab`)
39                    AS SIGNED)'
40            ),
41            'bookable__endInDays' => self::expression(
42                'IF((`availability`.`Offen_ab` = "0" AND `availability`.`Offen_bis` = "0") OR `availability`.`Offen_bis`, `availability`.`Offen_bis`, `availabilityscope`.`Termine_bis`)'
43            ),
44            'description' => 'availability.kommentar',
45            'startDate' => 'availability.Startdatum',
46            'startTime' => self::expression(
47                'IF(`availability`.`Terminanfangszeit`,`availability`.`Terminanfangszeit`,`availability`.`Anfangszeit`)'
48            ),
49            'endDate' => 'availability.Endedatum',
50            'endTime' => self::expression(
51                'IF(`availability`.`Terminanfangszeit`, `availability`.`Terminendzeit`, `availability`.`Endzeit`)'
52            ),
53            'lastChange' => 'availability.updateTimestamp',
54            'version' => 'availability.version',
55            'multipleSlotsAllowed' => 'availability.erlaubemehrfachslots',
56            'repeat__afterWeeks' => 'availability.allexWochen',
57            'repeat__weekOfMonth' => 'availability.jedexteWoche',
58            'slotTimeInMinutes' => self::expression('FLOOR(TIME_TO_SEC(`availability`.`Timeslot`) / 60)') ,
59            // dependant function on this IF(): \BO\Zmsdb\Availablity::readList()
60            'type' => self::expression(
61                "IF(`availability`.`Terminanfangszeit`, 'appointment', 'openinghours')"
62            ),
63            'weekday__monday' => self::expression('`availability`.`Wochentag` & 2'),
64            'weekday__tuesday' => self::expression('`availability`.`Wochentag` & 4'),
65            'weekday__wednesday' => self::expression('`availability`.`Wochentag` & 8'),
66            'weekday__thursday' => self::expression('`availability`.`Wochentag` & 16'),
67            'weekday__friday' => self::expression('`availability`.`Wochentag` & 32'),
68            'weekday__saturday' => self::expression('`availability`.`Wochentag` & 64'),
69            'weekday__sunday' => self::expression('`availability`.`Wochentag` & 1'),
70            'workstationCount__callcenter' => self::expression(
71                'GREATEST(0, `availability`.`Anzahlterminarbeitsplaetze` - `availability`.`reduktionTermineCallcenter`)'
72            ),
73            'workstationCount__intern' => 'availability.Anzahlterminarbeitsplaetze',
74            'workstationCount__public' => self::expression(
75                'GREATEST(0, `availability`.`Anzahlterminarbeitsplaetze` - `availability`.`reduktionTermineImInternet`)'
76            )
77        ];
78        if ('openinghours' == $type) {
79            // Test if following line is needed: type mapping with IF() a few lines before
80            //$mapping['type'] = self::expression('"openinghours"');
81            $mapping['startTime'] = 'availability.Anfangszeit';
82            $mapping['endTime'] = 'availability.Endzeit';
83        }
84        return $mapping;
85    }
86
87    public function getReferenceMapping()
88    {
89        return [
90            'scope__$ref' => self::expression('CONCAT("/scope/", `availability`.`StandortID`, "/")'),
91        ];
92    }
93
94    public function addConditionAvailabilityId($availabilityId)
95    {
96        $this->query->where('availability.OeffnungszeitID', '=', $availabilityId);
97        return $this;
98    }
99
100    public function addConditionScopeId($scopeId)
101    {
102        $this->query->where('availabilityscope.StandortID', '=', $scopeId);
103        return $this;
104    }
105
106    public function addConditionAppointmentHours()
107    {
108        $this->query
109            ->where('availability.Terminanfangszeit', '!=', '00:00:00')
110            ->where('availability.Terminendzeit', '!=', '00:00:00');
111        return $this;
112    }
113
114    public function addConditionOpeningHours()
115    {
116        $this->query
117            ->where('availability.Anfangszeit', '!=', '00:00:00')
118            ->where('availability.Endzeit', '!=', '00:00:00');
119        return $this;
120    }
121
122    /**
123     * Used to identify old availabilities as appointment and openinghours
124     *
125     */
126    public function addConditionDoubleTypes()
127    {
128        $this->query
129            ->where('availability.Terminanfangszeit', '!=', '00:00:00')
130            ->where('availability.Terminendzeit', '!=', '00:00:00')
131            ->where('availability.Anfangszeit', '!=', '00:00:00')
132            ->where('availability.Endzeit', '!=', '00:00:00');
133        return $this;
134    }
135
136    public function addConditionSkipOld(\DateTimeInterface $dateTime)
137    {
138        $date = $dateTime->format('Y-m-d');
139        $this->query
140            ->where('availability.Endedatum', '>=', $date);
141        return $this;
142    }
143
144    /**
145     * Used to identify availabilities whose End Date was more than 4 weeks ago
146     *
147     */
148    public function addConditionOnlyOld(\DateTimeInterface $dateTime)
149    {
150        $date = $dateTime->format('Y-m-d');
151        $this->query
152            ->where('availability.Endedatum', '<=', $date);
153        return $this;
154    }
155
156   /**
157     * Identify availabilities between two dates
158     *
159     */
160    public function addConditionTimeframe(\DateTimeInterface $startDate, \DateTimeInterface $endDate)
161    {
162        $this->query->where(function (\BO\Zmsdb\Query\Builder\ConditionBuilder $condition) use ($startDate, $endDate) {
163            $condition
164                ->andWith('availability.Startdatum', '<=', $endDate->format('Y-m-d'))
165                ->andWith('availability.Endedatum', '>=', $startDate->format('Y-m-d'));
166        });
167        return $this;
168    }
169
170    public function addConditionDate(\DateTimeInterface $dateTime)
171    {
172        $date = $dateTime->format('Y-m-d');
173        $this->query
174            ->where('availability.Startdatum', '<=', $date)
175            ->where('availability.Endedatum', '>=', $date);
176        //-- match weekday
177        $this->query->where(self::expression("availability.Wochentag & POW(2, DAYOFWEEK('$date') - 1)"), '>=', '1');
178        //-- match week
179        $this->query->where(self::expression("
180            (
181                (
182                    availability.allexWochen
183                    AND FLOOR(
184                        (
185                            FLOOR(UNIX_TIMESTAMP('$date'))
186                            - FLOOR(UNIX_TIMESTAMP(availability.Startdatum)))
187                            / 86400
188                            / 7
189                        )
190                        % availability.allexWochen = 0
191                )
192                OR (
193                    availability.jedexteWoche
194                    AND (
195                        CEIL(DAYOFMONTH('$date') / 7) = availability.jedexteWoche
196                        OR (
197                            availability.jedexteWoche = 5
198                            AND CEIL(LAST_DAY('$date') / 7) = CEIL(DAYOFMONTH('$date') / 7)
199                        )
200                    )
201                )
202                OR (availability.allexWochen = 0 AND availability.jedexteWoche = 0)
203            ) AND 1
204            "), '=', '1');
205        return $this;
206    }
207
208    public function addConditionAppointmentTime(\DateTimeInterface $dateTime)
209    {
210        $time = $dateTime->format('H:i:s');
211        $this->query->where("availability.Terminanfangszeit", '<=', $time);
212        $this->query->where("availability.Terminendzeit", '>', $time);
213
214        return $this;
215    }
216
217    public function reverseEntityMapping(\BO\Zmsentities\Availability $entity)
218    {
219        $data = array();
220        $data['StandortID'] = $entity->scope['id'];
221        $data['Offen_ab'] = $entity->bookable['startInDays'];
222        $data['Offen_bis'] = $entity->bookable['endInDays'];
223        $data['kommentar'] = $entity->description;
224        $data['Startdatum'] = $entity->getStartDateTime()->format('Y-m-d');
225        $data['Endedatum'] = $entity->getEndDateTime()->format('Y-m-d');
226        $data['version'] = $entity->version;
227        if ('openinghours' == $entity->type) {
228            $data['Anfangszeit'] = $entity->startTime;
229            $data['Endzeit'] = $entity->endTime;
230            $data['Terminanfangszeit'] = 0;
231            $data['Terminendzeit'] = 0;
232        } else {
233            $data['Anfangszeit'] = 0;
234            $data['Endzeit'] = 0;
235            $data['Terminanfangszeit'] = $entity->startTime;
236            $data['Terminendzeit'] = $entity->endTime;
237        }
238        $data['allexWochen'] = $entity->repeat['afterWeeks'];
239        $data['jedexteWoche'] = $entity->repeat['weekOfMonth'];
240        $data['Timeslot'] = gmdate("H:i", $entity->slotTimeInMinutes * 60);
241        $data['erlaubemehrfachslots'] = $entity->multipleSlotsAllowed ? 1 : 0;
242        $wochentagBinaryCoded = 0;
243        $binaryCodes = [
244            'sunday' => 1,
245            'monday' => 2,
246            'tuesday' => 4,
247            'wednesday' => 8,
248            'thursday' => 16,
249            'friday' => 32,
250            'saturday' => 64,
251            ];
252        foreach ($entity->weekday as $weekday => $isActive) {
253            if ($isActive) {
254                $wochentagBinaryCoded |= $binaryCodes[$weekday];
255            }
256        }
257        $data['Wochentag'] = $wochentagBinaryCoded;
258        $data['Anzahlterminarbeitsplaetze'] = $entity->workstationCount['intern'];
259        $data['reduktionTermineImInternet'] =
260            $entity->workstationCount['intern'] - $entity->workstationCount['public'];
261        $data['reduktionTermineCallcenter'] =
262            $entity->workstationCount['intern'] - $entity->workstationCount['callcenter'];
263
264        $data = array_filter($data, function ($value) {
265            return ($value !== null && $value !== false);
266        });
267            return $data;
268    }
269
270    public static function getJoinExpression($process, $availability)
271    {
272        // UNIX_TIMESTAMP is relative here, no dependency to TIMEZONE
273        return self::expression("
274            $availability.StandortID = $process.StandortID
275            AND $availability.OeffnungszeitID IS NOT NULL
276
277            -- match weekday
278            AND $availability.Wochentag & POW(2, DAYOFWEEK($process.Datum) - 1)
279
280            -- match week
281            AND (
282                (
283                    $availability.allexWochen
284                    AND FLOOR(
285                        (
286                            FLOOR(UNIX_TIMESTAMP($process.Datum))
287                            - FLOOR(UNIX_TIMESTAMP($availability.Startdatum)))
288                            / 86400
289                            / 7
290                        )
291                        % $availability.allexWochen = 0
292                )
293                OR (
294                    $availability.jedexteWoche
295                    AND (
296                        CEIL(DAYOFMONTH($process.Datum) / 7) = $availability.jedexteWoche
297                        OR (
298                            $availability.jedexteWoche = 5
299                            AND CEIL(LAST_DAY($process.Datum) / 7) = CEIL(DAYOFMONTH($process.Datum) / 7)
300                        )
301                    )
302                )
303                OR (availability.allexWochen = 0 AND availability.jedexteWoche = 0)
304            )
305
306            -- match time and date
307            AND $process.Uhrzeit >= $availability.Terminanfangszeit
308            AND $process.Uhrzeit < $availability.Terminendzeit
309            AND $process.Datum >= $availability.Startdatum
310            AND $process.Datum <= $availability.Endedatum
311            ");
312    }
313
314    public function postProcess($data)
315    {
316        $startDateKey = $this->getPrefixed("startDate");
317        $endDateKey = $this->getPrefixed("endDate");
318        $lastChangeKey = $this->getPrefixed("lastChange");
319        $startDate = $data[$startDateKey] ?? null;
320        $endDate = $data[$endDateKey] ?? null;
321        $lastChange = $data[$lastChangeKey] ?? null;
322        $data[$startDateKey] = $startDate !== null ? (new \DateTime($startDate))->getTimestamp() : null;
323        $data[$endDateKey] = $endDate !== null ? (new \DateTime($endDate))->getTimestamp() : null;
324        $data[$lastChangeKey] = $lastChange !== null ? (new \DateTime($lastChange . \BO\Zmsdb\Connection\Select::$connectionTimezone))->getTimestamp() : null;
325        return $data;
326    }
327}