Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.98% covered (success)
97.98%
97 / 99
90.00% covered (success)
90.00%
9 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
CalculateDailyWaitingStatisticByCron
97.98% covered (success)
97.98%
97 / 99
90.00% covered (success)
90.00%
9 / 10
30
0.00% covered (danger)
0.00%
0 / 1
 run
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 fetchArchiveData
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 processArchiveRows
88.89% covered (warning)
88.89%
16 / 18
0.00% covered (danger)
0.00%
0 / 1
7.07
 determineHourAndType
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 initializeStatsIfNeeded
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 saveStatistics
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 ensureStatisticsRow
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
2
 updateStatisticsValues
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
3
 addHourUpdateColumns
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
3
 hourSuffixForStatistic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace BO\Zmsbackend\Helper;
4
5use DateTimeImmutable;
6
7/**
8 * Berechnung der Wartezeiten pro Standort und Stunde.
9 *
10 * Liest archivierte Prozesse für ein Datum, filtert Einträge ohne Wartezeit,
11 * und speichert die Durchschnittswerte in 'wartenrstatistik'.
12 * Bereits vorhandene Zeilen für (Standort, Datum) werden aktualisiert.
13 */
14class CalculateDailyWaitingStatisticByCron extends \BO\Zmsbackend\Base
15{
16    public function run(DateTimeImmutable $day, bool $commit = false): void
17    {
18        \App::$log->info('CalculateDailyWaitingStatisticByCron started', ['date' => $day->format('Y-m-d')]);
19
20        $archiveRows = $this->fetchArchiveData($day);
21        $statsByScopeDate = $this->processArchiveRows($archiveRows);
22        $this->saveStatistics($statsByScopeDate, $commit);
23
24        \App::$log->info('CalculateDailyWaitingStatisticByCron finished', ['date' => $day->format('Y-m-d')]);
25    }
26
27    private function fetchArchiveData(DateTimeImmutable $day): array
28    {
29        $sql = "
30            SELECT
31              StandortID,
32              Datum,
33              `Timestamp`,
34              mitTermin,
35              waiting_time,
36              way_time
37            FROM buergerarchiv
38            WHERE Datum = :theDay
39              AND StandortID > 0
40        ";
41        return $this->getReader()->fetchAll($sql, [
42            'theDay' => $day->format('Y-m-d'),
43        ]);
44    }
45
46    private function processArchiveRows(array $archiveRows): array
47    {
48        $statsByScopeDate = [];
49
50        foreach ($archiveRows as $row) {
51            // Same skip as the former buerger path: no waiting_time => skip count, wait, and way
52            if (empty($row['waiting_time'])) {
53                continue;
54            }
55
56            $scopeId = (int) $row['StandortID'];
57            if ($scopeId <= 0) {
58                continue;
59            }
60
61            [$hour, $type] = $this->determineHourAndType($row);
62            if ($hour < 0 || $hour > 23) {
63                continue;
64            }
65
66            $waitMins = (float) $row['waiting_time'];
67            $wayMins = is_numeric($row['way_time']) ? (float) $row['way_time'] : 0.0;
68
69            $dateStr = $row['Datum'];
70            $this->initializeStatsIfNeeded($statsByScopeDate, $scopeId, $dateStr);
71
72            $statsByScopeDate[$scopeId][$dateStr][$hour][$type]['count'] += 1;
73            $statsByScopeDate[$scopeId][$dateStr][$hour][$type]['sumWait'] += $waitMins;
74            $statsByScopeDate[$scopeId][$dateStr][$hour][$type]['sumWay'] += $wayMins;
75        }
76
77        return $statsByScopeDate;
78    }
79
80    private function determineHourAndType(array $archiveRecord): array
81    {
82        $type = ((int) $archiveRecord['mitTermin'] === 1) ? 'termin' : 'spontan';
83        $parts = explode(':', (string) $archiveRecord['Timestamp']);
84        $hour = isset($parts[0]) && $parts[0] !== '' ? (int) $parts[0] : -1;
85
86        return [$hour, $type];
87    }
88
89    private function initializeStatsIfNeeded(array &$statsByScopeDate, int $scopeId, string $dateStr): void
90    {
91        if (!isset($statsByScopeDate[$scopeId])) {
92            $statsByScopeDate[$scopeId] = [];
93        }
94
95        if (!isset($statsByScopeDate[$scopeId][$dateStr])) {
96            $statsByScopeDate[$scopeId][$dateStr] = [];
97            foreach (range(0, 23) as $h) {
98                $statsByScopeDate[$scopeId][$dateStr][$h] = [
99                    'spontan' => ['count' => 0, 'sumWait' => 0.0, 'sumWay' => 0.0],
100                    'termin'  => ['count' => 0, 'sumWait' => 0.0, 'sumWay' => 0.0],
101                ];
102            }
103        }
104    }
105
106    private function saveStatistics(array $statsByScopeDate, bool $commit): void
107    {
108        foreach ($statsByScopeDate as $scopeId => $dateArray) {
109            foreach ($dateArray as $dateStr => $hoursData) {
110                $this->updateStatisticsValues((int) $scopeId, $dateStr, $hoursData, $commit);
111            }
112        }
113    }
114
115    private function ensureStatisticsRow(int $scopeId, string $dateStr): int
116    {
117        $existingId = $this->fetchValue(
118            'SELECT wartenrstatistikid
119             FROM wartenrstatistik
120             WHERE standortid = :sid
121               AND datum = :d
122             ORDER BY wartenrstatistikid ASC
123             LIMIT 1',
124            [
125                'sid' => $scopeId,
126                'd' => $dateStr,
127            ]
128        );
129
130        if ($existingId) {
131            return (int) $existingId;
132        }
133
134        $this->perform(
135            'INSERT INTO wartenrstatistik (standortid, datum) VALUES (:sid, :d)',
136            [
137                'sid' => $scopeId,
138                'd' => $dateStr,
139            ]
140        );
141
142        return (int) $this->getWriter()->lastInsertId();
143    }
144
145    private function updateStatisticsValues(int $scopeId, string $dateStr, array $hoursData, bool $commit): void
146    {
147        $updateParams = [];
148        $updateCols = [];
149
150        foreach (range(0, 23) as $hour) {
151            $this->addHourUpdateColumns($updateCols, $updateParams, $hour, $hoursData, 'spontan');
152            $this->addHourUpdateColumns($updateCols, $updateParams, $hour, $hoursData, 'termin');
153        }
154
155        if (!$commit) {
156            \App::$log->info('[DRY RUN] update scope statistics', [
157                'scopeId' => $scopeId,
158                'date' => $dateStr,
159            ]);
160            return;
161        }
162
163        $rowId = $this->ensureStatisticsRow($scopeId, $dateStr);
164        $updateParams['id'] = $rowId;
165
166        $sqlUpdate = sprintf(
167            'UPDATE wartenrstatistik
168             SET %s
169             WHERE wartenrstatistikid = :id
170             LIMIT 1',
171            implode(', ', $updateCols)
172        );
173
174        $this->perform($sqlUpdate, $updateParams);
175    }
176
177    private function addHourUpdateColumns(
178        array &$updateCols,
179        array &$updateParams,
180        int $hour,
181        array $hoursData,
182        string $type
183    ): void {
184        $hourSuffix = $this->hourSuffixForStatistic($type);
185        $colWaitCount = sprintf('hour_%02d_waiting_count_%s', $hour, $hourSuffix);
186        $colWaitTime = sprintf('hour_%02d_waiting_time_%s', $hour, $hourSuffix);
187        $colWayTime = sprintf('hour_%02d_way_time_%s', $hour, $hourSuffix);
188
189        $count = $hoursData[$hour][$type]['count'];
190        $avgWait = ($count > 0)
191            ? round($hoursData[$hour][$type]['sumWait'] / $count, 2)
192            : 0.0;
193        $avgWay = ($count > 0)
194            ? round($hoursData[$hour][$type]['sumWay'] / $count, 2)
195            : 0.0;
196
197        $updateCols[] = "`$colWaitCount` = :$colWaitCount";
198        $updateCols[] = "`$colWaitTime` = :$colWaitTime";
199        $updateCols[] = "`$colWayTime` = :$colWayTime";
200
201        $updateParams[$colWaitCount] = $count;
202        $updateParams[$colWaitTime] = $avgWait;
203        $updateParams[$colWayTime] = $avgWay;
204    }
205
206    private function hourSuffixForStatistic(string $type): string
207    {
208        return $type === 'termin' ? 'appointment' : 'spontaneous';
209    }
210}