Passed
Pull Request — main (#25)
by Michael
04:17
created

Coupon   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 8
Bugs 0 Features 2
Metric Value
wmc 23
eloc 53
c 8
b 0
f 2
dl 0
loc 212
ccs 51
cts 51
cp 1
rs 10

13 Methods

Rating   Name   Duplication   Size   Complexity  
A isAllowedToRedeemBy() 0 13 5
A for() 0 11 1
A isOverQuantity() 0 5 2
A __construct() 0 7 1
A isNotExpired() 0 3 1
A newFactory() 0 3 1
A isExpired() 0 5 2
A isRedeemedBy() 0 5 1
A isOverLimit() 0 4 3
A isOverLimitFor() 0 9 2
A isEnabled() 0 3 1
A isDisabled() 0 3 1
A isDisposable() 0 5 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MichaelRubel\Couponables\Models;
6
7
use Illuminate\Database\Eloquent\Factories\Factory;
8
use Illuminate\Database\Eloquent\Factories\HasFactory;
9
use Illuminate\Database\Eloquent\Model;
10
use MichaelRubel\Couponables\Models\Contracts\CouponContract;
11
use MichaelRubel\Couponables\Models\Factories\CouponFactory;
12
use MichaelRubel\Couponables\Models\Traits\DefinesColumnChecks;
13
use MichaelRubel\Couponables\Models\Traits\DefinesColumns;
14
use MichaelRubel\Couponables\Models\Traits\DefinesModelRelations;
15
use MichaelRubel\Couponables\Traits\Concerns\CalculatesCosts;
16
use MichaelRubel\EnhancedContainer\Core\CallProxy;
17
18
class Coupon extends Model implements CouponContract
19
{
20
    use HasFactory;
21
    use DefinesColumns;
22
    use DefinesColumnChecks;
23
    use DefinesModelRelations;
24
    use CalculatesCosts;
25
26
    /**
27
     * The attributes that aren't mass assignable.
28
     *
29
     * @var array<string>|bool
30
     */
31
    protected $guarded = [];
32
33
    /**
34
     * The attributes that should be cast.
35
     *
36
     * @var array<string, string>
37
     */
38
    protected $casts = [
39
        'code'       => 'string',
40
        'type'       => 'string',
41
        'value'      => 'string',
42
        'is_enabled' => 'boolean',
43
        'data'       => 'collection',
44
        'quantity'   => 'integer',
45
        'limit'      => 'integer',
46
        'expires_at' => 'datetime',
47
    ];
48
49
    /**
50
     * @var CallProxy
51
     */
52
    protected static CallProxy $bindable;
53
54
    /**
55
     * @param  array  $attributes
56
     */
57 78
    public function __construct(array $attributes = [])
58
    {
59 78
        parent::__construct($attributes);
60
61 78
        $this->table = config('couponables.table', 'coupons');
62
63 78
        static::$bindable = call($this);
64
    }
65
66
    /**
67
     * Check if code is expired.
68
     *
69
     * @return bool
70
     */
71 35
    public function isExpired(): bool
72
    {
73 35
        $expires_at = $this->{static::$bindable->getExpiresAtColumn()};
74
75 35
        return $expires_at && now()->gte($expires_at);
76
    }
77
78
    /**
79
     * Check if code is not expired.
80
     *
81
     * @return bool
82
     */
83 1
    public function isNotExpired(): bool
84
    {
85 1
        return ! static::$bindable->isExpired();
86
    }
87
88
    /**
89
     * Check if code is enabled.
90
     *
91
     * @return bool
92
     */
93 38
    public function isEnabled(): bool
94
    {
95 38
        return $this->{static::$bindable->getIsEnabledColumn()} ?? true;
96
    }
97
98
    /**
99
     * Check if code is disabled.
100
     *
101
     * @return bool
102
     */
103 36
    public function isDisabled(): bool
104
    {
105 36
        return ! static::$bindable->isEnabled();
106
    }
107
108
    /**
109
     * Check if code amount is over.
110
     *
111
     * @return bool
112
     */
113 26
    public function isOverQuantity(): bool
114
    {
115 26
        $quantity = $this->{static::$bindable->getQuantityColumn()};
116
117 26
        return ! is_null($quantity) && $quantity <= 0;
118
    }
119
120
    /**
121
     * Check if coupon is disposable.
122
     *
123
     * @return bool
124
     */
125 26
    public function isDisposable(): bool
126
    {
127 26
        $limit = $this->{static::$bindable->getLimitColumn()};
128
129 26
        return ! is_null($limit) && $limit == 1;
130
    }
131
132
    /**
133
     * Check if the code is reached its global limit.
134
     *
135
     * @param  Model  $redeemer
136
     * @param  string|null  $code
137
     *
138
     * @return bool
139
     */
140 26
    public function isOverLimit(Model $redeemer, ?string $code): bool
141
    {
142 26
        return (static::$bindable->isDisposable() && call($redeemer)->isCouponAlreadyUsed($code))
143 26
            || static::$bindable->isOverLimitFor($redeemer);
144
    }
145
146
    /**
147
     * Check if the code is reached its limit for the passed model.
148
     *
149
     * @param  Model  $redeemer
150
     *
151
     * @return bool
152
     */
153 26
    public function isOverLimitFor(Model $redeemer): bool
154
    {
155 26
        $column = static::$bindable->getCodeColumn();
156 26
        $limit  = $this->{static::$bindable->getLimitColumn()};
157
158 26
        return ! is_null($limit) && $limit <= $redeemer
159 26
            ->coupons()
160 26
            ->where($column, $this->{$column})
161 26
            ->count();
162
    }
163
164
    /**
165
     * Check if coupon is already redeemed by the model.
166
     *
167
     * @param  Model  $redeemer
168
     *
169
     * @return bool
170
     */
171 2
    public function isRedeemedBy(Model $redeemer): bool
172
    {
173 2
        $column = static::$bindable->getCodeColumn();
174
175 2
        return $redeemer->coupons()->where($column, $this->{$column})->exists();
176
    }
177
178
    /**
179
     * Check if the model is allowed to redeem.
180
     *
181
     * @param  Model  $redeemer
182
     *
183
     * @return bool
184
     */
185 32
    public function isAllowedToRedeemBy(Model $redeemer): bool
186
    {
187
        /** @var bool */
188 32
        return with(static::$bindable, function ($coupon) use ($redeemer) {
0 ignored issues
show
Bug Best Practice introduced by
The expression return with(static::bind...ion(...) { /* ... */ }) could return the type MichaelRubel\EnhancedContainer\Core\CallProxy which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
189 32
            if ($coupon->isMorphColumnsFilled() && ! $coupon->redeemer?->is($redeemer)) {
190 5
                return false;
191
            }
192
193 27
            if ($coupon->isOnlyRedeemerTypeFilled() && ! $coupon->isSameRedeemerModel($redeemer)) {
194 1
                return false;
195
            }
196
197 26
            return true;
198 32
        });
199
    }
200
201
    /**
202
     * Assign the model to the latest redeemed coupon.
203
     *
204
     * @param  Model  $redeemed
205
     *
206
     * @return CouponContract
207
     */
208 1
    public function for(Model $redeemed): CouponContract
209
    {
210 1
        return with($this->couponables()->first(), function ($couponable) use ($redeemed) {
211 1
            $morphValues = transform($couponable, fn ($bindable) => [
212 1
                $bindable->getRedeemedTypeColumn() => $redeemed->getMorphClass(),
213 1
                $bindable->getRedeemedIdColumn()   => $redeemed->id,
214 1
            ]);
215
216 1
            $couponable->update($morphValues);
217
218 1
            return $this;
219 1
        });
220
    }
221
222
    /**
223
     * Create a new factory instance for the model.
224
     *
225
     * @return Factory<Coupon>
226
     */
227 60
    private static function newFactory()
0 ignored issues
show
Unused Code introduced by
The method newFactory() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
228
    {
229 60
        return CouponFactory::new();
230
    }
231
}
232