Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.25% covered (danger)
0.25%
1 / 399
4.55% covered (danger)
4.55%
1 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
CompanyService
0.25% covered (danger)
0.25%
1 / 399
4.55% covered (danger)
4.55%
1 / 22
2632.49
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 updateCompany
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
110
 getCompanyBySlug
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupBySlug
0.00% covered (danger)
0.00%
0 / 152
0.00% covered (danger)
0.00%
0 / 1
2
 addCompany
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 deactivateCompany
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 deactivateCompanies
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 deleteCompanies
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 deleteCompany
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
72
 updateCompanyInstancy
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 createInstancyUser
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 createGroupInstancy
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 createCompany
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 createLicenses
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 handleSubscription
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 processPocs
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
12
 createNewUser
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 updateExistingUser
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 createPoc
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 sendInvitationMail
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
12
 createAdminUserInvitation
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
 createSubscription
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Http\Services\Admin\Companies;
4
5use App\Events\User\Registered;
6use App\Helpers\DateHelper;
7use App\Helpers\FlyMSGLogger;
8use App\Http\Models\Admin\AdminUserInvitation;
9use App\Http\Models\Admin\Company;
10use App\Http\Models\Admin\CompanyGroup;
11use App\Http\Models\Admin\CompanyLicenses;
12use App\Http\Models\Auth\Role;
13use App\Http\Models\Auth\User;
14use App\Http\Models\Plans;
15use App\Http\Repositories\InstancyRepository;
16use App\Http\Services\Admin\Users\AdminUsersService;
17use App\Http\Services\InstancyServiceV2;
18use App\Mail\GlobalAdminInvitationExistentUserMail;
19use App\Mail\GlobalAdminInvitationMail;
20use App\Services\Email\EmailService;
21use Carbon\Carbon;
22use Illuminate\Support\Facades\DB;
23use Illuminate\Support\Str;
24use stdClass;
25
26class CompanyService
27{
28    protected $instancyRepository;
29
30    public function __construct(
31        InstancyRepository $instancyRepository,
32        private readonly AdminUsersService $adminUsersService,
33        private readonly EmailService $emailService
34    ) {
35        $this->instancyRepository = $instancyRepository;
36    }
37
38    public function updateCompany(Company $company, array $data): Company
39    {
40        $session = DB::getMongoClient()->startSession();
41        $session->startTransaction();
42
43        $company->update([
44            'name' => $data['company_name'],
45            'slug' => Str::slug($data['company_name']),
46            'address_line_1' => $data['company_address_line1'],
47            'address_line_2' => $data['company_address_line2'],
48            'city' => $data['city'],
49            'state' => $data['state'],
50            'zip' => $data['zip_code'],
51            'country' => $data['country'],
52        ]);
53
54        try {
55            $this->updateCompanyInstancy($company->instancy_id, $data['company_name']);
56        } catch (\Exception $e) {
57            FlyMSGLogger::logError(__METHOD__.' Update Company on Instancy', $e);
58        }
59
60        // / update company licenses
61        $termOfContract = $data['term_of_contract'] > 0 ? $data['term_of_contract'] : $data['custom_term_of_contract'];
62
63        $license = $company->licenses()->active()->first() ?? $company->licenses()->latest()->first();
64
65        $contractEndDate = Carbon::parse($data['contract_end_date']);
66
67        if (filled($data['extend_contract_end_date']) && $data['extend_contract_end_date'] > 0) {
68            $contractEndDate = $contractEndDate->addDays($data['extend_contract_end_date']);
69        }
70
71        // Update contract end date for all active users of the company
72        foreach ($company->users as $user) {
73            if (! empty($user->status) && strtolower($user->status) != 'active') {
74                continue;
75            }
76
77            $userSub = $user->subscriptions()->latest()->first();
78            if (isset($userSub->stripe_status) && $userSub->stripe_status == 'active') {
79                $userSub->ends_at = $contractEndDate->format('Y-m-d H:i:s');
80                $userSub->save();
81            }
82
83            // Update instancy membership details
84            $instancyService = new InstancyServiceV2;
85            $instancyService->updateMembership($user->email);
86        }
87
88        $license->update([
89            'term_of_contract' => DateHelper::getLabelByInterval($termOfContract),
90            'contract_start_date' => Carbon::parse($data['contract_start_date'])->format('Y-m-d H:i:s'),
91            'contract_end_date' => $contractEndDate->format('Y-m-d H:i:s'),
92            'business_pro_enterprise_plus' => $data['business_pro_enterprise_plus'],
93            'auto_renew_license' => $data['auto_renewal'],
94            'total_growth_license_count' => $data['growth'],
95            'total_starter_license_count' => $data['starter'],
96            'total_sales_pro_license_count' => $data['sales_pro'],
97            'total_sales_pro_teams_license_count' => $data['sales_pro_teams_smb'],
98        ]);
99
100        $session->commitTransaction();
101
102        return $company;
103    }
104
105    public function getCompanyBySlug(string $slug)
106    {
107        return Company::with(['licenses' => function ($query) {
108            $query->active();
109        }])->firstWhere('slug', $slug);
110    }
111
112    public function getGroupBySlug(string $company_id, string $slug)
113    {
114        $pipeline = [
115            [
116                '$match' => [
117                    'company_id' => $company_id,
118                    '$expr' => [
119                        '$eq' => [
120                            $slug,
121                            [
122                                '$toLower' => [
123                                    '$replaceAll' => [
124                                        'input' => '$name',
125                                        'find' => ' ',
126                                        'replacement' => '-',
127                                    ],
128                                ],
129                            ],
130                        ],
131                    ],
132                ],
133            ],
134            [
135                '$addFields' => [
136                    'converted_group_id' => ['$toString' => '$_id'],
137                ],
138            ],
139            [
140                '$lookup' => [
141                    'from' => 'company_groups',
142                    'localField' => 'converted_group_id',
143                    'foreignField' => 'parent_id',
144                    'as' => 'subgroups_data',
145                ],
146            ],
147            [
148                '$addFields' => [
149                    'all_group_and_subgroup_ids' => [
150                        '$concatArrays' => [
151                            ['$converted_group_id'],
152                            [
153                                '$map' => [
154                                    'input' => '$subgroups_data',
155                                    'as' => 's',
156                                    'in' => ['$toString' => '$$s._id'],
157                                ],
158                            ],
159                        ],
160                    ],
161                ],
162            ],
163            [
164                '$lookup' => [
165                    'from' => 'users',
166                    'localField' => 'all_group_and_subgroup_ids',
167                    'foreignField' => 'company_group_id',
168                    'as' => 'all_relevant_users',
169                ],
170            ],
171            [
172                '$addFields' => [
173                    'subgroups' => [
174                        '$map' => [
175                            'input' => '$subgroups_data',
176                            'as' => 'sub',
177                            'in' => [
178                                'id' => ['$toString' => '$$sub._id'],
179                                'name' => '$$sub.name',
180                                'slug' => [
181                                    '$toLower' => [
182                                        '$replaceAll' => [
183                                            'input' => '$$sub.name',
184                                            'find' => ' ',
185                                            'replacement' => '-',
186                                        ],
187                                    ],
188                                ],
189                                'users_count' => [
190                                    '$size' => [
191                                        '$filter' => [
192                                            'input' => '$all_relevant_users',
193                                            'as' => 'user',
194                                            'cond' => [
195                                                '$eq' => ['$$user.company_group_id', ['$toString' => '$$sub._id']],
196                                            ],
197                                        ],
198                                    ],
199                                ],
200                            ],
201                        ],
202                    ],
203                    'users_in_main_group' => [
204                        '$filter' => [
205                            'input' => '$all_relevant_users',
206                            'as' => 'user',
207                            'cond' => [
208                                '$and' => [
209                                    ['$eq' => ['$$user.company_group_id', '$converted_group_id']],
210                                    [
211                                        '$not' => [
212                                            '$in' => [
213                                                '$$user.company_group_id',
214                                                [
215                                                    '$map' => [
216                                                        'input' => '$subgroups_data',
217                                                        'as' => 's',
218                                                        'in' => ['$toString' => '$$s._id'],
219                                                    ],
220                                                ],
221                                            ],
222                                        ],
223                                    ],
224                                ],
225                            ],
226                        ],
227                    ],
228                ],
229            ],
230            [
231                '$project' => [
232                    '_id' => 0,
233                    'id' => '$_id',
234                    'name' => 1,
235                    'company_id' => 1,
236                    'slug' => [
237                        '$toLower' => [
238                            '$replaceAll' => [
239                                'input' => '$name',
240                                'find' => ' ',
241                                'replacement' => '-',
242                            ],
243                        ],
244                    ],
245                    'subgroups' => [
246                        '$concatArrays' => [
247                            '$subgroups',
248                            [
249                                [
250                                    'id' => null,
251                                    'name' => 'Not Assigned',
252                                    'slug' => 'not-assigned',
253                                    'users_count' => ['$size' => '$users_in_main_group'],
254                                ],
255                            ],
256                        ],
257                    ],
258                    'total_group_users_count' => ['$size' => '$all_relevant_users'],
259                ],
260            ],
261        ];
262
263        $results = CompanyGroup::raw(function ($collection) use ($pipeline) {
264            return $collection->aggregate($pipeline);
265        });
266
267        return $results->first();
268    }
269
270    public function addCompany($data): Company
271    {
272        $session = DB::getMongoClient()->startSession();
273        $session->startTransaction();
274        try {
275            $company = $this->createCompany($data['company']);
276            $companyLicense = $this->createLicenses($company, $data['company']);
277            $this->processPocs($company, $data['pocs'], $companyLicense);
278
279            $this->createInstancyUser($data['pocs'], $company->instancy_id);
280
281            $session->commitTransaction();
282
283            return $company;
284        } catch (\Exception $e) {
285            $session->abortTransaction();
286            FlyMSGLogger::logError(__METHOD__, $e);
287            throw $e;
288        }
289    }
290
291    public function deactivateCompany(Company $company, string $adminId, $cancellation_date = null)
292    {
293        // Not necessary to deactivate users when deactivating a company
294        $users = User::where('company_id', $company->id)->get();
295
296        foreach ($users as $user) {
297            $this->adminUsersService->deactivateUser($user, $adminId, $company->id, $cancellation_date);
298        }
299
300        $company->deactivated_at = Carbon::now();
301        $company->save();
302    }
303
304    public function deactivateCompanies($companyIds, string $adminId)
305    {
306        $companies = Company::whereIn('_id', $companyIds)->get();
307
308        foreach ($companies as $company) {
309            $this->deactivateCompany($company, $adminId);
310        }
311    }
312
313    public function deleteCompanies(array $companyIds, User $admin)
314    {
315        if (in_array($admin->company_id, $companyIds)) {
316            throw new UnprocessableEntityHttpException('You cannot delete your own company.');
317        }
318
319        $companies = Company::whereIn('_id', $companyIds)->get();
320
321        foreach ($companies as $company) {
322            $this->deleteCompany($company, $admin);
323        }
324    }
325
326    public function deleteCompany(Company $company, User $admin)
327    {
328        if (isset($admin->company_id) && $admin->company_id === $company->id) {
329            throw new UnprocessableEntityHttpException('You cannot delete your own company.');
330        }
331
332        AdminUserInvitation::where('company_id', $company->id)->delete();
333        $company->licenses()->delete();
334        $company->pocs()->delete();
335        $company->groupsAndSubgroups()->delete();
336
337        foreach ($company->users as $user) {
338            $user->removeAllRoles();
339
340            $userSub = $user->subscription('main');
341            if ($userSub) {
342                if ($user->company_id) {
343                    if ($userSub->plan->identifier != Plans::FREEMIUM_IDENTIFIER) {
344                        $userSub->markAsCancelledOnlyInDB();
345                    }
346                } else {
347                    if ($userSub->plan->identifier != Plans::FREEMIUM_IDENTIFIER) {
348                        $userSub->cancel();
349                    }
350                }
351            }
352
353            $user->unset('company_id');
354            $user->unset('company_group_id');
355            $user->unset('invited_to_company');
356            $user->unset('invited_to_company_by_admin');
357            $user->save();
358        }
359
360        $company->delete();
361    }
362
363    private function updateCompanyInstancy($instancyId, $name)
364    {
365        $newInstancyGroup = new stdClass;
366        $newInstancyGroup->groupId = $instancyId;
367        $newInstancyGroup->name = $name;
368
369        return $this->instancyRepository->updateGroup($newInstancyGroup);
370    }
371
372    private function createInstancyUser(array $pocs, string $groupId)
373    {
374        collect($pocs)->map(function ($poc) use ($groupId) {
375            (new InstancyServiceV2)->createInstancyUser($poc['email'], $groupId);
376        });
377    }
378
379    private function createGroupInstancy($companyName)
380    {
381        $newInstancyGroup = new stdClass;
382        $newInstancyGroup->name = $companyName;
383
384        return $this->instancyRepository->createGroup($newInstancyGroup);
385    }
386
387    private function createCompany(array $companyData): Company
388    {
389        $instancyId = $this->createGroupInstancy($companyData['company_name']);
390
391        return Company::create([
392            'name' => $companyData['company_name'],
393            'slug' => Str::slug($companyData['company_name']),
394            'address_line_1' => $companyData['company_address_line1'],
395            'address_line_2' => $companyData['company_address_line2'],
396            'city' => $companyData['city'],
397            'state' => $companyData['state'],
398            'zip' => $companyData['zip_code'],
399            'country' => $companyData['country'],
400            'instancy_id' => $instancyId,
401        ]);
402    }
403
404    private function createLicenses(Company $company, array $companyData): CompanyLicenses
405    {
406        $termOfContract = $companyData['term_of_contract'] > 0
407            ? $companyData['term_of_contract']
408            : $companyData['custom_term_of_contract'];
409
410        $companyLicenseCreated = $company->licenses()->create([
411            'term_of_contract' => DateHelper::getLabelByInterval($termOfContract),
412            'contract_start_date' => Carbon::parse($companyData['contract_start_date'])->format('Y-m-d H:i:s'),
413            'contract_end_date' => Carbon::parse($companyData['contract_end_date'])->format('Y-m-d H:i:s'),
414            'auto_renew_license' => $companyData['auto_renewal'],
415            'business_pro_enterprise_plus' => $companyData['business_pro_enterprise_plus'],
416            'total_growth_license_count' => $companyData['growth'],
417            'total_starter_license_count' => $companyData['starter'],
418            'total_sales_pro_license_count' => $companyData['sales_pro'],
419            'total_growth_license_remaining' => $companyData['growth'],
420            'total_starter_license_remaining' => $companyData['starter'],
421            'total_sales_pro_license_remaining' => $companyData['sales_pro'],
422            'total_sales_pro_teams_license_count' => $companyData['sales_pro_teams_smb'],
423            'total_sales_pro_teams_license_remaining' => $companyData['sales_pro_teams_smb'],
424        ]);
425
426        return $companyLicenseCreated;
427    }
428
429    private function handleSubscription(User $user, string $contractEndDate, string $stripeId, ?User $existingUser): void
430    {
431        if ($existingUser) {
432            $userSub = $user->subscription('main');
433            if ($userSub) {
434                $userSub->cancel();
435            }
436        }
437
438        $this->createSubscription($contractEndDate, $stripeId, $user);
439    }
440
441    private function processPocs(Company $company, array $pocs, CompanyLicenses $companyLicense): void
442    {
443        $admin = auth()->user();
444
445        collect($pocs)->map(function ($poc, $index) use ($company, $companyLicense, $admin) {
446            $existingUser = User::firstWhere('email', $poc['email']);
447            $password = Str::password(16);
448            $hashed_password = bcrypt($password);
449            $tempPasswordExpiry = Carbon::now()->addDays(7);
450
451            $data = [
452                'email' => $poc['email'],
453                'first_name' => $poc['first_name'],
454                'last_name' => $poc['last_name'],
455                'company_id' => $company->id,
456                'company_group_id' => null,
457                'company_subgroup_id' => null,
458                'temp_password_expiry' => $tempPasswordExpiry,
459                'temp_password' => $hashed_password,
460                'password' => $hashed_password,
461                'admin_email' => $admin->email,
462                'email_verified_at' => Carbon::now(),
463                'invited_to_company' => true,
464                'invited_to_company_by_admin' => $admin->email,
465            ];
466            $user = $existingUser ?? $this->createNewUser($data);
467
468            $plan = Plans::find($poc['plan_id']);
469            $stripeId = $plan->stripe_id;
470
471            $this->handleSubscription($user, $companyLicense->contract_end_date, $stripeId, $existingUser);
472
473            if ($existingUser) {
474                $this->updateExistingUser($existingUser, $data);
475            }
476
477            $this->createPoc($company, $poc, $user->id, $index === 0 ? true : false);
478            $this->createAdminUserInvitation($user, $hashed_password, $plan);
479            $this->sendInvitationMail($poc['email'], $user, filled($existingUser), $password);
480        });
481    }
482
483    private function createNewUser(array $poc): User
484    {
485        $user = new User;
486        $user->fill($poc);
487        $user->selected_plan = 'Sales Pro Teams';
488        $user->status = 'Invited';
489        $user->save();
490        $user->assignRole(Role::GLOBAL_ADMIN, []);
491        $user->onboardingv2_presented = true;
492
493        Registered::dispatch($user, $poc);
494
495        return $user;
496    }
497
498    private function updateExistingUser(User $existingUser, array $userData): void
499    {
500        $existingUser->update([
501            'company_id' => $userData['company_id'],
502            'company_group_id' => null,
503            'status' => 'Invited',
504            'invited_to_company' => true,
505            'invited_to_company_by_admin' => $userData['invited_to_company_by_admin'],
506        ]);
507        $existingUser->assignRole(Role::GLOBAL_ADMIN, []);
508    }
509
510    private function createPoc(Company $company, array $poc, string $userId, bool $isRoot): void
511    {
512        $company->pocs()->create([
513            'email' => $poc['email'],
514            'first_name' => $poc['first_name'],
515            'last_name' => $poc['last_name'],
516            'user_id' => $userId,
517            'root' => $isRoot,
518        ]);
519    }
520
521    public function sendInvitationMail(string $email, User $user, bool $isExistingUser, string $password): void
522    {
523        $inviter = auth()->user()->email;
524        $tempPasswordExpiry = Carbon::now()->addDays(7);
525
526        try {
527            if ($isExistingUser) {
528                $this->emailService->send(
529                    $email,
530                    new GlobalAdminInvitationExistentUserMail(
531                        $email,
532                        $user->first_name,
533                        $user->last_name,
534                        $inviter,
535                        $password,
536                        $tempPasswordExpiry->format('m/d/Y').' at '.$tempPasswordExpiry->format('h:i A'),
537                        $user->avatar ?? ''
538                    ),
539                    'cac_invite_existing_user'
540                );
541            } else {
542                $this->emailService->send(
543                    $email,
544                    new GlobalAdminInvitationMail(
545                        $email,
546                        $user->first_name,
547                        $user->last_name,
548                        $inviter,
549                        $password,
550                        $tempPasswordExpiry->format('m/d/Y').' at '.$tempPasswordExpiry->format('h:i A')
551                    ),
552                    'cac_invite_user'
553                );
554            }
555        } catch (\Exception $e) {
556            FlyMSGLogger::logError(__METHOD__, $e);
557        }
558    }
559
560    private function createAdminUserInvitation(User $user, string $hashed_password, ?Plans $plan = null): void
561    {
562        $data = [
563            'role_name' => Role::GLOBAL_ADMIN,
564            'email' => $user->email,
565            'company_id' => $user->company_id,
566            'company_group_id' => $user->group_id,
567            'company_subgroup_id' => $user->subgroup_id,
568            'temp_password_expiry' => $user->temp_password_expiry,
569            'temp_password' => $hashed_password,
570            'password' => $hashed_password,
571            'admin_email' => auth()->user()->email,
572        ];
573
574        if ($plan) {
575            $data['plan'] = $plan;
576            $data['plan_id'] = $plan->id;
577        }
578
579        AdminUserInvitation::updateOrCreate(['email' => $user->email], $data);
580    }
581
582    private function createSubscription($endDate, string $stripeId, User $user)
583    {
584        $user->subscriptions()->create([
585            'name' => 'main',
586            'stripe_status' => 'active',
587            'stripe_plan' => $stripeId,
588            'user_id' => $user->id,
589            'quantity' => '1',
590            'ends_at' => $endDate,
591            'starts_at' => Carbon::now()->toDateTimeString(),
592        ]);
593    }
594}