Completed
Push — master ( 9dc00c...932e6e )
by Scott
02:45
created

Discount::syncProductPrice()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 17

Duplication

Lines 9
Ratio 36 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 9
loc 25
rs 8.8571
cc 2
eloc 17
nc 2
nop 2
1
<?php namespace Bedard\Shop\Models;
2
3
use Carbon\Carbon;
4
use Flash;
5
use Lang;
6
use Model;
7
use October\Rain\Database\ModelException;
8
use Queue;
9
10
/**
11
 * Discount Model.
12
 */
13
class Discount extends Model
14
{
15
    use \Bedard\Shop\Traits\Subqueryable,
16
        \Bedard\Shop\Traits\Timeable,
17
        \October\Rain\Database\Traits\Purgeable,
18
        \October\Rain\Database\Traits\Validation;
19
20
    /**
21
     * @var string The database table used by the model.
22
     */
23
    public $table = 'bedard_shop_discounts';
24
25
    /**
26
     * @var array Default attributes
27
     */
28
    public $attributes = [
29
        'amount' => 0,
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  float $basePrice
125
     * @return float
126
     */
127
    public function calculatePrice($basePrice)
128
    {
129
        $value = $this->is_percentage
130
            ? $basePrice - ($basePrice * ($this->amount / 100))
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
        Queue::push(function ($job) use ($id) {
180
            $discount = Discount::findOrFail($id);
181
            $productIds = $discount->getAllProductIds();
182
            $products = Product::whereIn('id', $productIds)->select('id', 'base_price')->get();
183
            $discount->prices()->delete();
184
185
            foreach ($productIds as $productId) {
186
                $product = $products->find($productId);
187
                if ($product) {
188
                    Price::create([
189
                        'discount_id' => $discount->id,
190
                        'end_at' => $discount->end_at,
191
                        'price' => $discount->calculatePrice($product->base_price),
192
                        'product_id' => $productId,
193
                        'start_at' => $discount->start_at,
194
                    ]);
195
                }
196
            }
197
198
            $job->delete();
199
        });
200
    }
201
202
    /**
203
     * Sync the prices of all non-expired discounts.
204
     *
205
     * @return void
206
     */
207
    public static function syncAllPrices()
208
    {
209
        $data = [];
210
        $discounts = self::isNotExpired()->with('categories.products', 'products')->get();
211
        foreach ($discounts as $discount) {
212
            $products = $discount->products;
213
            foreach ($discount->categories as $category) {
214
                $products = $products->merge($category->products);
215
            }
216
217 View Code Duplication
            foreach ($products as $product) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
218
                $data[] = [
219
                    'discount_id' => $discount->id,
220
                    'end_at' => $discount->end_at,
221
                    'price' => $discount->calculatePrice($product->base_price),
222
                    'product_id' => $product->id,
223
                    'start_at' => $discount->start_at,
224
                ];
225
            }
226
        }
227
228
        Price::whereNotNull('discount_id')->delete();
229
        Price::insert($data);
230
    }
231
232
    public static function syncProductPrice(Product $product, array $categoryIds)
233
    {
234
        $discounts = self::isNotExpired()
235
            ->whereHas('products', function($product) use ($product) {
0 ignored issues
show
Bug Best Practice introduced by
The parameter name $product conflicts with one of the imported variables.
Loading history...
236
                return $product->where('id', $product->id);
237
            })
238
            ->orWhereHas('categories', function ($category) use ($categoryIds) {
239
                return $category->whereIn('id', $categoryIds);
240
            })
241
            ->get();
242
243
        $data = [];
244 View Code Duplication
        foreach ($discounts as $discount) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
245
            $data[] = [
246
                'discount_id' => $discount->id,
247
                'end_at' => $discount->end_at,
248
                'price' => $discount->calculatePrice($product->base_price),
249
                'product_id' => $product->id,
250
                'start_at' => $discount->start_at,
251
            ];
252
        }
253
254
        Price::whereProductId($product->id)->whereIn('discount_id', $discounts->lists('id'))->delete();
255
        Price::insert($data);
256
    }
257
258
    /**
259
     * This exists to makes statuses sortable by assigning them a value.
260
     *
261
     * Expired  0
262
     * Running  1
263
     * Upcoming 2
264
     *
265
     * @param  \October\Rain\Database\Builder   $query
266
     * @return \October\Rain\Database\Builder
267
     */
268
    public function scopeSelectStatus($query)
269
    {
270
        $grammar = $query->getQuery()->getGrammar();
271
        $start_at = $grammar->wrap($this->table.'.start_at');
272
        $end_at = $grammar->wrap($this->table.'.end_at');
273
        $now = Carbon::now();
274
275
        $subquery = 'CASE '.
276
            "WHEN ({$end_at} IS NOT NULL AND {$end_at} < '{$now}') THEN 0 ".
277
            "WHEN ({$start_at} IS NOT NULL AND {$start_at} > '{$now}') THEN 2 ".
278
            'ELSE 1 '.
279
        'END';
280
281
        return $query->selectSubquery($subquery, 'status');
282
    }
283
284
    /**
285
     * Set the discount amount.
286
     *
287
     * @return  void
288
     */
289
    public function setAmount()
290
    {
291
        $exact = $this->getOriginalPurgeValue('amount_exact');
292
        $percentage = $this->getOriginalPurgeValue('amount_percentage');
293
294
        $this->amount = $this->is_percentage
295
            ? $percentage
296
            : $exact;
297
    }
298
299
    /**
300
     * Ensure the start and end dates are valid.
301
     *
302
     * @return void
303
     */
304
    public function validateDates()
305
    {
306
        // Start date must be after the end date
307
        if ($this->start_at !== null &&
308
            $this->end_at !== null &&
309
            $this->start_at >= $this->end_at) {
310
            Flash::error(Lang::get('bedard.shop::lang.discounts.form.start_at_invalid'));
311
            throw new ModelException($this);
312
        }
313
    }
314
}
315