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