Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 228
0.00% covered (danger)
0.00%
0 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
ClientManagementUsersController
0.00% covered (danger)
0.00%
0 / 228
0.00% covered (danger)
0.00%
0 / 15
1406
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
 users
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
30
 usersCategory
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 createUserManually
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 createUserByEmails
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 resendInvitation
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 updateUser
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 resetPassword
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 assignRole
0.00% covered (danger)
0.00%
0 / 75
0.00% covered (danger)
0.00%
0 / 1
210
 moveToInvitedUsers
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
2
 moveToUsers
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 exportUsersCsv
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 deactivateUsers
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 deleteUsers
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 unassignUsers
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Http\Controllers\v1\AdminPortal;
4
5use App\Enums\HttpStatusCode;
6use App\Exceptions\ExpectedException;
7use App\Exports\ClientManagementUsersExport;
8use App\Helpers\FlyMSGLogger;
9use App\Http\Controllers\Controller;
10use App\Http\Models\Admin\CompanyGroup;
11use App\Http\Models\Auth\Role;
12use App\Http\Models\Auth\User;
13use App\Http\Models\Auth\UserRole;
14use App\Http\Models\Plans;
15use App\Http\Models\SalesProTeamManager;
16use App\Http\Requests\AssignMoveUserToCompanyRequest;
17use App\Http\Requests\AssignRoleRequest;
18use App\Http\Requests\CreateUserByEmailRequest;
19use App\Http\Requests\CreateUserManuallyRequest;
20use App\Http\Requests\ResendInvitationRequest;
21use App\Http\Requests\ResetPasswordRequest;
22use App\Http\Requests\UpdateUserRequest;
23use App\Http\Requests\UsersCategoryFilterRequest;
24use App\Http\Resources\ClientManagementInvitedUserResource;
25use App\Http\Resources\ClientManagementUserResource;
26use App\Http\Resources\CMCEditUserResource;
27use App\Http\Services\Admin\Users\AdminUsersService;
28use App\Http\Services\ClientManagementInvitedUserService;
29use App\Http\Services\ClientManagementUsersService;
30use App\Http\Services\CsvExportService;
31use Illuminate\Http\JsonResponse;
32use Illuminate\Http\Request;
33use Illuminate\Validation\Rule;
34
35class ClientManagementUsersController extends Controller
36{
37    public function __construct(
38        private AdminUsersService $adminUsersService,
39        public ClientManagementUsersService $clientManagementUsersService,
40        public ClientManagementInvitedUserService $clientManagementInvitedUserService,
41        private CsvExportService $csvExportService
42    ) {}
43
44    public function users(Request $request): JsonResponse
45    {
46        $deactivated = $request->has('deactivated') && $request->deactivated == 'true';
47        $perPage = intval(request('perPage', 0));
48        $page = intval(request('page', 1));
49        $filter = request('filter', '');
50        $categories = request('categories', '');
51        $license_types = request('selectedSubscriptionTypes', '');
52        $account_status_list = request('selectedAccountStatus', '');
53        $sortBy = request('sort_by', '');
54        $sortOrder = request('sort_order', '');
55
56        if ($categories) {
57            $categories = explode(',', urldecode($categories));
58        } else {
59            $categories = [];
60        }
61
62        if ($license_types) {
63            $license_types = explode(',', urldecode($license_types));
64        } else {
65            $license_types = [];
66        }
67
68        $account_status = [$account_status_list];
69
70        if (strpos($account_status_list, ',') !== false) {
71            $account_status = explode(',', $account_status_list);
72        }
73
74        $userData = $this->clientManagementUsersService->getUsersPaginated($filter, $deactivated, $perPage, $page, $categories, $sortBy, $sortOrder, $license_types, $account_status);
75
76        return response()->json(
77            data: [
78                'success' => true,
79                'data' => [
80                    'items' => ClientManagementUserResource::collection($userData['users']),
81                    'total' => $userData['total'],
82                    'total_pages' => $userData['total_pages'],
83                    'current_page' => $userData['current_page'],
84                ],
85            ],
86            status: HttpStatusCode::OK->value
87        );
88    }
89
90    public function usersCategory(UsersCategoryFilterRequest $request): JsonResponse
91    {
92        $validatedData = $request->validated();
93
94        $data = $this->clientManagementUsersService->categorizeUsers($validatedData['status'] ?? null);
95
96        return response()->json([
97            'success' => true,
98            'data' => $data,
99        ]);
100    }
101
102    public function createUserManually(CreateUserManuallyRequest $request): JsonResponse
103    {
104        $validatedData = $request->validated();
105
106        $user = $this->clientManagementUsersService->createUserManually(auth()->user(), $validatedData);
107
108        return response()->json([
109            'success' => true,
110            'data' => (new ClientManagementUserResource($user)),
111        ]);
112    }
113
114    public function createUserByEmails(CreateUserByEmailRequest $request): JsonResponse
115    {
116        $validatedData = $request->validated();
117        $emails = $validatedData['emails'];
118
119        $invitations = $this->clientManagementUsersService->createUserByEmails(auth()->user(), $emails);
120
121        return response()->json([
122            'success' => true,
123            'message' => 'Users invited successfully',
124            'data' => ClientManagementUserResource::collection($invitations),
125        ]);
126    }
127
128    public function resendInvitation(ResendInvitationRequest $request): JsonResponse
129    {
130        $validatedData = $request->validated();
131
132        $this->clientManagementUsersService->resendInvitations(auth()->user(), $validatedData['emails']);
133
134        return response()->json([
135            'success' => true,
136            'message' => 'Invitation resent',
137        ]);
138    }
139
140    public function updateUser(UpdateUserRequest $request): JsonResponse
141    {
142        $user = User::find($request->user);
143
144        $user->fill($request->validated());
145        $user->save();
146
147        return response()->json([
148            'success' => true,
149            'user' => new ClientManagementUserResource($user),
150        ]);
151    }
152
153    public function resetPassword(ResetPasswordRequest $request): JsonResponse
154    {
155        $validatedData = $request->validated();
156
157        $this->clientManagementUsersService->resetPassword(auth()->user(), $validatedData['user_ids']);
158
159        return response()->json([
160            'success' => true,
161            'message' => 'Password reset successfully. User will get the link to update their password.',
162        ]);
163    }
164
165    public function assignRole(AssignRoleRequest $request, User $user): JsonResponse
166    {
167        $validated_data = $request->validated();
168
169        $role = Role::find($validated_data['role_name']);
170        if (! $role) {
171            $role = Role::where('name', $validated_data['role_name'])->first();
172            if (! $role) {
173                throw new ExpectedException('Role not found');
174            }
175        }
176
177        $adminRole = Role::where('name', Role::GLOBAL_ADMIN)->first();
178
179        if ($role->name != Role::GLOBAL_ADMIN) {
180            $isAdmin = UserRole::where('role_id', $adminRole->id)
181                ->where('user_id', $user->id)
182                ->count() > 0;
183
184            if ($isAdmin) {
185                $roleId = $adminRole->id;
186                $companyId = $user->company_id;
187                $currentUserId = $user->id;
188
189                $exists = UserRole::raw(function ($collection) use ($roleId, $companyId, $currentUserId) {
190                    return $collection->aggregate([
191                        [
192                            '$lookup' => [
193                                'from' => 'users',
194                                'let' => ['userId' => ['$toObjectId' => '$user_id']],
195                                'pipeline' => [
196                                    ['$match' => [
197                                        '$expr' => [
198                                            '$eq' => ['$$userId', '$_id'],
199                                        ],
200                                    ]],
201                                ],
202                                'as' => 'user',
203                            ],
204                        ],
205                        [
206                            '$match' => [
207                                'role_id' => $roleId,
208                                'user.company_id' => $companyId,
209                                'user_id' => ['$ne' => $currentUserId],
210                            ],
211                        ],
212                        [
213                            '$limit' => 1,
214                        ],
215                    ]);
216                });
217
218                $isLastAdmin = empty($exists->toArray());
219
220                if ($isAdmin && $isLastAdmin) {
221                    throw new ExpectedException('Last admin cannot be demoted', 409);
222                }
223            }
224        }
225
226        UserRole::where('user_id', $user->id)->delete();
227
228        UserRole::create([
229            'user_id' => $user->id,
230            'role_id' => $role->id,
231        ]);
232        if ($role->name == Role::GROUP_ADMIN || $role->name = Role::REPORTING_ADMIN) {
233            $group_ids = $validated_data['groups'] ?? [];
234
235            $management_record = $user->sales_pro_team_manager;
236            if (! $management_record) {
237                $management_record = new SalesProTeamManager;
238                $management_record->user_id = $user->id;
239                $management_record->company_id = $user->company_id;
240                $management_record->first_name = $user->first_name;
241                $management_record->last_name = $user->last_name;
242                $management_record->email = $user->email;
243                $management_record->save();
244            }
245
246            if (! empty($group_ids)) {
247                $groups = CompanyGroup::whereIn('_id', $group_ids)->get();
248                if ($groups->isNotEmpty()) {
249                    $management_record->groups()->sync($groups);
250                }
251            }
252
253            $new_group_id = ! empty($group_ids) ? $group_ids[0] : null;
254            if ($user->company_group_id !== $new_group_id) {
255                $user->company_group_id = $new_group_id;
256                $user->save();
257            }
258        }
259
260        return response()->json([
261            'success' => true,
262            'message' => 'User role updated successfully',
263            'user' => new CMCEditUserResource($user->fresh()),
264        ]);
265    }
266
267    public function moveToInvitedUsers(Request $request): JsonResponse
268    {
269        $validatedData = $request->validate([
270            'company_id' => 'required|exists:companies,_id',
271            'plan_identifier' => ['required', Rule::in([
272                Plans::STARTER_YEARLY_IDENTIFIER,
273                Plans::GROWTH_YEARLY_IDENTIFIER,
274                Plans::PROFESSIONAL_YEARLY_IDENTIFIER,
275                Plans::ProPlanTeamsSMB,
276                Plans::FREEMIUM_IDENTIFIER,
277            ])],
278            'role_name' => [
279                'required',
280                'string',
281                Rule::in([
282                    Role::USER,
283                    Role::REPORTING_ADMIN,
284                    Role::GROUP_ADMIN,
285                    Role::GLOBAL_ADMIN,
286                    Role::VENGRESO_ADMIN,
287                ]),
288            ],
289            'user_ids' => 'required|array|min:1',
290            'group_id' => 'sometimes|string|exists:company_groups,_id',
291            'subgroup_id' => 'sometimes|string|exists:company_groups,_id',
292        ]);
293
294        $data = $this->clientManagementInvitedUserService->moveToInvitedUsers($validatedData);
295
296        return response()->json([
297            'success' => true,
298            'message' => 'Admin Invitation updated successfully',
299            'data' => ClientManagementInvitedUserResource::collection($data),
300        ]);
301    }
302
303    public function moveToUsers(AssignMoveUserToCompanyRequest $request): JsonResponse
304    {
305        $validatedData = $request->validated();
306        $admin = auth()->user();
307
308        $data = $this->clientManagementUsersService->moveToUsers($validatedData, $admin);
309
310        return response()->json([
311            'success' => true,
312            'message' => 'Users updated successfully',
313            'data' => CMCEditUserResource::collection($data),
314        ]);
315    }
316
317    public function exportUsersCsv(Request $request)
318    {
319        try {
320            $userIds = $request->users ? explode(',', $request->users) : [];
321            $onlyDeactivatedUsers = $request->deactivated == 'true';
322
323            return $this->csvExportService->downloadWithUserDelimiter(
324                auth()->user(),
325                fn () => (new ClientManagementUsersExport($userIds, $onlyDeactivatedUsers))->download('Users.csv')
326            );
327        } catch (\Throwable $th) {
328            // Send dev notification
329            FlyMSGLogger::logError(__METHOD__, $th);
330
331            return response()->json([
332                'success' => false,
333                'message' => $th->getMessage(),
334            ], 500);
335        }
336    }
337
338    public function deactivateUsers(Request $request): JsonResponse
339    {
340        $validatedData = $request->validate([
341            'user_ids' => 'required|array|min:1',
342            'user_ids.*' => ['required', function ($attribute, $value, $fail) {
343                // Disallow deleting user own account
344                if ($value == auth()->user()->id) {
345                    return $fail('You cannot deactivate your own account.');
346                }
347
348                // Check if user exists in either 'users' or 'admin_user_invitations'
349                $existsInUsers = \DB::table('users')->where('_id', $value)->exists();
350                $existsInInvites = \DB::table('admin_user_invitations')->where('_id', $value)->exists();
351
352                if (! ($existsInUsers || $existsInInvites)) {
353                    return $fail("The user ID {$value} does not exist in users or admin_user_invitations.");
354                }
355            }],
356        ]);
357
358        $this->adminUsersService->deactivateUserBulk($validatedData['user_ids'], auth()->user()->id, '');
359
360        return response()->json([
361            'success' => true,
362            'message' => 'Users deactivated successfully',
363        ]);
364    }
365
366    public function deleteUsers(Request $request): JsonResponse
367    {
368        $validatedData = $request->validate([
369            'users' => 'required',
370        ]);
371
372        $this->adminUsersService->deleteUsers($validatedData['users'], auth()->user(), null);
373
374        return response()->json([
375            'success' => true,
376            'message' => 'Users deleted successfully',
377        ]);
378    }
379
380    public function unassignUsers(Request $request): JsonResponse
381    {
382        $validatedData = $request->validate([
383            'users' => 'required',
384        ]);
385
386        $this->adminUsersService->unassignUsers($validatedData['users'], auth()->user(), null);
387
388        return response()->json([
389            'success' => true,
390            'message' => 'User unassigned successfully',
391        ]);
392    }
393}