Passed
Push — master ( 5d0f2f...49b4e9 )
by Darko
10:54
created

RolePromotion::getTotalUpgrades()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace App\Models;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Database\Eloquent\Relations\HasMany;
7
use Illuminate\Support\Carbon;
8
use Spatie\Permission\Models\Role;
9
10
/**
11
 * App\Models\RolePromotion
12
 *
13
 * @property int $id
14
 * @property string $name
15
 * @property string|null $description
16
 * @property array $applicable_roles
17
 * @property int $additional_days
18
 * @property string|null $start_date
19
 * @property string|null $end_date
20
 * @property bool $is_active
21
 * @property \Carbon\Carbon|null $created_at
22
 * @property \Carbon\Carbon|null $updated_at
23
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\RolePromotionStat[] $statistics
24
 */
25
class RolePromotion extends Model
26
{
27
    protected $fillable = [
28
        'name',
29
        'description',
30
        'applicable_roles',
31
        'additional_days',
32
        'start_date',
33
        'end_date',
34
        'is_active',
35
    ];
36
37
    protected $casts = [
38
        'is_active' => 'boolean',
39
        'start_date' => 'date',
40
        'end_date' => 'date',
41
        'additional_days' => 'integer',
42
        'applicable_roles' => 'array',
43
    ];
44
45
    /**
46
     * Get the statistics for this promotion
47
     */
48
    public function statistics(): HasMany
49
    {
50
        return $this->hasMany(RolePromotionStat::class);
51
    }
52
53
    /**
54
     * Get the roles this promotion applies to
55
     */
56
    public function getApplicableRolesModels()
57
    {
58
        if (empty($this->applicable_roles)) {
59
            return static::getCustomRoles();
60
        }
61
62
        return Role::whereIn('id', $this->applicable_roles)->get();
63
    }
64
65
    /**
66
     * Scope to get only valid (non-expired) promotions
67
     */
68
    public function scopeValid($query)
69
    {
70
        $now = Carbon::now();
71
        return $query->where('is_active', true)
72
            ->where(function ($q) use ($now) {
73
                $q->whereNull('end_date')
74
                    ->orWhere('end_date', '>=', $now);
75
            })
76
            ->where(function ($q) use ($now) {
77
                $q->whereNull('start_date')
78
                    ->orWhere('start_date', '<=', $now);
79
            });
80
    }
81
82
    /**
83
     * Get custom roles (excluding system roles)
84
     */
85
    public static function getCustomRoles()
86
    {
87
        $systemRoles = ['Admin', 'User', 'Moderator', 'Disabled', 'Friend'];
88
        return Role::whereNotIn('name', $systemRoles)->get();
89
    }
90
91
    /**
92
     * Check if the promotion is currently active
93
     */
94
    public function isCurrentlyActive(): bool
95
    {
96
        if (!$this->is_active) {
97
            return false;
98
        }
99
100
        $now = Carbon::now();
101
102
        if ($this->start_date && $now->lt($this->start_date)) {
103
            return false;
104
        }
105
106
        if ($this->end_date && $now->gt($this->end_date)) {
107
            return false;
108
        }
109
110
        return true;
111
    }
112
113
    /**
114
     * Get active promotions for a specific role
115
     */
116
    public static function getActivePromotions(?int $roleId = null)
117
    {
118
        $query = static::where('is_active', true);
119
120
        $now = Carbon::now();
121
        $query->where(function ($q) use ($now) {
122
            $q->whereNull('start_date')
123
                ->orWhere('start_date', '<=', $now);
124
        });
125
126
        $query->where(function ($q) use ($now) {
127
            $q->whereNull('end_date')
128
                ->orWhere('end_date', '>=', $now);
129
        });
130
131
        if ($roleId !== null) {
132
            $query->where(function ($q) use ($roleId) {
133
                // If applicable_roles is empty, applies to all custom roles
134
                $q->whereJsonLength('applicable_roles', 0)
135
                    ->orWhereJsonContains('applicable_roles', $roleId);
136
            });
137
        }
138
139
        return $query->get();
140
    }
141
142
    /**
143
     * Calculate total additional days from active promotions
144
     */
145
    public static function calculateAdditionalDays(int $roleId): int
146
    {
147
        $promotions = static::getActivePromotions($roleId);
148
149
        return $promotions->sum('additional_days');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $promotions->sum('additional_days') returns the type App\Models\RolePromotion which is incompatible with the type-hinted return integer.
Loading history...
150
    }
151
152
    /**
153
     * Get summary statistics for this promotion
154
     */
155
    public function getSummaryStatistics(): array
156
    {
157
        return RolePromotionStat::getPromotionSummary($this->id);
158
    }
159
160
    /**
161
     * Get the count of users who have received this promotion
162
     */
163
    public function getTotalUpgrades(): int
164
    {
165
        return $this->statistics()->count();
166
    }
167
168
    /**
169
     * Get the count of unique users who have received this promotion
170
     */
171
    public function getUniqueUsersCount(): int
172
    {
173
        return $this->statistics()->distinct('user_id')->count('user_id');
174
    }
175
176
    /**
177
     * Get the total days added across all applications of this promotion
178
     */
179
    public function getTotalDaysAdded(): int
180
    {
181
        return $this->statistics()->sum('days_added');
182
    }
183
184
    /**
185
     * Track a promotion application
186
     */
187
    public function trackApplication(
188
        int $userId,
189
        int $roleId,
190
        ?\Illuminate\Support\Carbon $previousExpiryDate = null,
191
        ?\Illuminate\Support\Carbon $newExpiryDate = null
192
    ): RolePromotionStat {
193
        return RolePromotionStat::recordPromotion(
194
            $userId,
195
            $this->id,
196
            $roleId,
197
            $this->additional_days,
198
            $previousExpiryDate,
199
            $newExpiryDate
200
        );
201
    }
202
203
    /**
204
     * Get statistics grouped by role
205
     */
206
    public function getStatisticsByRole(): array
207
    {
208
        return $this->statistics()
209
            ->with('role')
210
            ->get()
211
            ->groupBy('role_id')
212
            ->map(function ($stats, $roleId) {
213
                return [
214
                    'role_id' => $roleId,
215
                    'role_name' => $stats->first()->role?->name,
216
                    'total_upgrades' => $stats->count(),
217
                    'total_days_added' => $stats->sum('days_added'),
218
                    'unique_users' => $stats->unique('user_id')->count(),
219
                ];
220
            })
221
            ->values()
222
            ->all();
223
    }
224
225
    /**
226
     * Get statistics for a specific time period
227
     */
228
    public function getStatisticsForPeriod(Carbon $startDate, Carbon $endDate): array
229
    {
230
        $stats = $this->statistics()
231
            ->appliedBetween($startDate, $endDate)
232
            ->get();
233
234
        return [
235
            'period' => [
236
                'start' => $startDate->toDateString(),
237
                'end' => $endDate->toDateString(),
238
            ],
239
            'total_upgrades' => $stats->count(),
240
            'total_days_added' => $stats->sum('days_added'),
241
            'unique_users' => $stats->unique('user_id')->count(),
242
            'roles_affected' => $stats->unique('role_id')->count(),
243
        ];
244
    }
245
}
246
247