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