PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.4.5
Fluent Support – Helpdesk & Customer Support Ticket System v2.4.5
2.4.5 2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 All 69 releases
← All changes | app/Http/Controllers/ReportingController.php +196 -38 1.5.3 → 2.4.5 View file →
@@ -1,70 +1,82 @@
1 1 <?php
2 2
3 3 namespace FluentSupport\App\Http\Controllers;
4 4
5 -use FluentSupport\App\Models\Agent;
6 5 use FluentSupport\App\Modules\Reporting\Reporting;
7 6 use FluentSupport\App\Modules\StatModule;
8 7 use FluentSupport\App\Services\Helper;
9 -use FluentSupport\Framework\Request\Request;
8 +use FluentSupport\Framework\Http\Request\Request;
9 +use FluentSupport\App\Models\Ticket;
10 +use FluentSupport\App\Services\Tickets\AgentTicketAccess;
11 +use FluentSupport\App\Models\Conversation;
12 +use FluentSupport\App\Models\TagPivot;
10 13
14 +/**
15 + * Report endpoints.
16 + */
11 17 class ReportingController extends Controller
12 18 {
13 - public function getOverallReports(Request $request)
19 + public static function getSanitizedDateRange(Request $request)
14 20 {
15 - return [
16 - 'overall_reports' => StatModule::getOverAllStats()
17 - ];
18 - }
21 + $dateRange = $request->get('date_range', []);
19 22
20 - public function getTicketsChart(Request $request, Reporting $reporting)
21 - {
22 - list($from, $to) = $request->get('date_range') ?: ['', ''];
23 + if (is_array($dateRange) && count($dateRange) >= 2) {
24 + return [
25 + sanitize_text_field($dateRange[0] ?? ''),
26 + sanitize_text_field($dateRange[1] ?? '')
27 + ];
28 + }
23 29
24 - return [
25 - 'stats' => $reporting->getTicketsGrowth($from, $to)
26 - ];
27 - }
30 + if (is_string($dateRange)) {
31 + $parts = array_map('trim', explode(',', $dateRange));
32 + return [
33 + sanitize_text_field($parts[0] ?? ''),
34 + sanitize_text_field($parts[1] ?? '')
35 + ];
36 + }
28 37
29 - public function getResolveChart(Request $request, Reporting $reporting)
30 - {
31 - list($from, $to) = $request->get('date_range') ?: ['', ''];
32 -
33 - return [
34 - 'stats' => $reporting->getTicketResolveGrowth($from, $to)
35 - ];
38 + return ['', ''];
36 39 }
37 40
38 - public function getResponseChart(Request $request, Reporting $reporting)
41 + public static function getAgentIdsForGroup(Request $request)
39 42 {
40 - list($from, $to) = $request->get('date_range') ?: ['', ''];
43 + $groupId = $request->getSafe('agent_group_id', 'intval');
44 + if (!$groupId) {
45 + return null;
46 + }
41 47
42 - return [
43 - 'stats' => $reporting->getResponseGrowth($from, $to)
44 - ];
48 + return TagPivot::where('source_type', 'agent_group')
49 + ->where('tag_id', $groupId)
50 + ->pluck('source_id')
51 + ->toArray();
45 52 }
46 53
47 - public function getAgentsSummary(Request $request, Reporting $reporting)
54 + /**
55 + * Stats for the logged-in agent.
56 + *
57 + * @param Request $request
58 + * @return array
59 + */
60 + public function getAgentOverallReports(Request $request): array
48 61 {
49 - return [
50 - 'summary' => $reporting->agentSummary($request->get('from'), $request->get('to'))
51 - ];
52 - }
53 -
54 - public function getAgentOverallReports(Request $request)
55 - {
56 62 $agent = Helper::getAgentByUserId(get_current_user_id());
57 63
58 64 return [
59 - 'overall_reports' => StatModule::getAgentOverallStats($agent->id)
65 + 'overall_reports' => StatModule::getAgentOverallStats($agent->id),
66 + 'today_reports' => StatModule::getTodayStats($agent->id)
60 67 ];
61 68 }
62 69
70 + /**
71 + * @param Request $request
72 + * @param Reporting $reporting
73 + * @return array
74 + */
63 75 public function getAgentResolveChart(Request $request, Reporting $reporting)
64 76 {
65 77 $agent = Helper::getAgentByUserId(get_current_user_id());
66 - list($from, $to) = $request->get('date_range') ?: ['', ''];
78 + list($from, $to) = self::getSanitizedDateRange($request);
67 79
68 80 return [
69 81 'stats' => $reporting->getTicketResolveGrowth($from, $to, ['agent_id' => $agent->id])
70 82 ];
@@ -69,12 +81,17 @@
69 81 'stats' => $reporting->getTicketResolveGrowth($from, $to, ['agent_id' => $agent->id])
70 82 ];
71 83 }
72 84
85 + /**
86 + * @param Request $request
87 + * @param Reporting $reporting
88 + * @return array
89 + */
73 90 public function getAgentResponseChart(Request $request, Reporting $reporting)
74 91 {
75 92 $agent = Helper::getAgentByUserId(get_current_user_id());
76 - list($from, $to) = $request->get('date_range') ?: ['', ''];
93 + list($from, $to) = self::getSanitizedDateRange($request);
77 94
78 95 return [
79 96 'stats' => $reporting->getResponseGrowth($from, $to, ['person_id' => $agent->id])
80 97 ];
@@ -79,13 +96,154 @@
79 96 'stats' => $reporting->getResponseGrowth($from, $to, ['person_id' => $agent->id])
80 97 ];
81 98 }
82 99
100 + /**
101 + * Closed/open ticket and response counts for the logged-in agent in a date range.
102 + *
103 + * @param Reporting $reporting
104 + * @param Request $request
105 + * @return array
106 + */
83 107 public function getPersonalSummary(Reporting $reporting, Request $request)
84 108 {
85 109 $agent = Helper::getAgentByUserId(get_current_user_id());
86 110
87 111 return [
88 - 'summary' => $reporting->agentSummary($request->get('from'), $request->get('to'), $agent->id)
112 + 'summary' => $reporting->agentSummary($request->getSafe('from', 'sanitize_text_field'), $request->getSafe('to', 'sanitize_text_field'), $agent->id)
89 113 ];
90 114 }
115 +
116 + /**
117 + * Overview counts filtered by date_range, mailbox_id (or business_box), product_id,
118 + * agent_id, agent_group_id and customer_id. With agent_id, active_tickets is
119 + * replaced by the agent's total_replies.
120 + *
121 + * @param Request $request
122 + * @return array
123 + */
124 + public function getStats(Request $request)
125 + {
126 + list($from, $to) = self::getSanitizedDateRange($request);
127 +
128 + $filters = [
129 + 'mailbox_id' => $request->getSafe('mailbox_id', 'intval') ?: $request->getSafe('business_box', 'intval'),
130 + 'product_id' => $request->getSafe('product_id', 'intval'),
131 + 'agent_id' => $request->getSafe('agent_id', 'intval'),
132 + 'customer_id' => $request->getSafe('customer_id', 'intval'),
133 + ];
134 +
135 + $agentIds = self::getAgentIdsForGroup($request);
136 +
137 + $access = new AgentTicketAccess();
138 +
139 + // Bound first: a caller-supplied mailbox_id below is not proof of access.
140 + $baseQuery = $access->applyMailboxRestrictionScope(Ticket::query());
141 +
142 + foreach ($filters as $field => $value) {
143 + if ($value) {
144 + $baseQuery->where($field, $value);
145 + }
146 + }
147 +
148 + if ($agentIds !== null) {
149 + $baseQuery->whereIn('agent_id', $agentIds);
150 + }
151 +
152 + $applyDateRange = function($query, $dateField = 'created_at') use ($from, $to) {
153 + if ($from && $to) {
154 + $query->whereBetween($dateField, ["$from 00:00:00", "$to 23:59:59"]);
155 + } elseif ($from) {
156 + $query->where($dateField, '>=', "$from 00:00:00");
157 + } elseif ($to) {
158 + $query->where($dateField, '<=', "$to 23:59:59");
159 + }
160 + };
161 +
162 + $countTickets = function($status, $dateField = 'created_at') use ($baseQuery, $applyDateRange) {
163 + $query = clone $baseQuery;
164 + $query->where('status', $status);
165 + $applyDateRange($query, $dateField);
166 + return $query->count();
167 + };
168 +
169 + $newTickets = $countTickets('new');
170 + $closedTickets = $countTickets('closed', 'resolved_at');
171 +
172 + $openQuery = clone $baseQuery;
173 + $openQuery->where('status', '!=', 'closed');
174 + $applyDateRange($openQuery);
175 + $openTickets = $openQuery->count();
176 +
177 + $agentId = $filters['agent_id'];
178 +
179 + $responsesQuery = $access->applyMailboxRestrictionScopeViaTicket(
180 + Conversation::query()->where('conversation_type', 'response')
181 + );
182 +
183 + // For a single agent, count the replies they wrote, not every reply on tickets assigned to them.
184 + $ticketFilters = $filters;
185 + if ($agentId) {
186 + $responsesQuery->where('person_id', $agentId);
187 + unset($ticketFilters['agent_id']);
188 + }
189 +
190 + if (array_filter($ticketFilters) || $agentIds !== null) {
191 + $responsesQuery->whereHas('ticket', function ($q) use ($ticketFilters, $agentIds) {
192 + foreach ($ticketFilters as $field => $value) {
193 + if ($value) {
194 + $q->where($field, $value);
195 + }
196 + }
197 + if ($agentIds !== null) {
198 + $q->whereIn('agent_id', $agentIds);
199 + }
200 + });
201 + }
202 +
203 + $applyDateRange($responsesQuery, 'created_at');
204 + $responses = $responsesQuery->count();
205 +
206 + if ($agentId) {
207 + $stats = [
208 + 'new_tickets' => $newTickets,
209 + 'closed_tickets' => $closedTickets,
210 + 'responses' => $responses,
211 + 'open_tickets' => $openTickets,
212 + ];
213 + } else {
214 + $activeTickets = $countTickets('active');
215 +
216 + $stats = [
217 + 'new_tickets' => $newTickets,
218 + 'active_tickets' => $activeTickets,
219 + 'closed_tickets' => $closedTickets,
220 + 'responses' => $responses,
221 + 'open_tickets' => $openTickets,
222 + ];
223 + }
224 +
225 + $labels = [
226 + 'new_tickets' => __('New Tickets', 'fluent-support'),
227 + 'active_tickets' => __('Active Tickets', 'fluent-support'),
228 + 'closed_tickets' => __('Closed Tickets', 'fluent-support'),
229 + 'responses' => __('Responses', 'fluent-support'),
230 + 'open_tickets' => __('Open Tickets', 'fluent-support'),
231 + ];
232 +
233 + if ($agentId) {
234 + $labels['responses'] = __('Total Responses', 'fluent-support');
235 + }
236 +
237 + $overallReports = [];
238 + foreach ($stats as $key => $count) {
239 + $overallReports[$key] = [
240 + 'title' => $labels[$key] ?? ucwords(str_replace('_', ' ', (string) $key)),
241 + 'key' => $key,
242 + 'count' => $count,
243 + ];
244 + }
245 +
246 + return ['overall_reports' => $overallReports];
247 + }
248 +
91 249 }