Passed
Push — main ( f9e76f...2a9db7 )
by Michael
04:00
created

Coupon::isOverLimit()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace MichaelRubel\Couponables\Models;
6
7
use Illuminate\Database\Eloquent\Factories\HasFactory;
8
use Illuminate\Database\Eloquent\Model;
9
use MichaelRubel\Couponables\Exceptions\NotAllowedToRedeemException;
10
use MichaelRubel\Couponables\Models\Contracts\CouponContract;
11
use MichaelRubel\Couponables\Models\Traits\DefinesColumns;
12
use MichaelRubel\EnhancedContainer\Core\CallProxy;
13
14
class Coupon extends Model implements CouponContract
15
{
16
    use HasFactory, DefinesColumns;
17
18
    /**
19
     * The attributes that aren't mass assignable.
20
     *
21
     * @var array<string>|bool
22
     */
23
    protected $guarded = [];
24
25
    /**
26
     * The attributes that should be cast.
27
     *
28
     * @var array
29
     */
30
    protected $casts = [
31
        'code'     => 'string',
32
        'type'     => 'string',
33
        'data'     => 'collection',
34
        'quantity' => 'integer',
35
        'limit'    => 'integer',
36
        'datetime' => 'datetime',
37
    ];
38
39
    /**
40
     * @var CallProxy
41
     */
42
    protected static CallProxy $bindable;
43
44
    /**
45
     * @param array $attributes
46
     */
47 38
    public function __construct(array $attributes = [])
48
    {
49 38
        parent::__construct($attributes);
50
51 38
        $this->table = config('couponables.table', 'coupons');
52
53 38
        self::$bindable = call($this);
54
    }
55
56
    /**
57
     * The only model allowed to redeem the code if assigned.
58
     *
59
     * @return Model|null
60
     */
61 2
    public function redeemer(): ?Model
62
    {
63 2
        return $this->morphTo()->first();
64
    }
65
66
    /**
67
     * @param Model $model
68
     *
69
     * @return bool
70
     */
71 3
    public function isSameRedeemerModel(Model $model): bool
72
    {
73 3
        return $this->{self::$bindable->getRedeemerTypeColumn()} === $model->getMorphClass();
74
    }
75
76
    /**
77
     * Check if code is expired.
78
     *
79
     * @return bool
80
     */
81 24
    public function isExpired(): bool
82
    {
83 24
        $expires_at = $this->{self::$bindable->getExpiresAtColumn()};
84
85 24
        return $expires_at && now()->gte($expires_at);
86
    }
87
88
    /**
89
     * Check if code is not expired.
90
     *
91
     * @return bool
92
     */
93 1
    public function isNotExpired(): bool
94
    {
95 1
        return ! self::$bindable->isExpired();
96
    }
97
98
    /**
99
     * Check if code amount is over.
100
     *
101
     * @return bool
102
     */
103 21
    public function isOverQuantity(): bool
104
    {
105 21
        $quantity = $this->{self::$bindable->getQuantityColumn()};
106
107 21
        return ! is_null($quantity) && $quantity <= 0;
108
    }
109
110
    /**
111
     * Check if coupon is already redeemed by the model.
112
     *
113
     * @param Model $redeemer
114
     *
115
     * @return bool
116
     */
117 3
    public function isRedeemedBy(Model $redeemer): bool
118
    {
119 3
        $column = self::$bindable->getCodeColumn();
120 3
        $code   = $this->{$column};
121
122 3
        return ! is_null($code) && $redeemer
123 3
            ->coupons()
124 3
            ->where($column, $code)
125 3
            ->exists();
126
    }
127
128
    /**
129
     * Check if coupon is disposable.
130
     *
131
     * @return bool
132
     */
133 21
    public function isDisposable(): bool
134
    {
135 21
        $limit = $this->{self::$bindable->getLimitColumn()};
136
137 21
        return ! is_null($limit) && single($limit);
138
    }
139
140
    /**
141
     * Check if the code is reached its global limit.
142
     *
143
     * @param Model       $redeemer
144
     * @param string|null $code
145
     *
146
     * @return bool
147
     */
148 21
    public function isOverLimit(Model $redeemer, ?string $code): bool
149
    {
150 21
        return (self::$bindable->isDisposable() && call($redeemer)->isCouponRedeemed($code))
151 21
            || self::$bindable->isOverLimitFor($redeemer);
152
    }
153
154
    /**
155
     * Check if the code is reached its limit for the passed model.
156
     *
157
     * @param Model $redeemer
158
     *
159
     * @return bool
160
     */
161 21
    public function isOverLimitFor(Model $redeemer): bool
162
    {
163 21
        $column = self::$bindable->getCodeColumn();
164 21
        $limit  = $this->{self::$bindable->getLimitColumn()};
165
166 21
        return ! is_null($limit) && $limit <= $redeemer
167 4
            ->coupons()
168 4
            ->where($column, $this->{$column})
169 21
            ->count();
170
    }
171
172
    /**
173
     * Check if the model is allowed to redeem.
174
     *
175
     * @param Model $redeemer
176
     *
177
     * @return bool
178
     */
179 21
    public function isAllowedToRedeem(Model $redeemer): bool
180
    {
181 21
        if (self::$bindable->isMorphColumnsFilled() && ! self::$bindable->redeemer()?->is($redeemer)) {
182 1
            return false;
183
        }
184
185 20
        if (self::$bindable->isOnlyRedeemerTypeFilled() && ! self::$bindable->isSameRedeemerModel($redeemer)) {
186 1
            return false;
187
        }
188
189 19
        return true;
190
    }
191
}
192