PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
fluent-cart / app / Services / Report / OrderReportService.php

OrderReportService.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.5, at app/Services/Report/OrderReportService.php

372 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\Report;
4
5 use FluentCart\App\App;
6 use FluentCart\App\Services\DateTime\DateFormatter;
7
8 class OrderReportService extends ReportService
9 {
10 public function groupBy($params = [])
11 {
12 $groupKey = ReportHelper::sanitizeGroupKey($params['groupKey'] ?? null);
13
14 $query = App::db()->table('fct_orders as o');
15
16 $query = $this->applyFilters($query, $params);
17
18 if (in_array($groupKey, ['billing_country', 'shipping_country'])) {
19 $type = $groupKey === 'billing_country' ? 'billing' : 'shipping';
20
21 $query->leftJoin(
22 'fct_order_addresses as a',
23 fn ($join) => $join->on('o.id', '=', 'a.order_id')->where('a.type', '=', $type)
24 );
25
26 $query->selectRaw("COALESCE(a.country, 'Uncategorized') AS `{$groupKey}`");
27 } else {
28 $query->selectRaw("COALESCE(o.{$groupKey}, 'Uncategorized') AS `{$groupKey}`");
29 }
30
31 $query->selectRaw("COUNT(o.id) AS orders,
32
33 SUM(o.total_paid) / 100 AS gross_sale,
34
35 SUM(
36 o.total_paid
37 - o.total_refund
38 - o.tax_total
39 - o.shipping_tax
40 ) / 100 AS net_sale,
41
42 SUM(o.total_paid) / COUNT(o.id) / 100 AS average_order_gross,
43
44 SUM(
45 o.total_paid
46 - o.total_refund
47 - o.tax_total
48 - o.shipping_tax
49 ) / COUNT(o.id) / 100 AS average_order_net")
50 ->groupBy($groupKey)
51 ->orderBy($groupKey);
52
53 return $query->get();
54 }
55
56 public function getOrderValueDistribution(array $params)
57 {
58 $query = App::db()->table('fct_orders as o')
59 ->selectRaw("SUM(CASE WHEN total_amount <= 10000 THEN 1 ELSE 0 END) AS `0-100`,
60 SUM(CASE WHEN total_amount > 10000 AND total_amount <= 20000 THEN 1 ELSE 0 END) AS `100-200`,
61 SUM(CASE WHEN total_amount > 20000 AND total_amount <= 30000 THEN 1 ELSE 0 END) AS `200-300`,
62 SUM(CASE WHEN total_amount > 30000 AND total_amount <= 40000 THEN 1 ELSE 0 END) AS `300-400`,
63 SUM(CASE WHEN total_amount > 40000 AND total_amount <= 50000 THEN 1 ELSE 0 END) AS `400-500`,
64 SUM(CASE WHEN total_amount > 50000 AND total_amount <= 60000 THEN 1 ELSE 0 END) AS `500-600`,
65 SUM(CASE WHEN total_amount > 60000 AND total_amount <= 70000 THEN 1 ELSE 0 END) AS `600-700`,
66 SUM(CASE WHEN total_amount > 70000 AND total_amount <= 80000 THEN 1 ELSE 0 END) AS `700-800`,
67 SUM(CASE WHEN total_amount > 80000 AND total_amount <= 90000 THEN 1 ELSE 0 END) AS `800-900`,
68 SUM(CASE WHEN total_amount > 90000 AND total_amount <= 100000 THEN 1 ELSE 0 END) AS `900-1000`,
69 SUM(CASE WHEN total_amount > 100000 THEN 1 ELSE 0 END) AS `1000+`");
70
71 $query = $this->applyFilters($query, $params);
72
73 return $query->first();
74 }
75
76 public function getOrderLineChart($params = [])
77 {
78 $monthBetween = $params['endDate']->diffInMonths($params['startDate']) + 1;
79
80 $group = ReportHelper::processGroup(
81 $params['startDate'], $params['endDate'], $params['groupKey']
82 );
83
84 $variationIds = array_map('intval', $params['variationIds'] ?? []);
85
86 $itemsSub = App::db()->table('fct_order_items')
87 ->selectRaw('order_id, SUM(quantity) AS total_items')
88 ->groupBy('order_id')
89 ->whereBetween('created_at', [$params['startDate'], $params['endDate']])
90 ->when($variationIds, fn($q) => $q->whereIn('object_id', $variationIds));
91
92 $orderQuery = App::db()->table('fct_orders as o')
93 ->joinSub($itemsSub, 'oi_sum', fn($join) => $join->on('oi_sum.order_id', '=', 'o.id'));
94
95 $orderQuery = $this->applyFilters($orderQuery, $params);
96
97 $orderData = $orderQuery->selectRaw("{$group['field']},
98
99 SUM(o.total_paid) / 100 AS gross_sale,
100
101 SUM(
102 o.total_paid
103 - o.total_refund
104 - o.tax_total
105 - o.shipping_tax
106 ) / 100 AS net_revenue,
107
108 COUNT(o.id) AS order_count,
109
110 SUM(o.total_refund) / 100 AS total_refund,
111
112 SUM(o.shipping_total) / 100 AS shipping_total,
113
114 SUM(o.tax_total + o.shipping_tax) / 100 AS tax_total,
115
116 CASE
117 WHEN COUNT(o.id) = 0 THEN 0
118 ELSE SUM(
119 o.total_paid
120 - o.total_refund
121 - o.tax_total
122 - o.shipping_tax
123 ) / COUNT(o.id) / 100
124 END AS average_net,
125
126 CASE
127 WHEN COUNT(o.id) = 0 THEN 0
128 ELSE SUM(o.total_paid) / COUNT(o.id) / 100
129 END AS average_gross,
130
131 SUM(COALESCE(oi_sum.total_items, 0)) AS total_item_count,
132
133 CASE
134 WHEN COUNT(o.id) = 0 THEN 0
135 ELSE oi_sum.total_items / COUNT(o.id)
136 END AS average_order_items_count,
137
138 ROUND(AVG(o.total_paid / 100)) AS average_order_gross")
139 ->groupByRaw($group['by'])
140 ->get();
141
142 $summary = [
143 'net_revenue' => 0,
144 'gross_sale' => 0,
145 'order_count' => 0,
146 'total_item_count' => 0,
147 'average_net' => 0,
148 'average_order_items_count' => 0,
149 'average_gross' => 0,
150 'total_refund' => 0,
151 'tax_total' => 0,
152 'shipping_total' => 0
153 ];
154
155 $groups = $this->getPeriodRange(
156 $params['startDate'], $params['endDate'], $group['key'], array_keys($summary)
157 );
158
159 foreach ($orderData as $group) {
160 $summary['net_revenue'] += $group->net_revenue;
161 $summary['gross_sale'] += $group->gross_sale;
162 $summary['order_count'] += $group->order_count;
163 $summary['total_item_count'] += $group->total_item_count;
164 $summary['total_refund'] += $group->total_refund;
165 $summary['tax_total'] += $group->tax_total;
166 $summary['shipping_total'] += $group->shipping_total;
167
168 $groups[$group->group] = (array) $group;
169 }
170
171 if ($orderData->count()) {
172 $summary['average_net'] = $summary['net_revenue'] / $summary['order_count'];
173 $summary['average_order_items_count'] = $summary['total_item_count'] / $summary['order_count'];
174 $summary['average_gross'] = $summary['gross_sale'] / $summary['order_count'];
175
176 $summary['monthly_net'] = $summary['net_revenue'] / $monthBetween;
177 $summary['monthly_gross'] = $summary['gross_sale'] / $monthBetween;
178 $summary['monthly_orders'] = $summary['order_count'] / $monthBetween;
179 $summary['monthly_items'] = $summary['total_item_count'] / $monthBetween;
180 }
181
182 return [
183 'chartData' => array_values($groups),
184 'summary' => $summary,
185 ];
186 }
187
188 public function getNewVsReturningCustomer($params = [])
189 {
190 $orders = App::db()->table('fct_orders as o')
191 ->leftJoin('fct_customers as c', 'c.id', '=', 'o.customer_id');
192
193 $orders = $this->applyFilters($orders, $params);
194
195 $rows = $orders->selectRaw("CASE
196 WHEN
197 c.first_purchase_date IS NOT NULL
198 AND c.first_purchase_date >= '{$params['startDate']}'
199 THEN 'New'
200 ELSE 'Returning'
201 END AS customer_type,
202
203 COUNT(DISTINCT o.customer_id) AS customer_count,
204
205 COUNT(o.id) AS order_count,
206
207 SUM(o.total_paid) / 100 AS gross_sales,
208
209 SUM(
210 o.total_paid
211 - o.total_refund
212 - o.tax_total
213 - o.shipping_tax
214 ) / 100 AS net_sales")
215 ->groupBy('customer_type')
216 ->get();
217
218 $result = [];
219
220 if ($rows->count()) {
221 foreach ($rows as $index => $item) {
222 $result[$index] = $item;
223
224 $result[$index]->average_net = $item->net_sales / $item->order_count;
225 $result[$index]->average_gross = $item->gross_sales / $item->order_count;
226 }
227 } else {
228 $metrics = [
229 'customer_type' => 'new',
230 'customer_count' => 0,
231 'order_count' => 0,
232 'net_sales' => 0,
233 'average_net' => 0,
234 'gross_sales' => 0,
235 'average_gross' => 0,
236 ];
237
238 $result = [
239 $metrics,
240 wp_parse_args(['customer_type' => 'returning'], $metrics),
241 ];
242 }
243
244 return $result;
245 }
246
247 public function getReportByDayAndHour(array $params): array
248 {
249 $query = App::db()->table('fct_orders as o')
250 ->selectRaw("HOUR(o.created_at) AS hour_24,
251
252 DAYOFWEEK(o.created_at) AS day_of_week,
253
254 COUNT(o.id) AS order_count,
255
256 SUM(o.total_paid) / 100 AS gross_sale")
257 ->groupByRaw('hour_24, day_of_week')
258 ->orderByRaw('hour_24, day_of_week');
259
260 $query = $this->applyFilters($query, $params);
261
262 $ordersData = $query->get();
263
264 $daysOfWeek = [
265 1 => 'Sunday',
266 2 => 'Monday',
267 3 => 'Tuesday',
268 4 => 'Wednesday',
269 5 => 'Thursday',
270 6 => 'Friday',
271 7 => 'Saturday'
272 ];
273
274 $structuredData = [];
275 $grossSaleByHour = [];
276
277 for ($hour = 0; $hour < 24; $hour++) {
278 $timeLabel = gmdate(DateFormatter::hourFormat(), mktime($hour, 0));
279 $structuredData[$hour] = [
280 'hour' => $timeLabel,
281 'Sunday' => 0,
282 'Monday' => 0,
283 'Tuesday' => 0,
284 'Wednesday' => 0,
285 'Thursday' => 0,
286 'Friday' => 0,
287 'Saturday' => 0,
288 ];
289
290 $grossSaleByHour[$hour] = [
291 'hour' => $timeLabel,
292 'gross_sale' => 0,
293 'order_count' => 0,
294 ];
295 }
296
297 $grossSaleByDay = array_fill(1, 7, ['day' => 0, 'gross_sale' => 0, 'order_count' => 0]);
298
299 foreach ($ordersData as $order) {
300 $hour = (int) $order->hour_24;
301 $dayOfWeek = $daysOfWeek[$order->day_of_week];
302 $orderCount = $order->order_count;
303
304 $structuredData[$hour][$dayOfWeek] += $orderCount;
305
306 $grossSaleByDay[$order->day_of_week]['day'] = $order->day_of_week;
307 $grossSaleByDay[$order->day_of_week]['order_count'] += $orderCount;
308 $grossSaleByDay[$order->day_of_week]['gross_sale'] += $order->gross_sale;
309
310 $grossSaleByHour[$hour]['gross_sale'] += $order->gross_sale;
311 $grossSaleByHour[$hour]['order_count'] += $orderCount;
312 }
313
314 return [
315 'orderByDayAndHour' => $structuredData,
316 'grossSaleByDay' => array_values($grossSaleByDay),
317 'grossSaleByHour' => array_values($grossSaleByHour),
318 ];
319 }
320
321 public function getItemCountDistribution(array $params): array
322 {
323 $itemsSub = App::db()->table('fct_order_items')
324 ->selectRaw('order_id, SUM(quantity) AS item_count')
325 ->groupBy('order_id')
326 ->whereBetween('created_at', [$params['startDate'], $params['endDate']])
327 ->when($params['variationIds'], fn($q) => $q->whereIn('object_id', $params['variationIds']));
328
329 $query = App::db()->table('fct_orders as o')
330 ->selectRaw('COUNT(*) AS order_count, oi_sum.item_count')
331 ->joinSub($itemsSub, 'oi_sum', fn($join) => $join->on('oi_sum.order_id', '=', 'o.id'))
332 ->groupBy('oi_sum.item_count');
333
334 $query = $this->applyFilters($query, $params);
335
336 return $query->get()->toArray();
337 }
338
339 public function calculateFluctuations($currentData, $previousData)
340 {
341 $fluctuations = [];
342
343 foreach ($currentData as $key => $currentValue) {
344 $lastValue = $previousData[$key] ?? 0;
345 if ($lastValue > 0) {
346 $fluctuations[$key] = (($currentValue - $lastValue) / $lastValue) * 100;
347 } else {
348 $fluctuations[$key] = ($currentValue > 0) ? 100 : 0; // 100% increase if current value is greater than 0, else 0% change
349 }
350 }
351
352 return $fluctuations;
353 }
354
355 public function getOrderCompletionTime(array $params): array
356 {
357 $query = App::db()->table('fct_orders as o')
358 ->selectRaw("TIMESTAMPDIFF(
359 HOUR, o.created_at, o.completed_at
360 ) AS hour,
361
362 COUNT(o.id) AS orders")
363 ->whereNotNull('o.completed_at')
364 ->groupBy('hour')
365 ->orderBy('hour');
366
367 $query = $this->applyFilters($query, $params);
368
369 return $query->get()->toArray();
370 }
371 }
372