Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
88.00% |
88 / 100 |
|
53.85% |
7 / 13 |
CRAP | |
0.00% |
0 / 1 |
ReportClientService | |
88.00% |
88 / 100 |
|
53.85% |
7 / 13 |
49.66 | |
0.00% |
0 / 1 |
extractSelectedScopes | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
extractDateRange | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
5 | |||
getExchangeClientData | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
getExchangeClientForDateRange | |
71.43% |
10 / 14 |
|
0.00% |
0 / 1 |
6.84 | |||
getExchangeClientForPeriod | |
71.43% |
5 / 7 |
|
0.00% |
0 / 1 |
2.09 | |||
getClientPeriod | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
2.26 | |||
getYearsForDateRange | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
fetchAndCombineDataFromYears | |
95.24% |
20 / 21 |
|
0.00% |
0 / 1 |
6 | |||
filterDataByDateRange | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
createFilteredExchangeClient | |
81.82% |
9 / 11 |
|
0.00% |
0 / 1 |
3.05 | |||
isValidDateFormat | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
prepareDownloadArgs | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
5 | |||
getTotals | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * @package Zmsstatistic |
5 | * @copyright BerlinOnline Stadtportal GmbH & Co. KG |
6 | **/ |
7 | |
8 | namespace BO\Zmsstatistic\Service; |
9 | |
10 | use BO\Zmsentities\Day; |
11 | use DateTime; |
12 | use Exception; |
13 | |
14 | class ReportClientService |
15 | { |
16 | protected $totals = [ |
17 | 'clientscount', |
18 | 'missed', |
19 | 'withappointment', |
20 | 'missedwithappointment', |
21 | 'noappointment', |
22 | 'missednoappointment', |
23 | 'requestscount' |
24 | ]; |
25 | |
26 | /** |
27 | * Extract selected scope IDs from request parameters |
28 | */ |
29 | public function extractSelectedScopes(array $scopes): array |
30 | { |
31 | if (!empty($scopes)) { |
32 | $validScopes = array_filter($scopes, function ($scopeId) { |
33 | return is_numeric($scopeId) && $scopeId > 0; |
34 | }); |
35 | |
36 | if (!empty($validScopes)) { |
37 | return array_map('intval', $validScopes); |
38 | } |
39 | } |
40 | |
41 | return []; |
42 | } |
43 | |
44 | /** |
45 | * Extract and validate date range from request parameters |
46 | */ |
47 | public function extractDateRange(?string $fromDate, ?string $toDate): ?array |
48 | { |
49 | if ($fromDate && $toDate && $this->isValidDateFormat($fromDate) && $this->isValidDateFormat($toDate)) { |
50 | return [ |
51 | 'from' => $fromDate, |
52 | 'to' => $toDate |
53 | ]; |
54 | } |
55 | |
56 | return null; |
57 | } |
58 | |
59 | /** |
60 | * Get exchange client data based on date range or period |
61 | */ |
62 | public function getExchangeClientData(string $scopeId, ?array $dateRange, array $args): mixed |
63 | { |
64 | if ($dateRange) { |
65 | return $this->getExchangeClientForDateRange($scopeId, $dateRange); |
66 | } elseif (isset($args['period'])) { |
67 | return $this->getExchangeClientForPeriod($scopeId, $args['period']); |
68 | } |
69 | |
70 | return null; |
71 | } |
72 | |
73 | /** |
74 | * Get exchange client data for a specific date range |
75 | */ |
76 | public function getExchangeClientForDateRange(string $scopeId, array $dateRange): mixed |
77 | { |
78 | if (!isset($dateRange['from']) || !isset($dateRange['to'])) { |
79 | return null; |
80 | } |
81 | $fromDate = $dateRange['from']; |
82 | $toDate = $dateRange['to']; |
83 | |
84 | try { |
85 | $years = $this->getYearsForDateRange($fromDate, $toDate); |
86 | $combinedData = $this->fetchAndCombineDataFromYears($scopeId, $years); |
87 | |
88 | if (empty($combinedData['data'])) { |
89 | return null; |
90 | } |
91 | |
92 | $filteredData = $this->filterDataByDateRange($combinedData['data'], $fromDate, $toDate); |
93 | |
94 | if (empty($filteredData)) { |
95 | return null; |
96 | } |
97 | |
98 | return $this->createFilteredExchangeClient($combinedData['entity'], $filteredData, $fromDate, $toDate); |
99 | } catch (Exception $exception) { |
100 | return null; |
101 | } |
102 | } |
103 | |
104 | /** |
105 | * Get exchange client data for a specific period (legacy functionality) |
106 | */ |
107 | public function getExchangeClientForPeriod(string $scopeId, string $period): mixed |
108 | { |
109 | try { |
110 | return \App::$http |
111 | ->readGetResult('/warehouse/clientscope/' . $scopeId . '/' . $period . '/') |
112 | ->getEntity() |
113 | ->withCalculatedTotals($this->totals, 'date') |
114 | ->toHashed(); |
115 | } catch (Exception $exception) { |
116 | return null; |
117 | } |
118 | } |
119 | |
120 | /** |
121 | * Get client period data for the current scope |
122 | */ |
123 | public function getClientPeriod(string $scopeId): mixed |
124 | { |
125 | try { |
126 | return \App::$http |
127 | ->readGetResult('/warehouse/clientscope/' . $scopeId . '/') |
128 | ->getEntity(); |
129 | } catch (Exception $exception) { |
130 | return null; |
131 | } |
132 | } |
133 | |
134 | /** |
135 | * Get all years that need to be fetched for a date range |
136 | */ |
137 | private function getYearsForDateRange(string $fromDate, string $toDate): array |
138 | { |
139 | $fromYear = (int) substr($fromDate, 0, 4); |
140 | $toYear = (int) substr($toDate, 0, 4); |
141 | |
142 | $years = []; |
143 | for ($year = $fromYear; $year <= $toYear; $year++) { |
144 | $years[] = $year; |
145 | } |
146 | |
147 | return $years; |
148 | } |
149 | |
150 | /** |
151 | * Fetch and combine data from multiple years |
152 | */ |
153 | private function fetchAndCombineDataFromYears(string $scopeId, array $years): array |
154 | { |
155 | $combinedData = []; |
156 | $baseEntity = null; |
157 | |
158 | foreach ($years as $year) { |
159 | try { |
160 | $exchangeClient = \App::$http |
161 | ->readGetResult( |
162 | '/warehouse/clientscope/' . $scopeId . '/' . $year . '/', |
163 | ['groupby' => 'day'] |
164 | ) |
165 | ->getEntity(); |
166 | |
167 | // Use the first successfully fetched entity as the base |
168 | if ($baseEntity === null) { |
169 | $baseEntity = $exchangeClient; |
170 | } |
171 | |
172 | // Combine data from all years |
173 | if (isset($exchangeClient->data) && is_array($exchangeClient->data)) { |
174 | $combinedData = array_merge($combinedData, $exchangeClient->data); |
175 | } |
176 | } catch (Exception $exception) { |
177 | // Continue with other years - don't fail completely if one year is missing |
178 | } |
179 | } |
180 | |
181 | usort($combinedData, static function ($a, $b) { |
182 | return strcmp($a[1] ?? '', $b[1] ?? ''); |
183 | }); |
184 | |
185 | return [ |
186 | 'entity' => $baseEntity, |
187 | 'data' => $combinedData |
188 | ]; |
189 | } |
190 | |
191 | /** |
192 | * Filter data array by date range |
193 | */ |
194 | private function filterDataByDateRange(array $data, string $fromDate, string $toDate): array |
195 | { |
196 | $filteredData = []; |
197 | foreach ($data as $row) { |
198 | if ($row[1] >= $fromDate && $row[1] <= $toDate) { |
199 | $filteredData[] = $row; |
200 | } |
201 | } |
202 | return $filteredData; |
203 | } |
204 | |
205 | /** |
206 | * Create filtered exchange client with updated properties |
207 | */ |
208 | private function createFilteredExchangeClient( |
209 | $exchangeClientFull, |
210 | array $filteredData, |
211 | string $fromDate, |
212 | string $toDate |
213 | ): mixed { |
214 | $exchangeClient = clone $exchangeClientFull; |
215 | $exchangeClient->data = $filteredData; |
216 | |
217 | if (!isset($exchangeClient->period)) { |
218 | $exchangeClient->period = 'day'; |
219 | } |
220 | |
221 | $exchangeClient->firstDay = (new Day())->setDateTime(new DateTime($fromDate)); |
222 | $exchangeClient->lastDay = (new Day())->setDateTime(new DateTime($toDate)); |
223 | |
224 | if (!empty($filteredData)) { |
225 | return $exchangeClient |
226 | ->withCalculatedTotals($this->totals, 'date') |
227 | ->toHashed(); |
228 | } |
229 | |
230 | return $exchangeClient->toHashed(); |
231 | } |
232 | |
233 | /** |
234 | * Validate if the given string is a valid date format (YYYY-MM-DD) |
235 | */ |
236 | public function isValidDateFormat(string $date): bool |
237 | { |
238 | if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) { |
239 | return false; |
240 | } |
241 | |
242 | $dateTime = DateTime::createFromFormat('Y-m-d', $date); |
243 | return $dateTime && $dateTime->format('Y-m-d') === $date; |
244 | } |
245 | |
246 | /** |
247 | * Prepare download arguments for client report |
248 | */ |
249 | public function prepareDownloadArgs( |
250 | array $args, |
251 | mixed $exchangeClient, |
252 | ?array $dateRange, |
253 | array $selectedScopes = [] |
254 | ): array { |
255 | $args['category'] = 'clientscope'; |
256 | |
257 | if ($dateRange) { |
258 | $args['period'] = $dateRange['from'] . '_' . $dateRange['to']; |
259 | } |
260 | |
261 | if (!empty($selectedScopes)) { |
262 | $args['selectedScopes'] = $selectedScopes; |
263 | } |
264 | |
265 | if ($exchangeClient && count($exchangeClient->data)) { |
266 | $args['reports'][] = $exchangeClient; |
267 | } |
268 | |
269 | return $args; |
270 | } |
271 | |
272 | /** |
273 | * Get the totals array for calculations |
274 | */ |
275 | public function getTotals(): array |
276 | { |
277 | return $this->totals; |
278 | } |
279 | } |