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