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/Modules/StatModule.php +181 -103 1.10.2 → 2.4.5 View file →
@@ -5,23 +5,21 @@
5 5 use FluentSupport\App\Models\Agent;
6 6 use FluentSupport\App\Models\Conversation;
7 7 use FluentSupport\App\Models\Ticket;
8 8 use FluentSupport\App\Models\Product;
9 +use FluentSupport\App\Services\Tickets\AgentTicketAccess;
9 10
10 11 /**
11 - * StatModule class is responsible for getting data related to report
12 - * @package FluentSupport\App\Modules
13 - *
14 - * @version 1.0.0
12 + * Dashboard ticket stats, scoped to the current agent's mailbox restrictions.
15 13 */
16 14 class StatModule
17 15 {
18 16 /**
19 - * getAgentStat method will return ticket statistics by an agent id
20 - * This method will get agent id, start date and end date as parameter and fetch the ticket information and return
17 + * Date range defaults to today.
18 + *
21 19 * @param $agentId
22 - * @param false $startDate
23 - * @param false $endDate
20 + * @param string|false $startDate
21 + * @param string|false $endDate
24 22 * @return array[]
25 23 */
26 24 public static function getAgentStat($agentId, $startDate = false, $endDate = false)
27 25 {
@@ -30,41 +28,41 @@
30 28 $startDate = $currentDate;
31 29 $endDate = $currentDate;
32 30 }
33 31
34 - $startDate = date('Y-m-d 00:00.01', strtotime($startDate));
35 - $endDate = date('Y-m-d 23:59.59', strtotime($endDate));
32 + $startDate = gmdate('Y-m-d 00:00.01', strtotime($startDate));
33 + $endDate = gmdate('Y-m-d 23:59.59', strtotime($endDate));
36 34
37 - //Get list of new ticket by agent
38 - $newTickets = Ticket::where('agent_id', $agentId)
39 - ->where('status', 'new')
40 - ->count();
35 + $access = new AgentTicketAccess();
41 36
42 - //Get list of active ticket by agent
43 - $activeTickets = Ticket::where('agent_id', $agentId)
44 - ->where('status', 'active')
45 - ->count();
37 + $newTickets = $access->applyMailboxRestrictionScope(
38 + Ticket::where('agent_id', $agentId)->where('status', 'new')
39 + )->count();
46 40
47 - //Get list of closed ticket by agent within a date range(default today)
48 - $closedTickets = Ticket::where('agent_id', $agentId)
49 - ->where('status', 'closed')
50 - ->whereBetween('resolved_at', [$startDate, $endDate])
51 - ->count();
41 + $activeTickets = $access->applyMailboxRestrictionScope(
42 + Ticket::where('agent_id', $agentId)->where('status', 'active')
43 + )->count();
52 44
53 - //Get list of response by agent id within a date range(default today)
54 - $responses = Conversation::where('conversation_type', 'response')
55 - ->where('person_id', $agentId)
56 - ->whereBetween('created_at', [$startDate, $endDate])
57 - ->count();
45 + $closedTickets = $access->applyMailboxRestrictionScope(
46 + Ticket::where('agent_id', $agentId)
47 + ->where('status', 'closed')
48 + ->whereBetween('resolved_at', [$startDate, $endDate])
49 + )->count();
58 50
59 - //Count the response in tickets within a date range(default today) for agent
60 - $interactions = Conversation::where('person_id', $agentId)
61 - ->where('conversation_type', 'response')
62 - ->whereBetween('created_at', [$startDate, $endDate])
63 - ->groupBy('ticket_id')
64 - ->get()
65 - ->count();
51 + $responses = $access->applyMailboxRestrictionScopeViaTicket(
52 + Conversation::where('conversation_type', 'response')
53 + ->where('person_id', $agentId)
54 + ->whereBetween('created_at', [$startDate, $endDate])
55 + )->count();
66 56
57 + // Distinct tickets the agent replied to
58 + $interactions = $access->applyMailboxRestrictionScopeViaTicket(
59 + Conversation::where('person_id', $agentId)
60 + ->where('conversation_type', 'response')
61 + ->whereBetween('created_at', [$startDate, $endDate])
62 + ->groupBy('ticket_id')
63 + )->get()->count();
64 +
67 65 return [
68 66 'new_tickets' => [
69 67 'title' => __('New Tickets', 'fluent-support'),
70 68 'count' => $newTickets
@@ -77,9 +75,9 @@
77 75 'title' => __('Closed Tickets', 'fluent-support'),
78 76 'count' => $closedTickets
79 77 ],
80 78 'responses' => [
81 - 'title' => __('Responses', 'fluent-support'),
79 + 'title' => __('Total Responses', 'fluent-support'),
82 80 'count' => $responses
83 81 ],
84 82 'interactions' => [
85 83 'title' => __('Interactions', 'fluent-support'),
@@ -88,25 +86,27 @@
88 86 ];
89 87 }
90 88
91 89 /**
92 - * getOverAllStats method will return the overall statistics for all tickets
93 - * This method will count the ticket number by ticket status and return the array
94 90 * @return array[]
95 91 */
96 92 public static function getOverAllStats()
97 93 {
98 - $newTickets = Ticket::where('status', 'new')
94 + $access = new AgentTicketAccess();
95 +
96 + // Same mailbox boundary as the ticket list; these totals are agent-facing.
97 + $newTickets = $access->applyMailboxRestrictionScope(Ticket::where('status', 'new'))
99 98 ->count();
100 99
101 - $activeTickets = Ticket::where('status', 'active')
100 + $activeTickets = $access->applyMailboxRestrictionScope(Ticket::where('status', 'active'))
102 101 ->count();
103 102
104 - $closedTickets = Ticket::where('status', 'closed')
103 + $closedTickets = $access->applyMailboxRestrictionScope(Ticket::where('status', 'closed'))
105 104 ->count();
106 105
107 - $responses = Conversation::where('conversation_type', 'response')
108 - ->count();
106 + $responses = $access->applyMailboxRestrictionScopeViaTicket(
107 + Conversation::where('conversation_type', 'response')
108 + )->count();
109 109
110 110 return [
111 111 'new_tickets' => [
112 112 'title' => __('New Tickets', 'fluent-support'),
@@ -127,29 +127,33 @@
127 127 ];
128 128 }
129 129
130 130 /**
131 - * getTodayStats method will return a stats of today's tickets
132 - * This method will count the ticket number by ticket status and return the array
133 - * @param bool|int $agentId By default value set to false however when it gets an agent id it will fetch
134 - * the result by this id
135 - * @return array result in array format
131 + * @param bool|int $agentId Limit to this agent when given.
132 + * @return array
136 133 */
137 - public static function getTodayStats($agentId = false)
134 + public static function getTodayStats($agentId = false, ?AgentTicketAccess $access = null)
138 135 {
139 - $start = date('Y-m-d 00:00.01');
140 - $end = date('Y-m-d 23:59.59');
136 + list($start, $end) = static::todayRange();
141 137
142 - $newTickets = Ticket::whereBetween('created_at', [$start, $end]);
138 + $access = $access ?: new AgentTicketAccess();
143 139
144 - $closedTickets = Ticket::where('status', 'closed')->whereBetween('resolved_at', [$start, $end]);
140 + $newTickets = $access->applyMailboxRestrictionScope(
141 + Ticket::whereBetween('created_at', [$start, $end])
142 + );
145 143
146 - $responses = Conversation::where('conversation_type', 'response')
147 - ->whereHas('person', function ($q) {
148 - $q->where('person_type', 'agent');
149 - })
150 - ->whereBetween('created_at', [$start, $end]);
144 + $closedTickets = $access->applyMailboxRestrictionScope(
145 + Ticket::where('status', 'closed')->whereBetween('resolved_at', [$start, $end])
146 + );
151 147
148 + $responses = $access->applyMailboxRestrictionScopeViaTicket(
149 + Conversation::where('conversation_type', 'response')
150 + ->whereHas('person', function ($q) {
151 + $q->where('person_type', 'agent');
152 + })
153 + ->whereBetween('created_at', [$start, $end])
154 + );
155 +
152 156 if ($agentId) {
153 157 $newTickets->where('agent_id', $agentId);
154 158 $closedTickets->where('agent_id', $agentId);
155 159 $responses->where('person_id', $agentId);
@@ -171,36 +175,27 @@
171 175 ];
172 176 }
173 177
174 178 /**
175 - * getAgentOverallStats method will produce overall statistics by agent
176 179 * @param $agentId
177 180 * @return array[]
178 181 */
179 182 public static function getAgentOverallStats($agentId)
180 183 {
181 - //Get count of response by the agent
182 - $replies_count = Conversation::where('person_id', $agentId)->count();
184 + $replies_count = (new AgentTicketAccess())->applyMailboxRestrictionScopeViaTicket(
185 + Conversation::where('person_id', $agentId)
186 + )->count();
183 187
184 - //Get the number of interactions/responses by agent with tickets
185 - $interactions_count = Conversation::where('person_id', $agentId)
186 - ->where('conversation_type', 'response')
187 - ->groupBy('ticket_id')
188 - ->get()
189 - ->count();
188 + // Closed tickets assigned to the agent, whoever closed them
189 + $total_closed = (new AgentTicketAccess())->applyMailboxRestrictionScope(
190 + Ticket::where('agent_id', $agentId)->where('status', 'closed')
191 + )->count();
190 192
191 - //Get the number of tickets that are closed by this agent
192 - $total_closed = Ticket::where('agent_id', $agentId)->where('status', 'closed')->count();
193 -
194 193 return [
195 194 'replies_count' => [
196 195 'title' => __('Total Replies', 'fluent-support'),
197 196 'count' => $replies_count
198 197 ],
199 - 'interactions_count' => [
200 - 'title' => __('Total Interactions', 'fluent-support'),
201 - 'count' => $interactions_count
202 - ],
203 198 'total_closed' => [
204 199 'title' => __('Total Closed', 'fluent-support'),
205 200 'count' => $total_closed
206 201 ]
@@ -207,9 +202,9 @@
207 202 ];
208 203 }
209 204
210 205 /**
211 - * This `getActiveTicketsByProductStats` method will count today's tickets by product that are waiting for reply
206 + * Open tickets awaiting an agent reply, per product.
212 207 * @return array $result
213 208 */
214 209 public static function getActiveTicketsByProductStats()
215 210 {
@@ -215,12 +210,19 @@
215 210 {
216 211 $products = Product::all();
217 212 $result = [];
218 213
214 + $counts = static::awaitingTicketsQuery(new AgentTicketAccess())
215 + ->whereIn('product_id', $products->pluck('id')->all())
216 + ->groupBy('product_id')
217 + ->selectRaw('product_id, COUNT(*) AS total')
218 + ->pluck('total', 'product_id')
219 + ->all();
220 +
219 221 foreach ($products as $product) {
220 222 $result[$product->id] = [
221 223 'title' => $product->title,
222 - 'count' => static::countAwaitingTickets('product_id', $product->id)
224 + 'count' => (int) ($counts[$product->id] ?? 0)
223 225 ];
224 226 }
225 227
226 228 return $result;
@@ -227,56 +229,132 @@
227 229 }
228 230
229 231
230 232 /**
231 - * This will count the number of tickets that are waiting for response also it can receive parameters
232 - * @param string $whereClause // This is the where clause that will be used in the query
233 - * @param string $whereClauseValue // This is the value of the where clause
233 + * Open tickets awaiting an agent reply, optionally filtered by one column.
234 + *
235 + * @param string $whereClause Column name.
236 + * @param string $whereClauseValue
234 237 * @return int $awatingTicketCount
235 238 */
236 - public static function countAwaitingTickets($whereClause = null, $whereClauseValue = null)
239 + public static function countAwaitingTickets($whereClause = null, $whereClauseValue = null, ?AgentTicketAccess $access = null)
237 240 {
238 - $ticket = new Ticket;
241 + $ticket = static::awaitingTicketsQuery($access ?: new AgentTicketAccess());
239 242
240 243 if ($whereClause && $whereClauseValue) {
241 - $ticket = $ticket->where(sanitize_text_field($whereClause), sanitize_text_field($whereClauseValue));
244 + $ticket->where(sanitize_text_field($whereClause), sanitize_text_field($whereClauseValue));
242 245 }
243 246
244 - $awatingTicketCount = $ticket->where('status', '!=', 'closed')
247 + return $ticket->count();
248 + }
249 +
250 + /**
251 + * Open tickets awaiting an agent reply, within the viewer's mailboxes.
252 + *
253 + * @param AgentTicketAccess $access
254 + * @return \FluentSupport\Framework\Database\Orm\Builder
255 + */
256 + protected static function awaitingTicketsQuery(AgentTicketAccess $access)
257 + {
258 + // Must be a builder: ->where() on a bare model returns a new one, losing the scope.
259 + return $access->applyMailboxRestrictionScope(Ticket::query())
260 + ->where('status', '!=', 'closed')
245 261 ->where(function ($query) {
246 262 $query->whereColumn('last_agent_response', '<', 'last_customer_response')
247 263 ->orWhereNull('last_agent_response')
248 264 ->orWhere('status', 'new');
249 - })->count();
265 + });
266 + }
250 267
251 - return $awatingTicketCount;
268 + /**
269 + * Today in site time, like the stored dates.
270 + *
271 + * @return string[] [start, end] as "Y-m-d H:i:s".
272 + */
273 + protected static function todayRange()
274 + {
275 + $today = current_time('Y-m-d');
276 +
277 + return [$today . ' 00:00:00', $today . ' 23:59:59'];
252 278 }
253 279
254 280 /**
255 - * This method will return a summary of today's stats of all agent
281 + * Today's stats for every agent with replies today or tickets waiting.
256 282 * @return array
257 283 */
258 284 public static function getAgentTodayStats()
259 285 {
286 + // Share one instance so restrictions are resolved once.
287 + $access = new AgentTicketAccess();
288 + list($start, $end) = static::todayRange();
289 +
290 + $agents = Agent::get();
291 + $agentIds = $agents->pluck('id')->all();
292 +
293 + if (!$agentIds) {
294 + return [];
295 + }
296 +
297 + // One grouped query per figure for all agents.
298 + $countBy = function ($query, $column) {
299 + return $query->groupBy($column)
300 + ->selectRaw($column . ' AS grouped_id, COUNT(*) AS total')
301 + ->pluck('total', 'grouped_id')
302 + ->all();
303 + };
304 +
305 + $waiting = $countBy(static::awaitingTicketsQuery($access)->whereIn('agent_id', $agentIds), 'agent_id');
306 +
307 + $newTickets = $countBy($access->applyMailboxRestrictionScope(
308 + Ticket::whereBetween('created_at', [$start, $end])->whereIn('agent_id', $agentIds)
309 + ), 'agent_id');
310 +
311 + $closedTickets = $countBy($access->applyMailboxRestrictionScope(
312 + Ticket::where('status', 'closed')->whereBetween('resolved_at', [$start, $end])->whereIn('agent_id', $agentIds)
313 + ), 'agent_id');
314 +
315 + // person_id among the agent ids is the "written by an agent" check.
316 + $responses = $countBy($access->applyMailboxRestrictionScopeViaTicket(
317 + Conversation::where('conversation_type', 'response')
318 + ->whereIn('person_id', $agentIds)
319 + ->whereBetween('created_at', [$start, $end])
320 + ), 'person_id');
321 +
260 322 $stats = [];
261 - Agent::select(['id', 'first_name'])->get()->each(function ($agent) use (&$stats) {
262 - $agentStat = static::getTodayStats($agent->id);
263 - $waiting = static::countAwaitingTickets('agent_id', $agent->id);
264 - if(!empty($agentStat['responses']['count']) || $waiting) {
265 - $stats[] = [
266 - 'agent_name' => $agent->first_name,
267 - 'stats' => array_merge(
268 - [
269 - 'waiting_today' => [
270 - 'title' => __('Waiting Today', 'fluent-support'),
271 - 'count' => $waiting
272 - ]
273 - ],
274 - $agentStat
275 - )
276 - ];
323 +
324 + foreach ($agents as $agent) {
325 + $waitingCount = (int) ($waiting[$agent->id] ?? 0);
326 + $responseCount = (int) ($responses[$agent->id] ?? 0);
327 +
328 + if (!$responseCount && !$waitingCount) {
329 + continue;
277 330 }
278 - });
331 +
332 + $stats[] = [
333 + 'agent_id' => $agent->id,
334 + 'agent_name' => $agent->full_name,
335 + 'agent_email' => $agent->email,
336 + 'avatar' => $agent->photo,
337 + 'stats' => [
338 + 'waiting_today' => [
339 + 'title' => __('Waiting Today', 'fluent-support'),
340 + 'count' => $waitingCount
341 + ],
342 + 'new_tickets' => [
343 + 'title' => __('New Tickets', 'fluent-support'),
344 + 'count' => (int) ($newTickets[$agent->id] ?? 0)
345 + ],
346 + 'closed_tickets' => [
347 + 'title' => __('Closed Tickets', 'fluent-support'),
348 + 'count' => (int) ($closedTickets[$agent->id] ?? 0)
349 + ],
350 + 'responses' => [
351 + 'title' => __('Responses', 'fluent-support'),
352 + 'count' => $responseCount
353 + ]
354 + ]
355 + ];
356 + }
279 357
280 358 return $stats;
281 359 }
282 360