Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.00% covered (warning)
88.00%
88 / 100
53.85% covered (warning)
53.85%
7 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
ReportClientService
88.00% covered (warning)
88.00%
88 / 100
53.85% covered (warning)
53.85%
7 / 13
49.66
0.00% covered (danger)
0.00%
0 / 1
 extractSelectedScopes
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 extractDateRange
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 getExchangeClientData
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getExchangeClientForDateRange
71.43% covered (warning)
71.43%
10 / 14
0.00% covered (danger)
0.00%
0 / 1
6.84
 getExchangeClientForPeriod
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
2.09
 getClientPeriod
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
2.26
 getYearsForDateRange
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 fetchAndCombineDataFromYears
95.24% covered (success)
95.24%
20 / 21
0.00% covered (danger)
0.00%
0 / 1
6
 filterDataByDateRange
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 createFilteredExchangeClient
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
3.05
 isValidDateFormat
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 prepareDownloadArgs
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 getTotals
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * @package Zmsstatistic
5 * @copyright BerlinOnline Stadtportal GmbH & Co. KG
6 **/
7
8namespace BO\Zmsstatistic\Service;
9
10use BO\Zmsentities\Day;
11use DateTime;
12use Exception;
13
14class 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}