Passed
Push — main ( b4d9f2...96c851 )
by Michael
04:34
created

Coupon::newFactory()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
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 63
    protected static function newFactory()
228
    {
229 63
        return CouponFactory::new();
230
    }
231
}
232