Code Coverage  | 
      ||||||||||
Lines  | 
       Functions and Methods  | 
       Classes and Traits  | 
      ||||||||
| Total |         | 
       96.51%  | 
       166 / 172  | 
               | 
       93.75%  | 
       15 / 16  | 
       CRAP |         | 
       0.00%  | 
       0 / 1  | 
      
| Availability |         | 
       96.51%  | 
       166 / 172  | 
               | 
       93.75%  | 
       15 / 16  | 
       25 |         | 
       0.00%  | 
       0 / 1  | 
      
| addRequiredJoins |         | 
       100.00%  | 
       6 / 6  | 
               | 
       100.00%  | 
       1 / 1  | 
       1 | |||
| getEntityMapping |         | 
       100.00%  | 
       46 / 46  | 
               | 
       100.00%  | 
       1 / 1  | 
       2 | |||
| getReferenceMapping |         | 
       100.00%  | 
       3 / 3  | 
               | 
       100.00%  | 
       1 / 1  | 
       1 | |||
| addConditionAvailabilityId |         | 
       100.00%  | 
       2 / 2  | 
               | 
       100.00%  | 
       1 / 1  | 
       1 | |||
| addConditionScopeId |         | 
       100.00%  | 
       2 / 2  | 
               | 
       100.00%  | 
       1 / 1  | 
       1 | |||
| addConditionAppointmentHours |         | 
       100.00%  | 
       4 / 4  | 
               | 
       100.00%  | 
       1 / 1  | 
       1 | |||
| addConditionOpeningHours |         | 
       100.00%  | 
       4 / 4  | 
               | 
       100.00%  | 
       1 / 1  | 
       1 | |||
| addConditionDoubleTypes |         | 
       0.00%  | 
       0 / 6  | 
               | 
       0.00%  | 
       0 / 1  | 
       2 | |||
| addConditionSkipOld |         | 
       100.00%  | 
       4 / 4  | 
               | 
       100.00%  | 
       1 / 1  | 
       1 | |||
| addConditionOnlyOld |         | 
       100.00%  | 
       4 / 4  | 
               | 
       100.00%  | 
       1 / 1  | 
       1 | |||
| addConditionTimeframe |         | 
       100.00%  | 
       6 / 6  | 
               | 
       100.00%  | 
       1 / 1  | 
       1 | |||
| addConditionDate |         | 
       100.00%  | 
       11 / 11  | 
               | 
       100.00%  | 
       1 / 1  | 
       1 | |||
| addConditionAppointmentTime |         | 
       100.00%  | 
       4 / 4  | 
               | 
       100.00%  | 
       1 / 1  | 
       1 | |||
| reverseEntityMapping |         | 
       100.00%  | 
       43 / 43  | 
               | 
       100.00%  | 
       1 / 1  | 
       6 | |||
| getJoinExpression |         | 
       100.00%  | 
       17 / 17  | 
               | 
       100.00%  | 
       1 / 1  | 
       1 | |||
| postProcess |         | 
       100.00%  | 
       10 / 10  | 
               | 
       100.00%  | 
       1 / 1  | 
       4 | |||
| 1 | <?php | 
| 2 | |
| 3 | namespace BO\Zmsdb\Query; | 
| 4 | |
| 5 | /** | 
| 6 | * @SuppressWarnings(Public) | 
| 7 | */ | 
| 8 | class 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 | '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 (\BO\Zmsdb\Query\Builder\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 | $startDateKey = $this->getPrefixed("startDate"); | 
| 315 | $endDateKey = $this->getPrefixed("endDate"); | 
| 316 | $lastChangeKey = $this->getPrefixed("lastChange"); | 
| 317 | $startDate = $data[$startDateKey] ?? null; | 
| 318 | $endDate = $data[$endDateKey] ?? null; | 
| 319 | $lastChange = $data[$lastChangeKey] ?? null; | 
| 320 | $data[$startDateKey] = $startDate !== null ? (new \DateTime($startDate))->getTimestamp() : null; | 
| 321 | $data[$endDateKey] = $endDate !== null ? (new \DateTime($endDate))->getTimestamp() : null; | 
| 322 | $data[$lastChangeKey] = $lastChange !== null ? (new \DateTime($lastChange . \BO\Zmsdb\Connection\Select::$connectionTimezone))->getTimestamp() : null; | 
| 323 | return $data; | 
| 324 | } | 
| 325 | } |