Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.33% covered (warning)
83.33%
15 / 18
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Coupon
83.33% covered (warning)
83.33%
15 / 18
66.67% covered (warning)
66.67%
4 / 6
14.91
0.00% covered (danger)
0.00%
0 / 1
 auditName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 auditLabel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 redemptions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setCodeAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isLive
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
7.77
 discountFor
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace App\Models;
4
5use App\Support\Auditable;
6use Illuminate\Database\Eloquent\Model;
7
8class Coupon extends Model
9{
10    use Auditable;
11
12    protected $fillable = [
13        'code', 'type', 'value', 'min_order', 'max_discount',
14        'usage_limit', 'per_customer_limit', 'starts_at', 'ends_at',
15        'is_active', 'description',
16    ];
17
18    protected $casts = [
19        'value' => 'decimal:2',
20        'min_order' => 'decimal:2',
21        'max_discount' => 'decimal:2',
22        'is_active' => 'boolean',
23        'starts_at' => 'datetime',
24        'ends_at' => 'datetime',
25    ];
26
27    protected function auditName(): string
28    {
29        return 'coupon';
30    }
31
32    protected function auditLabel(): ?string
33    {
34        return $this->code;
35    }
36
37    public function redemptions()
38    {
39        return $this->hasMany(CouponRedemption::class);
40    }
41
42    public function setCodeAttribute($value): void
43    {
44        $this->attributes['code'] = strtoupper(trim((string) $value));
45    }
46
47    /** Is the coupon live right now (ignoring the current cart)? */
48    public function isLive(): bool
49    {
50        if (! $this->is_active) {
51            return false;
52        }
53        $now = now();
54        if ($this->starts_at && $now->lt($this->starts_at)) {
55            return false;
56        }
57        if ($this->ends_at && $now->gt($this->ends_at)) {
58            return false;
59        }
60
61        return $this->usage_limit === null || $this->used_count < $this->usage_limit;
62    }
63
64    /** The discount this coupon applies to a given order subtotal. */
65    public function discountFor(float $subtotal): float
66    {
67        $discount = $this->type === 'fixed'
68            ? (float) $this->value
69            : round($subtotal * ((float) $this->value / 100), 2);
70
71        if ($this->max_discount !== null) {
72            $discount = min($discount, (float) $this->max_discount);
73        }
74
75        return (float) min($discount, $subtotal);   // never exceed the subtotal
76    }
77}