Completed
Push — master ( 926ee4...8a6c2c )
by Scott
04:10
created

Discount::savePrices()   B

Complexity

Conditions 2
Paths 1

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 25
rs 8.8571
cc 2
eloc 16
nc 1
nop 0
1
<?php namespace Bedard\Shop\Models;
2
3
use Bedard\Shop\Models\Product;
4
use Carbon\Carbon;
5
use Flash;
6
use Lang;
7
use Model;
8
use October\Rain\Database\ModelException;
9
use Queue;
10
11
/**
12
 * Discount Model.
13
 */
14
class Discount extends Model
15
{
16
    use \Bedard\Shop\Traits\Subqueryable,
17
        \Bedard\Shop\Traits\Timeable,
18
        \October\Rain\Database\Traits\Purgeable,
19
        \October\Rain\Database\Traits\Validation;
20
21
    /**
22
     * @var string The database table used by the model.
23
     */
24
    public $table = 'bedard_shop_discounts';
25
26
    /**
27
     * @var array Default attributes
28
     */
29
    public $attributes = [
30
        'is_percentage' => true,
31
    ];
32
33
    /**
34
     * @var array Guarded fields
35
     */
36
    protected $guarded = ['*'];
37
38
    /**
39
     * @var array Fillable fields
40
     */
41
    protected $fillable = [
42
        'amount',
43
        'amount_exact',
44
        'amount_percentage',
45
        'end_at',
46
        'is_percentage',
47
        'name',
48
        'start_at',
49
    ];
50
51
    /**
52
     * @var array Purgeable vields
53
     */
54
    protected $purgeable = [
55
        'amount_exact',
56
        'amount_percentage',
57
    ];
58
59
    /**
60
     * @var array Relations
61
     */
62
    public $belongsToMany = [
63
        'categories' => [
64
            'Bedard\Shop\Models\Category',
65
            'table' => 'bedard_shop_category_discount',
66
        ],
67
        'products' => [
68
            'Bedard\Shop\Models\Product',
69
            'table' => 'bedard_shop_discount_product',
70
        ],
71
    ];
72
73
    public $hasMany = [
74
        'prices' => [
75
            'Bedard\Shop\Models\Price',
76
            'delete' => true,
77
        ],
78
    ];
79
80
    /**
81
     * @var  array Validation rules
82
     */
83
    public $rules = [
84
        'end_at' => 'date',
85
        'name' => 'required',
86
        'start_at' => 'date',
87
        'amount_exact' => 'numeric|min:0',
88
        'amount_percentage' => 'integer|min:0',
89
    ];
90
91
    /**
92
     * After save.
93
     *
94
     * @return void
95
     */
96
    public function afterSave()
97
    {
98
        $this->savePrices();
99
    }
100
101
    /**
102
     * After validate.
103
     *
104
     * @return void
105
     */
106
    public function afterValidate()
107
    {
108
        $this->validateDates();
109
    }
110
111
    /**
112
     * Before save.
113
     *
114
     * @return void
115
     */
116
    public function beforeSave()
117
    {
118
        $this->setAmount();
119
    }
120
121
    /**
122
     * Calculate the discounted price.
123
     *
124
     * @param  double $basePrice
125
     * @return double
126
     */
127
    public function calculatePrice($basePrice)
128
    {
129
        $value = $this->is_percentage
130
            ? $basePrice - ($this->amount > 0 ? $basePrice / $this->amount : 0)
131
            : $basePrice - $this->amount;
132
133
        if ($value < 0) {
134
            $value = 0;
135
        }
136
137
        return round($value, 2);
138
    }
139
140
    /**
141
     * Filter form fields.
142
     *
143
     * @param  object   $fields
144
     * @return void
145
     */
146
    public function filterFields($fields)
147
    {
148
        $fields->amount_exact->hidden = $this->is_percentage;
149
        $fields->amount_percentage->hidden = ! $this->is_percentage;
150
    }
151
152
    /**
153
     * Fetch all of the products within the scope of this discount.
154
     *
155
     * @return array
156
     */
157
    public function getAllProductIds()
158
    {
159
        $categoryProductIds = $this->categories()
160
            ->select('id')
161
            ->with(['products' => function ($products) {
162
                return $products->select('id');
163
            }])
164
            ->lists('categories.products.id');
165
166
        $productIds = $this->products()->lists('id');
167
168
        return array_unique(array_merge($productIds, $categoryProductIds));
169
    }
170
171
    /**
172
     * Save the prices created by this discount.
173
     *
174
     * @return void
175
     */
176
    public function savePrices()
177
    {
178
        $id = $this->id;
179
        $productIds = $this->getAllProductIds();
180
181
        Queue::push(function ($job) use ($id, $productIds) {
182
            $discount = Discount::findOrFail($id);
183
            $products = Product::whereIn('id', $productIds)->select('id', 'base_price')->get();
184
185
            $discount->prices()->delete();
186
187
            foreach ($productIds as $productId) {
188
                $product = $products->find($productId);
189
                Price::create([
190
                    'discount_id' => $discount->id,
191
                    'end_at' => $discount->end_at,
192
                    'price' => $discount->calculatePrice($product->base_price),
193
                    'product_id' => $productId,
194
                    'start_at' => $discount->start_at,
195
                ]);
196
            }
197
198
            $job->delete();
199
        });
200
    }
201
202
    /**
203
     * This exists to makes statuses sortable by assigning them a value.
204
     *
205
     * Expired  0
206
     * Running  1
207
     * Upcoming 2
208
     *
209
     * @param  \October\Rain\Database\Builder   $query
210
     * @return \October\Rain\Database\Builder
211
     */
212
    public function scopeSelectStatus($query)
213
    {
214
        $grammar = $query->getQuery()->getGrammar();
215
        $start_at = $grammar->wrap($this->table.'.start_at');
216
        $end_at = $grammar->wrap($this->table.'.end_at');
217
        $now = Carbon::now();
218
219
        $subquery = 'CASE '.
220
            "WHEN ({$end_at} IS NOT NULL AND {$end_at} < '{$now}') THEN 0 ".
221
            "WHEN ({$start_at} IS NOT NULL AND {$start_at} > '{$now}') THEN 2 ".
222
            'ELSE 1 '.
223
        'END';
224
225
        return $query->selectSubquery($subquery, 'status');
226
    }
227
228
    /**
229
     * Set the discount amount.
230
     *
231
     * @return  void
232
     */
233
    public function setAmount()
234
    {
235
        $exact = $this->getOriginalPurgeValue('amount_exact');
236
        $percentage = $this->getOriginalPurgeValue('amount_percentage');
237
238
        $this->amount = $this->is_percentage
239
            ? $percentage
240
            : $exact;
241
    }
242
243
    /**
244
     * Ensure the start and end dates are valid.
245
     *
246
     * @return void
247
     */
248
    public function validateDates()
249
    {
250
        // Start date must be after the end date
251
        if ($this->start_at !== null &&
252
            $this->end_at !== null &&
253
            $this->start_at >= $this->end_at) {
254
            Flash::error(Lang::get('bedard.shop::lang.discounts.form.start_at_invalid'));
255
            throw new ModelException($this);
256
        }
257
    }
258
}
259