*/ use Billable, HasApiTokens, HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var list */ protected $fillable = [ 'name', 'email', 'password', 'level', ]; /** * The attributes that should be hidden for serialization. * * @var list */ protected $hidden = [ 'password', 'remember_token', 'level', ]; /** * Get the attributes that should be cast. * * @return array */ protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', 'level' => UserLevel::class, ]; } public function initials(): string { return Str::of($this->name) ->explode(' ') ->map(fn (string $name) => Str::of($name)->substr(0, 1)) ->implode(''); } public function canAccessPanel(Panel $panel): bool { return str_ends_with($this->email, '@zemail.me') && $this->level === UserLevel::SUPERADMIN && $this->hasVerifiedEmail(); } /** * Scope to query only super admin users. */ public function scopeIsSuperAdmin($query) { return $query->where('level', UserLevel::SUPERADMIN->value); } /** * Scope to query only normal users. */ public function scopeIsNormalUser($query) { return $query->where('level', UserLevel::NORMALUSER->value); } /** * Scope to query only banner users. */ public function scopeIsBannerUser($query) { return $query->where('level', UserLevel::BANNEDUSER->value); } /** * Check if user is a super admin. */ public function isSuperAdmin(): bool { return $this->level === UserLevel::SUPERADMIN; } /** * Check if user is a normal user. */ public function isNormalUser(): bool { return $this->level === UserLevel::NORMALUSER; } /** * Check if user is a banner user. */ public function isBannerUser(): bool { return $this->level === UserLevel::BANNEDUSER; } /** * Get user level name. */ public function getLevelName(): string { return $this->level->name; } public function tickets(): HasMany { return $this->hasMany(Ticket::class); } public function logs() { return $this->hasMany(Log::class); } public function usageLogs() { return $this->hasMany(UsageLog::class); } public function impersonationLogs() { return $this->hasMany(ImpersonationLog::class, 'admin_id'); } public function impersonationTargets() { return $this->hasMany(ImpersonationLog::class, 'target_user_id'); } /** * Get all subscriptions for the user */ public function subscriptions() { return $this->hasMany(Subscription::class); } /** * Get the current active subscription for the user */ public function currentSubscription() { return $this->hasOne(Subscription::class) ->where(function ($query) { $query->where('status', 'active') ->orWhere('status', 'trialing'); }) ->where(function ($query) { $query->whereNull('ends_at') ->orWhere('ends_at', '>', now()); }) ->latest(); } /** * Get the latest subscription (regardless of status) */ public function latestSubscription() { return $this->hasOne(Subscription::class)->latestOfMany(); } /** * Scope: Users with active subscriptions */ public function scopeWithActiveSubscription($query) { return $query->whereHas('subscriptions', function ($subscriptionQuery) { $subscriptionQuery->where(function ($q) { $q->where('status', 'active') ->orWhere('status', 'trialing'); })->where(function ($q) { $q->whereNull('ends_at') ->orWhere('ends_at', '>', now()); }); }); } /** * Scope: Users with trial subscriptions */ public function scopeWithTrialSubscription($query) { return $query->whereHas('subscriptions', function ($subscriptionQuery) { $subscriptionQuery->where('status', 'trialing') ->where('trial_ends_at', '>', now()); }); } /** * Scope: Users with cancelled subscriptions */ public function scopeWithCancelledSubscription($query) { return $query->whereHas('subscriptions', function ($subscriptionQuery) { $subscriptionQuery->where('status', 'cancelled') ->orWhere(function ($q) { $q->where('ends_at', '<=', now()); }); }); } /** * Scope: Users without any active subscriptions */ public function scopeWithoutActiveSubscription($query) { return $query->whereDoesntHave('subscriptions', function ($subscriptionQuery) { $subscriptionQuery->where(function ($q) { $q->where('status', 'active') ->orWhere('status', 'trialing'); })->where(function ($q) { $q->whereNull('ends_at') ->orWhere('ends_at', '>', now()); }); }); } /** * Scope: Users by subscription provider */ public function scopeBySubscriptionProvider($query, string $provider) { return $query->whereHas('subscriptions', function ($subscriptionQuery) use ($provider) { $subscriptionQuery->where('provider', $provider) ->where(function ($q) { $q->whereNull('ends_at') ->orWhere('ends_at', '>', now()); }); }); } /** * Scope: Users with subscriptions expiring soon (within given days) */ public function scopeWithSubscriptionExpiringSoon($query, int $days = 7) { return $query->whereHas('subscriptions', function ($subscriptionQuery) use ($days) { $subscriptionQuery->where('status', 'active') ->whereNotNull('ends_at') ->where('ends_at', '<=', now()->addDays($days)) ->where('ends_at', '>', now()); }); } /** * Check if user has an active subscription */ public function hasActiveSubscription(): bool { return $this->subscriptions() ->where(function ($query) { $query->where('status', 'active') ->orWhere('status', 'trialing'); }) ->where(function ($query) { $query->whereNull('ends_at') ->orWhere('ends_at', '>', now()); }) ->exists(); } /** * Check if user is currently on trial */ public function isOnTrial(): bool { return $this->subscriptions() ->where('status', 'trialing') ->where('trial_ends_at', '>', now()) ->exists(); } /** * Check if user has cancelled subscription */ public function hasCancelledSubscription(): bool { return $this->subscriptions() ->where(function ($query) { $query->where('status', 'cancelled') ->orWhere(function ($q) { $q->whereNotNull('ends_at') ->where('ends_at', '<=', now()); }); }) ->exists(); } /** * Check if user has ever had a subscription */ public function hasHadSubscription(): bool { return $this->subscriptions()->exists(); } /** * Get user's subscription status as string */ public function getSubscriptionStatus(): string { if ($this->isOnTrial()) { return 'trialing'; } if ($this->hasActiveSubscription()) { return 'active'; } if ($this->hasCancelledSubscription()) { return 'cancelled'; } return 'none'; } /** * Get user's current subscription plan */ public function getCurrentPlan(): ?Plan { return $this->currentSubscription?->plan; } /** * Get user's subscription expiry date */ public function getSubscriptionExpiryDate(): ?\Carbon\Carbon { return $this->currentSubscription?->ends_at; } /** * Get user's trial end date */ public function getTrialEndDate(): ?\Carbon\Carbon { $trialSubscription = $this->subscriptions() ->where('status', 'trialing') ->where('trial_ends_at', '>', now()) ->first(); return $trialSubscription?->trial_ends_at; } /** * Check if user's subscription is expiring soon (within given days) */ public function isSubscriptionExpiringSoon(int $days = 7): bool { $currentSubscription = $this->currentSubscription; return $currentSubscription && $currentSubscription->ends_at && $currentSubscription->ends_at->lte(now()->addDays($days)) && $currentSubscription->ends_at->gt(now()); } /** * Get total amount spent by user across all subscriptions */ public function getTotalSpent(): float { return $this->subscriptions() ->with('plan') ->get() ->sum(function ($subscription) { return $subscription->plan ? $subscription->plan->price : 0; }); } /** * Get user's subscription provider */ public function getSubscriptionProvider(): ?string { return $this->currentSubscription?->provider; } /** * Check if user can upgrade/downgrade their plan */ public function canChangePlan(): bool { return $this->hasActiveSubscription() && $this->currentSubscription?->isRecurring(); } /** * Get subscription metrics for analytics */ public function getSubscriptionMetrics(): array { $subscriptions = $this->subscriptions()->with('plan')->get(); return [ 'total_subscriptions' => $subscriptions->count(), 'active_subscriptions' => $subscriptions->where(function ($sub) { return in_array($sub->status, ['active', 'trialing']) && (! $sub->ends_at || $sub->ends_at->isFuture()); })->count(), 'total_spent' => $this->getTotalSpent(), 'current_plan' => $this->getCurrentPlan()?->name, 'provider' => $this->getSubscriptionProvider(), 'status' => $this->getSubscriptionStatus(), 'trial_ends_at' => $this->getTrialEndDate(), 'subscription_ends_at' => $this->getSubscriptionExpiryDate(), 'is_expiring_soon' => $this->isSubscriptionExpiringSoon(), ]; } }