Issues (48)

src/Models/Coupon.php (10 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace MichaelRubel\Couponables\Models;
6
7
use Carbon\CarbonInterface;
0 ignored issues
show
The type Carbon\CarbonInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Illuminate\Database\Eloquent\Collection;
9
use Illuminate\Database\Eloquent\Factories\Factory;
10
use Illuminate\Database\Eloquent\Factories\HasFactory;
11
use Illuminate\Database\Eloquent\Model;
12
use MichaelRubel\Couponables\Models\Contracts\CouponContract;
13
use MichaelRubel\Couponables\Models\Factories\CouponFactory;
14
use MichaelRubel\Couponables\Models\Traits\DefinesColumnChecks;
15
use MichaelRubel\Couponables\Models\Traits\DefinesColumns;
16
use MichaelRubel\Couponables\Models\Traits\DefinesModelRelations;
17
use MichaelRubel\Couponables\Traits\Concerns\CalculatesCosts;
18
19
/**
20
 * "Coupon code" model class.
21
 *
22
 * @property string $code
23
 * @property string|null $type
24
 * @property string|null $value
25
 * @property bool $is_enabled
26
 * @property Collection|null $data
27
 * @property int|null $quantity
28
 * @property int|null $limit
29
 * @property string|null $redeemer_type
30
 * @property int|null $redeemer_id
31
 * @property CarbonInterface|null $expires_at
32
 */
33
class Coupon extends Model implements CouponContract
34
{
35
    use CalculatesCosts;
36
    use DefinesColumnChecks;
37
    use DefinesColumns;
38
    use DefinesModelRelations;
39
    use HasFactory;
40
41
    /**
42
     * The attributes that aren't mass assignable.
43
     *
44
     * @var array<string>|bool
45
     */
46
    protected $guarded = [];
47
48
    /**
49
     * The attributes that should be cast.
50
     *
51
     * @var array<string, string>
52
     */
53
    protected $casts = [
54
        'code'       => 'string',
55
        'type'       => 'string',
56
        'value'      => 'string',
57
        'is_enabled' => 'boolean',
58
        'data'       => 'collection',
59
        'quantity'   => 'integer',
60
        'limit'      => 'integer',
61
        'expires_at' => 'datetime',
62
    ];
63
64
    /**
65
     * @param  array  $attributes
66
     */
67 75
    public function __construct(array $attributes = [])
68
    {
69 75
        parent::__construct($attributes);
70
71 75
        $this->table = config('couponables.table', 'coupons');
72
    }
73
74
    /**
75
     * Check if code is expired.
76
     *
77
     * @return bool
78
     */
79 31
    public function isExpired(): bool
80
    {
81 31
        $expires_at = $this->{static::getExpiresAtColumn()};
0 ignored issues
show
Bug Best Practice introduced by
The method MichaelRubel\Couponables...n::getExpiresAtColumn() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

81
        $expires_at = $this->{static::/** @scrutinizer ignore-call */ getExpiresAtColumn()};
Loading history...
82
83 31
        return $expires_at && now()->gte($expires_at);
84
    }
85
86
    /**
87
     * Check if code is not expired.
88
     *
89
     * @return bool
90
     */
91 1
    public function isNotExpired(): bool
92
    {
93 1
        return ! $this->isExpired();
94
    }
95
96
    /**
97
     * Check if code is enabled.
98
     *
99
     * @return bool
100
     */
101 34
    public function isEnabled(): bool
102
    {
103 34
        return $this->{static::getIsEnabledColumn()} ?? true;
0 ignored issues
show
Bug Best Practice introduced by
The method MichaelRubel\Couponables...n::getIsEnabledColumn() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

103
        return $this->{static::/** @scrutinizer ignore-call */ getIsEnabledColumn()} ?? true;
Loading history...
104
    }
105
106
    /**
107
     * Check if code is disabled.
108
     *
109
     * @return bool
110
     */
111 32
    public function isDisabled(): bool
112
    {
113 32
        return ! $this->isEnabled();
114
    }
115
116
    /**
117
     * Check if code amount is over.
118
     *
119
     * @return bool
120
     */
121 27
    public function isOverQuantity(): bool
122
    {
123 27
        $quantity = $this->{static::getQuantityColumn()};
0 ignored issues
show
Bug Best Practice introduced by
The method MichaelRubel\Couponables...on::getQuantityColumn() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

123
        $quantity = $this->{static::/** @scrutinizer ignore-call */ getQuantityColumn()};
Loading history...
124
125 27
        return ! is_null($quantity) && $quantity <= 0;
126
    }
127
128
    /**
129
     * Check if coupon is disposable.
130
     *
131
     * @return bool
132
     */
133 22
    public function isDisposable(): bool
134
    {
135 22
        return $this->{static::getLimitColumn()} == 1;
0 ignored issues
show
Bug Best Practice introduced by
The method MichaelRubel\Couponables...oupon::getLimitColumn() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

135
        return $this->{static::/** @scrutinizer ignore-call */ getLimitColumn()} == 1;
Loading history...
136
    }
137
138
    /**
139
     * Check if the code is reached its global limit.
140
     *
141
     * @param  Model  $redeemer
142
     *
143
     * @return bool
144
     */
145 21
    public function isOverLimit(Model $redeemer): bool
146
    {
147 21
        return ($this->isDisposable() && $redeemer->isCouponAlreadyUsed($this->{static::getCodeColumn()}))
0 ignored issues
show
Bug Best Practice introduced by
The method MichaelRubel\Couponables...Coupon::getCodeColumn() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

147
        return ($this->isDisposable() && $redeemer->isCouponAlreadyUsed($this->{static::/** @scrutinizer ignore-call */ getCodeColumn()}))
Loading history...
148 21
            || $this->isOverLimitFor($redeemer);
149
    }
150
151
    /**
152
     * Check if the code is reached its limit for the passed model.
153
     *
154
     * @param  Model  $redeemer
155
     *
156
     * @return bool
157
     */
158 21
    public function isOverLimitFor(Model $redeemer): bool
159
    {
160 21
        $column = static::getCodeColumn();
0 ignored issues
show
Bug Best Practice introduced by
The method MichaelRubel\Couponables...Coupon::getCodeColumn() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

160
        /** @scrutinizer ignore-call */ 
161
        $column = static::getCodeColumn();
Loading history...
161 21
        $limit  = $this->{static::getLimitColumn()};
0 ignored issues
show
Bug Best Practice introduced by
The method MichaelRubel\Couponables...oupon::getLimitColumn() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

161
        $limit  = $this->{static::/** @scrutinizer ignore-call */ getLimitColumn()};
Loading history...
162
163 21
        return ! is_null($limit) && $limit <= $redeemer
164 21
            ->coupons()
165 21
            ->where($column, $this->{$column})
166 21
            ->count();
167
    }
168
169
    /**
170
     * Check if coupon is already redeemed by the model.
171
     *
172
     * @param  Model  $redeemer
173
     *
174
     * @return bool
175
     */
176 1
    public function isRedeemedBy(Model $redeemer): bool
177
    {
178 1
        $column = static::getCodeColumn();
0 ignored issues
show
Bug Best Practice introduced by
The method MichaelRubel\Couponables...Coupon::getCodeColumn() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

178
        /** @scrutinizer ignore-call */ 
179
        $column = static::getCodeColumn();
Loading history...
179
180 1
        return $redeemer->coupons()->where($column, $this->{$column})->exists();
181
    }
182
183
    /**
184
     * Check if the model is allowed to redeem.
185
     *
186
     * @param  Model  $redeemer
187
     *
188
     * @return bool
189
     */
190 27
    public function isAllowedToRedeemBy(Model $redeemer): bool
191
    {
192 27
        if ($this->isMorphColumnsFilled() && ! $this->redeemer?->is($redeemer)) {
0 ignored issues
show
The property redeemer does not exist on MichaelRubel\Couponables\Models\Coupon. Did you mean redeemer_type?
Loading history...
193 5
            return false;
194
        }
195
196 22
        if ($this->isOnlyRedeemerTypeFilled() && ! $this->isSameRedeemerModel($redeemer)) {
197 1
            return false;
198
        }
199
200 21
        return true;
201
    }
202
203
    /**
204
     * Assign the model to the latest redeemed coupon.
205
     *
206
     * @param  Model  $redeemed
207
     *
208
     * @return CouponContract
209
     */
210 1
    public function for(Model $redeemed): CouponContract
211
    {
212 1
        return with($this->couponables()->first(), function ($couponable) use ($redeemed) {
213 1
            $morphValues = transform($couponable, fn ($bindable) => [
214 1
                $bindable->getRedeemedTypeColumn() => $redeemed->getMorphClass(),
215 1
                $bindable->getRedeemedIdColumn()   => $redeemed->id,
216 1
            ]);
217
218 1
            $couponable->update($morphValues);
219
220 1
            return $this;
221 1
        });
222
    }
223
224
    /**
225
     * Create a new factory instance for the model.
226
     *
227
     * @return Factory<Coupon>
228
     */
229 59
    protected static function newFactory(): Factory
230
    {
231 59
        return CouponFactory::new();
232
    }
233
}
234