Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 236
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 2
FindUsersOverviewFilter
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
SharedReportingService
0.00% covered (danger)
0.00%
0 / 235
0.00% covered (danger)
0.00%
0 / 11
1260
0.00% covered (danger)
0.00%
0 / 1
 findShortcut
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
12
 findFlyMsgAI
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
12
 findUsers
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
72
 findInactiveUsers
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
42
 findLicensesUsage
0.00% covered (danger)
0.00%
0 / 99
0.00% covered (danger)
0.00%
0 / 1
12
 findActiveLicensesUsage
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 findLicensesAssigned
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 findInvitationsAssigned
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 getTopUsers
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getUserById
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 formatUser
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Http\Services\Admin\Reports;
4
5use App\Actions\AccountCenter\Reporting\AccountCenterReporting;
6use App\Http\Models\Admin\AdminUserInvitation;
7use App\Http\Models\Auth\User;
8use App\Traits\AccountCenter\Reporting\FlyMsgAITrackingTrait;
9use App\Traits\AccountCenter\Reporting\ShortcutTrait;
10use App\Traits\HubspotPropertiesTrait;
11use Carbon\Carbon;
12use MongoDB\BSON\UTCDateTime;
13
14class FindUsersOverviewFilter
15{
16    public function __construct(
17        public readonly ?Carbon $fromDate,
18        public readonly ?Carbon $toDate,
19        public readonly ?array $userIds,
20        public readonly ?array $groupIds,
21        public readonly ?array $subgroupIds,
22        public readonly ?array $companyIds,
23        public readonly ?string $currentRole,
24        public readonly ?string $companyId,
25        public readonly ?array $adminGroupIds,
26        public readonly ?int $monthPeriod,
27        public readonly ?bool $userDefined = false,
28        public readonly ?string $feature = null,
29        public readonly ?bool $isTop5 = false,
30    ) {}
31}
32
33class SharedReportingService extends AccountCenterReporting
34{
35    use FlyMsgAITrackingTrait, HubspotPropertiesTrait, ShortcutTrait;
36
37    public function findShortcut(FindUsersOverviewFilter $filter)
38    {
39        $users = $this->findUsers($filter);
40        $usersActive = $users->whereStatus('Active');
41        $userIds = $usersActive->pluck('id')->toArray();
42        $shortcut = $this->getShortcutByUsers($userIds, $filter->userDefined, $filter->fromDate, $filter->toDate);
43
44        if ($filter->isTop5) {
45            $user_usage_counts = $shortcut->groupBy('user_id')->map(function ($userFlycuts) {
46                return $userFlycuts->count();
47            })->sortDesc();
48            $top_users = $this->getTopUsers($user_usage_counts, $usersActive->get());
49
50            return $top_users;
51        }
52
53        $total_users = $users->count();
54        $total_shortcut_created = $shortcut->count();
55        $months = $filter->toDate->diffInMonths($filter->fromDate);
56        $average_shortcut_per_active_user = $total_users > 0 ? $total_shortcut_created / $total_users : 0;
57        $chart = $this->buildLineChartDataForCounts($shortcut, $months);
58
59        return [
60            'chart' => $chart,
61            'total_shortcut_created' => $total_shortcut_created,
62            'average_shortcut_per_active_user' => $average_shortcut_per_active_user,
63        ];
64    }
65
66    public function findFlyMsgAI(FindUsersOverviewFilter $filter)
67    {
68        $users = $this->findUsers($filter);
69        $usersActive = $users->whereStatus('Active');
70        $userIds = $usersActive->pluck('id')->toArray();
71        $flyMsgAI = $this->getFlyMsgAIByUsers($userIds, $filter->feature, $filter->fromDate, $filter->toDate);
72        if ($filter->isTop5) {
73            $user_usage_counts = $flyMsgAI->groupBy('user_id')->map(function ($flyMsgAIs) {
74                return $flyMsgAIs->count();
75            })->sortDesc();
76            $top_users = $this->getTopUsers($user_usage_counts, $usersActive->get());
77
78            return $top_users;
79        }
80        $total_users = $users->count();
81        $total_flymsg_ai = $flyMsgAI->count();
82        $months = $filter->toDate->diffInMonths($filter->fromDate);
83        $average_flymsgai_per_active_user = $total_users > 0 ? $total_flymsg_ai / $total_users : 0;
84        $chart = $this->buildLineChartDataForCounts($flyMsgAI, $months);
85
86        return [
87            'chart' => $chart,
88            'total_flymsg_ai' => $total_flymsg_ai,
89            'average_flymsgai_per_active_user' => $average_flymsgai_per_active_user,
90        ];
91    }
92
93    protected function findUsers(FindUsersOverviewFilter $filter, $global = false)
94    {
95        $user_ids = $filter->userIds;
96        $group_ids = $filter->groupIds;
97        $subgroup_ids = $filter->subgroupIds;
98        $role = $filter->currentRole;
99        $adminGroupIds = $filter->adminGroupIds;
100        $companyId = $filter->companyId;
101
102        $users = User::select([
103            'id',
104            'first_name',
105            'last_name',
106            'company_group_id',
107            'company_id',
108            'deleted_at',
109            'deactivated_at',
110            'status',
111            'created_at',
112            'avatar',
113        ]);
114
115        if (! $global) {
116            if (count($user_ids) > 0) {
117                $users = $users->whereIn('_id', $user_ids);
118            }
119
120            if (count($group_ids) > 0) {
121                $hasNotAssignedGroup = in_array('-1', $group_ids);
122
123                if ($hasNotAssignedGroup) {
124                    $users = $users->whereNull('company_group_id');
125                }
126
127                $group_ids = array_filter($group_ids, function ($value) {
128                    return $value !== '' && $value !== '-1';
129                });
130
131                $users = $users->whereIn('company_group_id', $group_ids);
132            }
133
134            if (count($subgroup_ids) > 0) {
135                $users = $users->whereIn('company_group_id', $subgroup_ids);
136            }
137        }
138
139        if ($companyId) {
140            return $this->filterUsersByCompany($users, $role, $group_ids, $companyId, $adminGroupIds, $global);
141        }
142
143        return $users;
144    }
145
146    protected function findInactiveUsers(FindUsersOverviewFilter $filter)
147    {
148        $start_date = $filter->fromDate;
149        $end_date = $filter->toDate;
150        $user_ids = $filter->userIds;
151        $group_ids = $filter->groupIds;
152        $subgroup_ids = $filter->subgroupIds;
153        $role = $filter->currentRole;
154        $adminGroupIds = $filter->adminGroupIds;
155        $companyId = $filter->companyId;
156
157        $inactive_users = User::whereNotNull('deactivated_at');
158
159        if (count($user_ids) > 0) {
160            $inactive_users = $inactive_users->whereIn('_id', $user_ids);
161        }
162
163        if (count($group_ids) > 0) {
164            $inactive_users = $inactive_users->whereIn('company_group_id', $group_ids);
165        }
166
167        if (count($subgroup_ids) > 0) {
168            $inactive_users = $inactive_users->whereIn('company_group_id', $subgroup_ids);
169        }
170
171        $inactive_users = $this->filterUsersByCompany($inactive_users, $role, $group_ids, $companyId, $adminGroupIds);
172
173        if ($start_date && $end_date) {
174            $inactive_users = $inactive_users->whereBetween('created_at', [
175                new UTCDateTime($start_date->getTimestamp() * 1000),
176                new UTCDateTime($end_date->getTimestamp() * 1000),
177            ]);
178        }
179
180        return $inactive_users;
181    }
182
183    protected function findLicensesUsage(bool $vengresoAdmin, $userIds, bool $onlyActiveUsers)
184    {
185        if ($vengresoAdmin) {
186            $userPipelineMatch = [
187                '$match' => [
188                    'company_id' => ['$exists' => true, '$ne' => null],
189                ],
190            ];
191        } else {
192            $userPipelineMatch = [
193                '$match' => [
194                    '_id' => [
195                        '$in' => array_map(function ($userId) {
196                            return new \MongoDB\BSON\ObjectId($userId);
197                        }, $userIds),
198                    ],
199                ],
200            ];
201
202            if ($onlyActiveUsers) {
203                $userPipelineMatch['$match']['status'] = ['$ne' => 'Invited'];
204            }
205        }
206
207        $results = User::raw(function ($collection) use ($userPipelineMatch) {
208            return $collection->aggregate([
209                $userPipelineMatch,
210                [
211                    '$lookup' => [
212                        'from' => 'subscriptions',
213                        'let' => ['userId' => ['$toString' => '$_id']],
214                        'pipeline' => [
215                            [
216                                '$match' => [
217                                    '$expr' => [
218                                        '$and' => [
219                                            ['$eq' => ['$user_id', '$$userId']],
220                                        ],
221                                    ],
222                                    'name' => 'main',
223                                    'stripe_status' => 'active',
224                                ],
225                            ],
226                            [
227                                '$sort' => ['created_at' => -1],
228                            ],
229                            [
230                                '$group' => [
231                                    '_id' => '$user_id',
232                                    'most_recent_subscription' => ['$first' => '$$ROOT'],
233                                ],
234                            ],
235                            [
236                                '$lookup' => [
237                                    'from' => 'plans',
238                                    'let' => ['stripeId' => '$most_recent_subscription.stripe_plan'],
239                                    'pipeline' => [
240                                        [
241                                            '$match' => [
242                                                '$expr' => [
243                                                    '$eq' => ['$stripe_id', '$$stripeId'],
244                                                ],
245                                            ],
246                                        ],
247                                        [
248                                            '$project' => [
249                                                'title' => 1,
250                                                'identifier' => 1,
251                                            ],
252                                        ],
253                                    ],
254                                    'as' => 'plan',
255                                ],
256                            ],
257                        ],
258                        'as' => 'sub',
259                    ],
260                ],
261                [
262                    '$unwind' => [
263                        'path' => '$sub',
264                        'preserveNullAndEmptyArrays' => true,
265                    ],
266                ],
267                [
268                    '$unwind' => [
269                        'path' => '$sub.plan',
270                        'preserveNullAndEmptyArrays' => true,
271                    ],
272                ],
273                [
274                    '$group' => [
275                        '_id' => '$sub.plan.identifier',
276                        'count' => ['$sum' => 1],
277                    ],
278                ],
279                [
280                    '$project' => [
281                        'identifier' => '$_id',
282                        'count' => 1,
283                        '_id' => 0,
284                    ],
285                ],
286            ]);
287        });
288
289        return $results->toArray();
290    }
291
292    protected function findActiveLicensesUsage(bool $vengresoAdmin, FindUsersOverviewFilter $filter)
293    {
294        $users = $this->findUsers($filter);
295
296        $userIds = $users->whereStatus('Active')->pluck('id')->toArray();
297
298        $licensesUsed = $this->findLicensesUsage($vengresoAdmin, $userIds, true);
299
300        return array_reduce($licensesUsed, function ($carry, $item) {
301            if (! isset($item['identifier'])) {
302                return $carry;
303            }
304
305            return $carry + $item['count'];
306        }, 0);
307    }
308
309    protected function findLicensesAssigned(FindUsersOverviewFilter $filter, $global, bool $vengresoAdmin)
310    {
311        $users = $this->findUsers($filter, $global);
312
313        $userIds = $users->pluck('id')->toArray();
314
315        $licensesUsed = $this->findLicensesUsage($vengresoAdmin, $userIds, false);
316
317        $licensesAssigned = array_reduce($licensesUsed, function ($carry, $item) {
318            if (! isset($item['identifier'])) {
319                return $carry;
320            }
321
322            return $carry + $item['count'];
323        }, 0);
324
325        $licensesInvited = $this->findInvitationsAssigned($filter, $global);
326
327        return $licensesAssigned + $licensesInvited;
328    }
329
330    protected function findInvitationsAssigned(FindUsersOverviewFilter $filter, $global)
331    {
332        $group_ids = $filter->groupIds;
333        $subgroup_ids = $filter->subgroupIds;
334        $companyId = $filter->companyId;
335
336        $invitations = AdminUserInvitation::whereNull('deleted_at')->whereNotNull('plan_id');
337
338        if (! $global) {
339            if (count($group_ids) > 0) {
340                $invitations = $invitations->whereIn('company_group_id', $group_ids);
341            }
342
343            if (count($subgroup_ids) > 0) {
344                $invitations = $invitations->whereIn('company_subgroup_id', $subgroup_ids);
345            }
346        }
347
348        if ($companyId) {
349            $invitations = $invitations->where('company_id', $companyId);
350        }
351
352        return $invitations->count();
353    }
354
355    protected function getTopUsers($user_usage_counts, $users)
356    {
357        return $user_usage_counts->take(5)->map(function ($count, $user_id) use ($users) {
358            $user = $this->getUserById($users, $user_id);
359
360            return $this->formatUser($user, $count);
361        });
362    }
363
364    protected function getUserById($users, $user_id)
365    {
366        return $users->firstWhere('_id', $user_id);
367    }
368
369    protected function formatUser($user, $count)
370    {
371        return [
372            'name' => $user->first_name.' '.$user->last_name,
373            'count' => $count,
374            'image' => $user->avatar,
375        ];
376    }
377}