Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
3.87% covered (danger)
3.87%
11 / 284
26.19% covered (danger)
26.19%
11 / 42
CRAP
0.00% covered (danger)
0.00%
0 / 1
User
3.87% covered (danger)
3.87%
11 / 284
26.19% covered (danger)
26.19%
11 / 42
3702.25
0.00% covered (danger)
0.00%
0 / 1
 getRoleAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setEmailAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getEmailAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 sendWelcomeNotification
0.00% covered (danger)
0.00%
0 / 95
0.00% covered (danger)
0.00%
0 / 1
272
 sendPasswordResetNotification
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isPasswordSet
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 linkedSocialAccounts
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 fileMetaData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setting
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 shortcuts
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 flyshares
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 shortcutsSharedWithUser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 shortcutsSharedWithOthers
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 flycutUsage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 charts
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 role_play_projects
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 flycutsUsed
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 charactersTyped
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 charactersSaved
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 timeSaved
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getChartData
0.00% covered (danger)
0.00%
0 / 111
0.00% covered (danger)
0.00%
0 / 1
6
 saved_prompts
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 promptUsage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 subscriptionTrials
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 flyMsgAITracking
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getflyMsgAITrackingUsage
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
6
 convertIntegerKeysToWords
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 convertIntegerToWord
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 convertIntegerToOrdinal
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 hubspot_property
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 sales_pro_team_manager
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 pocs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 stripe
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 taxRates
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 planTaxRates
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 company_group
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 company
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 invitation
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNameAttribute
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInvitationLinkForAdminPortal
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isPOC
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newFactory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\Http\Models\Auth;
4
5use App\Http\Helpers\FileManagement\S3\model\FileMetaData;
6use App\Http\Models\Admin\AdminUserInvitation;
7use App\Http\Models\Admin\Company;
8use App\Http\Models\Admin\CompanyGroup;
9use App\Http\Models\Admin\CompanyPOC;
10use App\Http\Models\Auth\BaseUser as Authenticatable;
11use App\Http\Models\Chart;
12use App\Http\Models\ClonedSharedShortcut;
13use App\Http\Models\FlyCutUsage;
14use App\Http\Models\FlyMsgAI\FlyMsgAITracking;
15use App\Http\Models\FlyMsgAI\PromptUsage;
16use App\Http\Models\FlyMsgAI\SavedPrompt;
17use App\Http\Models\FlyMsgUserDailyUsage;
18use App\Http\Models\FlyShare;
19use App\Http\Models\HubspotProperties;
20use App\Http\Models\Invitation;
21use App\Http\Models\RolePlayProjects;
22use App\Http\Models\SalesProTeamManager;
23use App\Http\Models\Setting;
24use App\Http\Models\SharesShortcut;
25use App\Http\Models\Shortcut;
26use App\Http\Models\SubscriptionTrials;
27use App\Http\Models\UserReferral;
28use App\Http\Scopes\UserScope;
29use App\Notifications\User\ResetPassword;
30use App\Notifications\User\Welcome;
31use App\Observers\UserObserver;
32use App\Traits\CustomHasRoles;
33use Carbon\Carbon;
34use Database\Factories\Http\Models\Auth\UserFactory;
35use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
36use Illuminate\Contracts\Auth\MustVerifyEmail;
37use Illuminate\Database\Eloquent\Attributes\ObservedBy;
38use Illuminate\Database\Eloquent\Factories\HasFactory;
39use Illuminate\Database\Eloquent\Relations\HasOne;
40use Illuminate\Notifications\Notifiable;
41use Illuminate\Support\Facades\Cache;
42use Laravel\Cashier\Billable;
43use Laravel\Passport\HasApiTokens;
44use MongoDB\BSON\UTCDateTime;
45use MongoDB\Laravel\Eloquent\SoftDeletes;
46use Mpociot\Teamwork\Traits\UserHasTeams;
47use Stripe\Stripe;
48use Stripe\StripeClient;
49
50/**
51 * @property bool $is_beta Whether the user has access to beta features
52 * @property bool $developer_mode Whether the user has developer mode enabled (overrides global remote config)
53 * @property bool $processed_2026_usage Whether the user has processed 2026 usage
54 * @property string $temp_password Temporary password for invited users
55 * @property string $temp_password_expiry Expiry date/time string for the temporary password
56 * @property string|null $company_id The company ID this user belongs to
57 * @property string|null $company_group_id The company group ID this user belongs to
58 */
59#[ObservedBy([UserObserver::class])]
60class User extends Authenticatable implements AuthenticatableContract, MustVerifyEmail
61{
62    use Billable, CustomHasRoles, HasApiTokens, HasFactory, Notifiable, SoftDeletes, UserHasTeams;
63
64    protected $table = 'users';
65
66    protected $fillable = [
67        'first_name',
68        'last_name',
69        'email',
70        'password',
71        'avatar',
72        'signup_source',
73        'hubspot_id',
74        'verification_code',
75        'created_by',
76        'refered_by',
77        'rewardable',
78        'rewards_level',
79        'referrals_count',
80        'referral_key',
81        'emails',
82        'instancy_id',
83        'sales_pro_manager_id',
84        'company_group_id',
85        'deactivated_at',
86        'status',
87        'activation_date',
88        'temp_password',
89        'temp_password_expiry',
90        'company_id',
91        'invited_to_company_by_admin',
92        'invited_to_company',
93        'move_assign',
94        'email_verified_at',
95        'onboardingv2_step',
96        'onboardingv2_presented',
97        'heap_analytics_id',
98        'roleplayCredits',
99        'totalTimeCredits',
100        'projectCredits',
101        'is_beta',
102        'developer_mode',
103    ];
104
105    protected $hidden = [
106        'password',
107        'remember_token',
108    ];
109
110    protected $casts = [
111        'deactivated_at' => 'datetime',
112        'activation_date' => 'datetime',
113    ];
114
115    /**
116     * The relationships that should always be loaded.
117     *
118     * @var array
119     */
120    protected $with = ['company'];
121
122    /**
123     * The accessors to append to the model's array form.
124     *
125     * @var array
126     */
127    protected $appends = ['role'];
128
129    protected function getRoleAttribute(): string
130    {
131        return implode(',', $this->roles());
132    }
133
134    /**
135     * Set the email to lower case whenever a User is created.
136     */
137    public function setEmailAttribute(string $value): void
138    {
139        $this->attributes['email'] = strtolower($value);
140    }
141
142    /**
143     * Get the user's email and transform it to lower case.
144     */
145    public function getEmailAttribute(string $value): string
146    {
147        return strtolower($value);
148    }
149
150    public function sendWelcomeNotification()
151    {
152        // Update user rewards and the invitation status
153        $searchArray = ['email' => $this->email, 'status' => 'Pending'];
154
155        $invitation = Invitation::where($searchArray);
156        $invitation_count = $invitation->count();
157
158        if ($invitation_count > 0) {
159            $invitation = $invitation->oldest()->first();
160            if (! empty($invitation)) {
161                $invitation_user_id = $invitation->user_id;
162                if ($invitation_user_id && $invitation_user_id != '') {
163                    $userSearchArray = ['_id' => $invitation_user_id];
164                    $invitation_user = User::where($userSearchArray)->first();
165
166                    $rewards_level = (isset($invitation_user['rewards_level']) && $invitation_user['rewards_level'] != 4) ? $invitation_user['rewards_level'] + 1 : 1;
167                    $referrals_count = (isset($invitation_user['referrals_count'])) ? $invitation_user['referrals_count'] + 1 : 1;
168
169                    $invitation_user->rewardable = 1;
170                    $invitation_user->rewards_level = $rewards_level;
171                    $invitation_user->referrals_count = $referrals_count;
172                    $invitation_user->save();
173
174                    $whereUserReferrals = ['user_id' => $invitation_user_id];
175                    $referralCollection = UserReferral::where($whereUserReferrals);
176                    $referral_count = $referralCollection->count();
177
178                    if ($referral_count > 0) {
179                        $referralData = $referralCollection->first();
180                        $whereExistingUserRefeerals = ['user_id' => $invitation_user_id];
181                        $invitedUserData = [
182                            'first_name' => $invitation->first_name,
183                            'last_name' => $invitation->last_name,
184                            'email' => $invitation->email,
185                            'created_at' => now(),
186                            'updated_at' => now(),
187                        ];
188
189                        $current_flycutData = json_encode($invitedUserData, true);
190                        $existingReferralData = $referralData->toArray();
191                        $existingReferralData['total_referrals'] = $rewards_level;
192                        // $existingReferralData['total_referrals'] = $referralData['total_referrals'] + 1;
193
194                        $existingReferralData[] = $current_flycutData; // Directly set the values in the array
195                        // $existingReferralData = $this->convertIntegerKeysToWords($existingReferralData);
196                        unset($existingReferralData['_id']);
197                        UserReferral::where($whereExistingUserRefeerals)->update($existingReferralData);
198                    } else {
199                        $userReferral = new UserReferral;
200                        $invitedUserData = [
201                            'first_name' => $invitation->first_name,
202                            'last_name' => $invitation->last_name,
203                            'email' => $invitation->email,
204                            'created_at' => now(),
205                            'updated_at' => now(),
206                        ];
207
208                        $referralData[0] = json_encode($invitedUserData, true);
209                        $referralData['total_referrals'] = 1;
210                        $referralData['user_id'] = $invitation_user_id;
211                        $userReferral->fill($referralData);
212                        $userReferral->push();
213                    }
214
215                    $invitationUpdateData = ['status' => 'Completed'];
216                    Invitation::where($searchArray)->update($invitationUpdateData);
217                }
218            }
219        } elseif (isset($this->refered_by) && $this->refered_by != '') {
220            // Get user id for the referral if user directly signup with the link
221            $refUserArray = ['referral_key' => $this->refered_by];
222            $refuser = User::where($refUserArray)->first();
223
224            // Update user refferal counts and referral level in user documents
225            $rewards_level = (isset($refuser['rewards_level']) && $refuser['rewards_level'] != 4) ? $refuser['rewards_level'] + 1 : 1;
226            $referrals_count = (isset($refuser['referrals_count'])) ? $refuser['referrals_count'] + 1 : 1;
227
228            $refuser->rewardable = 1;
229            $refuser->rewards_level = $rewards_level;
230            $refuser->referrals_count = $referrals_count;
231            $refuser->save();
232
233            $whereUserReferrals = ['user_id' => $refuser->id];
234            $referralCollection = UserReferral::where($whereUserReferrals);
235            $referral_count = $referralCollection->count();
236
237            if ($referral_count > 0) {
238                $referralData = $referralCollection->first();
239                $whereExistingUserRefeerals = ['user_id' => $refuser->id];
240                $invitedUserData = [
241                    'first_name' => $this->first_name,
242                    'last_name' => $this->last_name,
243                    'email' => $this->email,
244                    'created_at' => now(),
245                    'updated_at' => now(),
246                ];
247
248                $current_refData = json_encode($invitedUserData, true);
249                $existingReferralData = $referralData->toArray();
250                $existingReferralData['total_referrals'] = $rewards_level;
251                // $existingReferralData['total_referrals'] = $referralData['total_referrals'] + 1;
252                $existingReferralData[] = $current_refData; // Directly set the values in the array
253                // $existingReferralData = $this->convertIntegerKeysToWords($existingReferralData);
254                unset($existingReferralData['_id']);
255                UserReferral::where($whereExistingUserRefeerals)->update($existingReferralData);
256            } else {
257                $userReferral = new UserReferral;
258                $invitedUserData = [
259                    'first_name' => $this->first_name,
260                    'last_name' => $this->last_name,
261                    'email' => $this->email,
262                    'created_at' => now(),
263                    'updated_at' => now(),
264                ];
265
266                $referralData[0] = json_encode($invitedUserData, true);
267                $referralData['total_referrals'] = 1;
268                $referralData['user_id'] = $refuser->id;
269                $userReferral->fill($referralData);
270                $userReferral->push();
271            }
272        }
273
274        if (Cache::has('welcome_email_sent_'.$this->email)) {
275            return;
276        }
277
278        // Set cache for email sent to avoid any duplication. At lease for an hour.
279        Cache::put('welcome_email_sent_'.$this->email, true, now()->addHour());
280        $this->notify(new Welcome($this));
281    }
282
283    public function sendPasswordResetNotification($token)
284    {
285        $this->notify(new ResetPassword($token));
286    }
287
288    public function isPasswordSet()
289    {
290        return ! is_null($this->password);
291    }
292
293    public function linkedSocialAccounts()
294    {
295        return $this->hasMany(LinkedSocialAccount::class);
296    }
297
298    public function fileMetaData()
299    {
300        return $this->hasMany(FileMetaData::class, 'user_id');
301    }
302
303    /**
304     * Get the setting that belongs to the user.
305     */
306    public function setting()
307    {
308        return $this->hasOne(Setting::class);
309    }
310
311    /**
312     * Get the shortcuts that belongs to the user.
313     */
314    public function shortcuts()
315    {
316        return $this->hasMany(Shortcut::class)->withoutGlobalScope(UserScope::class);
317    }
318
319    /**
320     * Get the flyshares that belongs to the user.
321     */
322    public function flyshares()
323    {
324        return $this->hasMany(FlyShare::class);
325    }
326
327    /**
328     * Get the shortcuts that was shared to the user.
329     */
330    public function shortcutsSharedWithUser()
331    {
332        return $this->hasMany(SharesShortcut::class);
333    }
334
335    /**
336     * Get the shortcuts that the user shared with others.
337     */
338    public function shortcutsSharedWithOthers()
339    {
340        return $this->hasMany(ClonedSharedShortcut::class);
341    }
342
343    /**
344     * Get the flycut usage records for the user.
345     */
346    public function flycutUsage()
347    {
348        return $this->hasMany(FlyCutUsage::class);
349    }
350
351    /**
352     * Get the chart records for the user.
353     */
354    public function charts()
355    {
356        return $this->hasMany(Chart::class);
357    }
358
359    /**
360     * Get the role_play_projects records for the user.
361     */
362    public function role_play_projects()
363    {
364        return $this->hasMany(RolePlayProjects::class, 'user_id');
365    }
366
367    /**
368     * Get the total number of flycuts used by the user in a time range.
369     */
370    public function flycutsUsed(string $from): int
371    {
372        $from = Carbon::parse($from);
373
374        return $this->flycutUsage()->whereNull('feature')->where('created_at', '>=', $from)->count();
375    }
376
377    /**
378     * Get the total number of characters typed by the user in a time range.
379     */
380    public function charactersTyped(string $from): int
381    {
382        $from = Carbon::parse($from);
383
384        return $this->flycutUsage()->where('created_at', '>=', $from)->sum('characters_typed');
385    }
386
387    /**
388     * Get the total number of characters saved by the user in a time range.
389     */
390    public function charactersSaved(string $from): int
391    {
392        $from = Carbon::parse($from);
393
394        return $this->flycutUsage()->where('created_at', '>=', $from)->sum('characters_saved');
395    }
396
397    /**
398     * Get the total number of time saved by the user in a time range.
399     */
400    public function timeSaved(string $from): float
401    {
402        $from = Carbon::parse($from);
403
404        return $this->flycutUsage()->where('created_at', '>=', $from)->sum('time_saved');
405    }
406
407    /**
408     * Get chart data for user.
409     */
410    public function getChartData(string $from)
411    {
412        $userId = $this->id;
413        $fromDate = Carbon::parse($from);
414        $startDate = new UTCDateTime($fromDate->getTimestamp() * 1000);
415
416        $result = FlyMsgUserDailyUsage::raw(function ($collection) use ($userId, $startDate) {
417            return $collection->aggregate([
418                [
419                    '$match' => [
420                        'user_id' => $userId,
421                        'created_at' => ['$gte' => $startDate],
422                    ],
423                ],
424                [
425                    '$project' => [
426                        'month' => ['$month' => '$created_at'],
427                        'year' => ['$year' => '$created_at'],
428                        'flycut_count' => ['$ifNull' => ['$flycut_count', 0]],
429                        'sentence_rewrite_count' => ['$ifNull' => ['$sentence_rewrite_count', 0]],
430                        'paragraph_rewrite_count' => ['$ifNull' => ['$paragraph_rewrite_count', 0]],
431                        'fly_grammar_actions' => ['$ifNull' => ['$fly_grammar_actions', 0]],
432                        'fly_grammar_accepted' => ['$ifNull' => ['$fly_grammar_accepted', 0]],
433                        'fly_grammar_autocorrect' => ['$ifNull' => ['$fly_grammar_autocorrect', 0]],
434                        'fly_grammar_autocomplete' => ['$ifNull' => ['$fly_grammar_autocomplete', 0]],
435                        'characters_typed' => ['$ifNull' => ['$characters_typed', 0]],
436                        'time_saved' => ['$ifNull' => ['$time_saved', 0]],
437                        'cost_saved' => ['$ifNull' => ['$cost_savings', 0]],
438                    ],
439                ],
440                [
441                    '$project' => [
442                        'month_year' => [
443                            '$concat' => [
444                                [
445                                    '$arrayElemAt' => [
446                                        ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
447                                        '$month',
448                                    ],
449                                ],
450                                ' ',
451                                ['$toString' => '$year'],
452                            ],
453                        ],
454                        'flycut_count' => 1,
455                        'sentence_rewrite_count' => 1,
456                        'paragraph_rewrite_count' => 1,
457                        'fly_grammar_actions' => 1,
458                        'fly_grammar_accepted' => 1,
459                        'fly_grammar_autocorrect' => 1,
460                        'fly_grammar_autocomplete' => 1,
461                        'characters_typed' => 1,
462                        'time_saved' => 1,
463                        'cost_saved' => 1,
464                    ],
465                ],
466                [
467                    '$group' => [
468                        '_id' => '$month_year',
469                        'flycut_count' => ['$sum' => '$flycut_count'],
470                        'sentence_rewrite_count' => ['$sum' => '$sentence_rewrite_count'],
471                        'paragraph_rewrite_count' => ['$sum' => '$paragraph_rewrite_count'],
472                        'fly_grammar_actions' => ['$sum' => '$fly_grammar_actions'],
473                        'fly_grammar_accepted' => ['$sum' => '$fly_grammar_accepted'],
474                        'fly_grammar_autocorrect' => ['$sum' => '$fly_grammar_autocorrect'],
475                        'fly_grammar_autocomplete' => ['$sum' => '$fly_grammar_autocomplete'],
476                        'characters_typed' => ['$sum' => '$characters_typed'],
477                        'time_saved' => ['$sum' => '$time_saved'],
478                        'cost_saved' => ['$sum' => '$cost_saved'],
479                    ],
480                ],
481                [
482                    '$sort' => ['_id' => 1],
483                ],
484                [
485                    '$project' => [
486                        'month_year' => '$_id',
487                        '_id' => 0,
488                        'flycut_count' => 1,
489                        'sentence_rewrite_count' => 1,
490                        'paragraph_rewrite_count' => 1,
491                        'fly_grammar_actions' => 1,
492                        'fly_grammar_accepted' => 1,
493                        'fly_grammar_autocorrect' => 1,
494                        'fly_grammar_autocomplete' => 1,
495                        'characters_typed' => 1,
496                        'time_saved' => 1,
497                        'cost_saved' => 1,
498                    ],
499                ],
500            ]);
501        });
502
503        $months = Carbon::parse($from)->diffInMonths(now());
504        $line_chart_data = [];
505        for ($i = 0; $i <= $months; $i++) {
506            $month = $fromDate->copy()->addMonths($i)->format('M');
507            $month_year = $fromDate->copy()->addMonths($i)->format('M Y');
508
509            $data = $result->firstWhere('month_year', $month_year);
510
511            $line_chart_data[$month_year] = [
512                'flycut_count' => $data->flycut_count ?? 0,
513                'sentence_rewrite_count' => $data->sentence_rewrite_count ?? 0,
514                'paragraph_rewrite_count' => $data->paragraph_rewrite_count ?? 0,
515                'fly_grammar_actions' => $data->fly_grammar_actions ?? 0,
516                'fly_grammar_accepted' => $data->fly_grammar_accepted ?? 0,
517                'fly_grammar_autocorrect' => $data->fly_grammar_autocorrect ?? 0,
518                'fly_grammar_autocomplete' => $data->fly_grammar_autocomplete ?? 0,
519                'characters_typed' => $data->characters_typed ?? 0,
520                'time_saved' => $data->time_saved ?? 0,
521                'cost_saved' => $data->cost_saved ?? 0,
522                'month_year' => $month_year,
523            ];
524        }
525
526        return collect($line_chart_data)->sortBy(function ($item) {
527            return Carbon::parse($item['month_year'])->timestamp;
528        }, SORT_REGULAR, true);
529    }
530
531    public function saved_prompts()
532    {
533        return $this->hasMany(SavedPrompt::class);
534    }
535
536    public function promptUsage()
537    {
538        return $this->hasOne(PromptUsage::class);
539    }
540
541    /**
542     * Get the subscription trials records for the user.
543     *
544     * @return \Illuminate\Database\Eloquent\Relations\HasMany
545     */
546    public function subscriptionTrials()
547    {
548        return $this->hasMany(SubscriptionTrials::class);
549    }
550
551    /**
552     * Get the flymsg AI Tracking records for the user.
553     *
554     * @return \Illuminate\Database\Eloquent\Relations\HasMany
555     */
556    public function flyMsgAITracking()
557    {
558        return $this->hasMany(FlyMsgAITracking::class);
559    }
560
561    /**
562     * Get flyMsgAITracking Usage for user.
563     *
564     * @param  string  $feature
565     * @return Illuminate\Database\Eloquent\Collection
566     */
567    public function getflyMsgAITrackingUsage($feature)
568    {
569        $usageData = $this->flyMsgAITracking()
570            ->where('feature', $feature)
571            ->where('created_at', '>=', now()->subMonths(7))
572            ->get(['created_at']);
573
574        $groupedData = $usageData->groupBy(function ($record) {
575            return Carbon::parse($record->created_at)->format('M Y');
576        })
577            ->map(function ($group) {
578                return $group->count();
579            });
580
581        $months = collect([]);
582        for ($i = 6; $i >= 0; $i--) {
583            $month = now()->subMonths($i)->format('M Y');
584            $months[$month] = $groupedData[$month] ?? 0;
585        }
586
587        return $months;
588    }
589
590    private function convertIntegerKeysToWords($array)
591    {
592        $result = [];
593
594        foreach ($array as $key => $value) {
595            if (is_int($key)) {
596                $key = $this->convertIntegerToWord($key);
597            }
598
599            $result[$key] = is_array($value) ? $this->convertIntegerKeysToWords($value) : $value;
600        }
601
602        return $result;
603    }
604
605    private function convertIntegerToWord($integer)
606    {
607        $words = [
608            0 => 'first',
609            1 => 'second',
610            2 => 'third',
611            3 => 'fourth',
612            4 => 'fifth',
613            5 => 'sixth',
614            6 => 'seventh',
615            7 => 'eighth',
616            8 => 'ninth',
617            9 => 'tenth',
618        ];
619
620        return isset($words[$integer]) ? $words[$integer] : 'nth';
621    }
622
623    private function convertIntegerToOrdinal($integer)
624    {
625        $ordinal = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth', 'ninth', 'tenth'];
626
627        return isset($ordinal[$integer - 1]) ? $ordinal[$integer - 1] : (string) $integer.'th';
628    }
629
630    public function hubspot_property()
631    {
632        return $this->hasOne(HubspotProperties::class, 'email', 'email');
633    }
634
635    public function sales_pro_team_manager()
636    {
637        return $this->hasOne(SalesProTeamManager::class, 'user_id');
638    }
639
640    /**
641     * Company point of contact persons
642     *
643     * @return \Illuminate\Database\Eloquent\Relations\HasMany
644     */
645    public function pocs()
646    {
647        return $this->hasMany(CompanyPOC::class);
648    }
649
650    /**
651     * Get the Stripe client instance.
652     *
653     * @return \Stripe\StripeClient
654     */
655    public function stripe()
656    {
657        return new StripeClient(config: [
658            'api_key' => config('cashier.secret'),
659        ]);
660    }
661
662    /**
663     * Tax rates a user pays on a subscription
664     *
665     * @return string[] ['tax-rate-id'];
666     */
667    public function taxRates()
668    {
669        return [];
670    }
671
672    /**
673     * Tax rates a user pays on a subscription per plan
674     *
675     * return [
676     *   'plan-id' => ['tax-rate-id'],
677     * ];
678     *
679     * @return []
680     */
681    public function planTaxRates()
682    {
683        return [];
684    }
685
686    public function company_group()
687    {
688        return $this->belongsTo(CompanyGroup::class, 'company_group_id');
689    }
690
691    public function company()
692    {
693        return $this->belongsTo(Company::class, 'company_id');
694    }
695
696    public function invitation(): HasOne
697    {
698        return $this->hasOne(AdminUserInvitation::class, 'email', 'email');
699    }
700
701    public function getNameAttribute()
702    {
703        return $this->first_name.' '.$this->last_name;
704    }
705
706    public function getInvitationLinkForAdminPortal()
707    {
708        return config('romeo.frontend-base-url').'/session/signup?email='.$this->email;
709    }
710
711    public function isPOC()
712    {
713        return $this->pocs()->exists();
714    }
715
716    protected static function newFactory()
717    {
718        return UserFactory::new();
719    }
720}