Passed
Push — main ( d07568...cf64be )
by Michael
03:25
created

Coupon::isAllowedToRedeem()   A

Complexity

Conditions 5
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 5

Importance

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