Completed
Push — master ( 20c7c7...fa6eea )
by Scott
02:26
created

Discount::afterValidate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php namespace Bedard\Shop\Models;
2
3
use Lang;
4
use Flash;
5
use Model;
6
use Queue;
7
use Carbon\Carbon;
8
use October\Rain\Database\ModelException;
9
10
/**
11
 * Discount Model.
12
 */
13
class Discount extends Model
14
{
15
    use \Bedard\Shop\Traits\StartEndable,
16
        \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
        'amount' => 0,
31
        'amount_exact' => 0,
32
        'amount_percentage' => 0,
33
        'is_percentage' => true,
34
    ];
35
36
    /**
37
     * @var array Attribute casting
38
     */
39
    public $casts = [
40
        'amount' => 'float',
41
        'is_percentage' => 'boolean',
42
    ];
43
44
    /**
45
     * @var array Guarded fields
46
     */
47
    protected $guarded = ['*'];
48
49
    /**
50
     * @var array Fillable fields
51
     */
52
    protected $fillable = [
53
        'amount',
54
        'amount_exact',
55
        'amount_percentage',
56
        'end_at',
57
        'is_percentage',
58
        'name',
59
        'start_at',
60
    ];
61
62
    /**
63
     * @var array Purgeable vields
64
     */
65
    protected $purgeable = [
66
        'amount_exact',
67
        'amount_percentage',
68
    ];
69
70
    /**
71
     * @var array Relations
72
     */
73
    public $belongsToMany = [
74
        'categories' => [
75
            'Bedard\Shop\Models\Category',
76
            'table' => 'bedard_shop_category_discount',
77
        ],
78
        'products' => [
79
            'Bedard\Shop\Models\Product',
80
            'table' => 'bedard_shop_discount_product',
81
        ],
82
    ];
83
84
    public $hasMany = [
85
        'prices' => [
86
            'Bedard\Shop\Models\Price',
87
            'delete' => true,
88
        ],
89
    ];
90
91
    /**
92
     * @var  array Validation rules
93
     */
94
    public $rules = [
95
        'end_at' => 'date',
96
        'name' => 'required',
97
        'start_at' => 'date',
98
        'amount_exact' => 'numeric|min:0',
99
        'amount_percentage' => 'numeric|min:0|max:100',
100
    ];
101
102
    /**
103
     * After save.
104
     *
105
     * @return void
106
     */
107
    public function afterSave()
108
    {
109
        $this->savePrices();
110
    }
111
112
    /**
113
     * Before save.
114
     *
115
     * @return void
116
     */
117
    public function beforeSave()
118
    {
119
        $this->setAmount();
120
    }
121
122
    /**
123
     * Calculate the discounted price.
124
     *
125
     * @param  float $basePrice
126
     * @return float
127
     */
128
    public function calculatePrice($basePrice)
129
    {
130
        $value = $this->is_percentage
131
            ? $basePrice - ($basePrice * ($this->amount / 100))
132
            : $basePrice - $this->amount;
133
134
        if ($value < 0) {
135
            $value = 0;
136
        }
137
138
        return round($value, 2);
139
    }
140
141
    /**
142
     * Filter form fields.
143
     *
144
     * @param  object   $fields
145
     * @return void
146
     */
147
    public function filterFields($fields)
148
    {
149
        $fields->amount_exact->hidden = $this->is_percentage;
150
        $fields->amount_percentage->hidden = ! $this->is_percentage;
151
    }
152
153
    /**
154
     * Fetch all of the products within the scope of this discount.
155
     *
156
     * @return array
157
     */
158
    public function getAllProductIds()
159
    {
160
        $productIds = $this->products()->lists('id');
161
        $categories = $this->categories()
162
            ->with(['products' => function ($product) {
163
                return $product->select('id');
164
            }])
165
            ->get(['id']);
166
167
        foreach ($categories as $category) {
168
            foreach ($category->products as $product) {
169
                $productIds[] = $product->id;
170
            }
171
        }
172
173
        return array_unique($productIds);
174
    }
175
176
    /**
177
     * Get the exact amount.
178
     *
179
     * @return float
180
     */
181
    public function getAmountExactAttribute()
182
    {
183
        return ! $this->is_percentage ? $this->amount : 0;
184
    }
185
186
    /**
187
     * Get the percentage amount.
188
     *
189
     * @return float
190
     */
191
    public function getAmountPercentageAttribute()
192
    {
193
        return $this->is_percentage ? $this->amount : 0;
194
    }
195
196
    /**
197
     * Get categories options.
198
     *
199
     * @return array
200
     */
201
    public function getCategoriesOptions()
202
    {
203
        return Category::lists('name', 'id');
204
    }
205
206
    /**
207
     * Get products options.
208
     *
209
     * @return array
210
     */
211
    public function getProductsOptions()
212
    {
213
        return Product::lists('name', 'id');
214
    }
215
216
    /**
217
     * Save the prices created by this discount.
218
     *
219
     * @return void
220
     */
221
    public function savePrices()
222
    {
223
        $id = $this->id;
224
        Queue::push(function ($job) use ($id) {
225
            $discount = Discount::findOrFail($id);
226
            $productIds = $discount->getAllProductIds();
227
            $products = Product::whereIn('id', $productIds)->select('id', 'base_price')->get();
228
            $discount->prices()->delete();
229
230
            foreach ($productIds as $productId) {
231
                $product = $products->find($productId);
232
                if ($product) {
233
                    Price::create(Discount::buildPriceArray($discount, $product));
234
                }
235
            }
236
237
            $job->delete();
238
        });
239
    }
240
241
    /**
242
     * Sync the prices of all non-expired discounts.
243
     *
244
     * @return void
245
     */
246
    public static function syncAllPrices()
247
    {
248
        $data = [];
249
        $discounts = self::isNotExpired()->with('categories.products', 'products')->get();
250
        foreach ($discounts as $discount) {
251
            $products = $discount->products;
252
            foreach ($discount->categories as $category) {
253
                $products = $products->merge($category->products);
254
            }
255
256
            foreach ($products as $product) {
257
                $data[] = self::buildPriceArray($discount, $product);
258
            }
259
        }
260
261
        Price::whereNotNull('discount_id')->delete();
262
        Price::insert($data);
263
    }
264
265
    /**
266
     * Sync the prices of a single product.
267
     *
268
     * @param  Product $product
269
     * @param  array   $categoryIds
270
     * @return void
271
     */
272
    public static function syncProductPrice(Product $product, array $categoryIds)
273
    {
274
        $discounts = self::isNotExpired()
275
            ->whereHas('products', function ($query) use ($product) {
276
                return $query->where('id', $product->id);
277
            })
278
            ->orWhereHas('categories', function ($query) use ($categoryIds) {
279
                return $query->whereIn('id', $categoryIds);
280
            })
281
            ->get();
282
283
        $data = [];
284
        foreach ($discounts as $discount) {
285
            $data[] = self::buildPriceArray($discount, $product);
286
        }
287
288
        Price::whereProductId($product->id)->whereNotNull('discount_id')->delete();
289
        Price::insert($data);
290
    }
291
292
    /**
293
     * Build the array needed to insert price models.
294
     *
295
     * @param  \Bedard\Shop\Models\Discount $discount
296
     * @param  \Bedard\Shop\Models\Product  $product
297
     * @return array
298
     */
299
    public static function buildPriceArray(Discount $discount, Product $product)
300
    {
301
        return [
302
            'discount_id' => $discount->id,
303
            'end_at' => $discount->end_at,
304
            'price' => $discount->calculatePrice($product->base_price),
305
            'product_id' => $product->id,
306
            'start_at' => $discount->start_at,
307
        ];
308
    }
309
310
    /**
311
     * This exists to makes statuses sortable by assigning them a value.
312
     *
313
     * Expired  0
314
     * Running  1
315
     * Upcoming 2
316
     *
317
     * @param  \October\Rain\Database\Builder   $query
318
     * @return \October\Rain\Database\Builder
319
     */
320
    public function scopeSelectStatus($query)
321
    {
322
        $grammar = $query->getQuery()->getGrammar();
323
        $start_at = $grammar->wrap($this->table.'.start_at');
324
        $end_at = $grammar->wrap($this->table.'.end_at');
325
        $now = Carbon::now();
326
327
        $subquery = 'CASE '.
328
            "WHEN ({$end_at} IS NOT NULL AND {$end_at} < '{$now}') THEN 0 ".
329
            "WHEN ({$start_at} IS NOT NULL AND {$start_at} > '{$now}') THEN 2 ".
330
            'ELSE 1 '.
331
        'END';
332
333
        return $query->selectSubquery($subquery, 'status');
334
    }
335
336
    /**
337
     * Set the discount amount.
338
     *
339
     * @return  void
340
     */
341 View Code Duplication
    public function setAmount()
342
    {
343
        $exact = $this->getOriginalPurgeValue('amount_exact');
344
        $percentage = $this->getOriginalPurgeValue('amount_percentage');
345
346
        $this->amount = $this->is_percentage
347
            ? $percentage
348
            : $exact;
349
    }
350
}
351