Completed
Push — master ( d52f5c...f665bb )
by Scott
02:35
created

Discount::syncProductPrice()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 15

Duplication

Lines 9
Ratio 40.91 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 9
loc 22
rs 9.2
cc 2
eloc 15
nc 2
nop 2
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
        'amount' => 0,
31
        'is_percentage' => true,
32
    ];
33
34
    /**
35
     * @var array Guarded fields
36
     */
37
    protected $guarded = ['*'];
38
39
    /**
40
     * @var array Fillable fields
41
     */
42
    protected $fillable = [
43
        'amount',
44
        'amount_exact',
45
        'amount_percentage',
46
        'end_at',
47
        'is_percentage',
48
        'name',
49
        'start_at',
50
    ];
51
52
    /**
53
     * @var array Purgeable vields
54
     */
55
    protected $purgeable = [
56
        'amount_exact',
57
        'amount_percentage',
58
    ];
59
60
    /**
61
     * @var array Relations
62
     */
63
    public $belongsToMany = [
64
        'categories' => [
65
            'Bedard\Shop\Models\Category',
66
            'table' => 'bedard_shop_category_discount',
67
        ],
68
        'products' => [
69
            'Bedard\Shop\Models\Product',
70
            'table' => 'bedard_shop_discount_product',
71
        ],
72
    ];
73
74
    public $hasMany = [
75
        'prices' => [
76
            'Bedard\Shop\Models\Price',
77
            'delete' => true,
78
        ],
79
    ];
80
81
    /**
82
     * @var  array Validation rules
83
     */
84
    public $rules = [
85
        'end_at' => 'date',
86
        'name' => 'required',
87
        'start_at' => 'date',
88
        'amount_exact' => 'numeric|min:0',
89
        'amount_percentage' => 'integer|min:0',
90
    ];
91
92
    /**
93
     * After save.
94
     *
95
     * @return void
96
     */
97
    public function afterSave()
98
    {
99
        $this->savePrices();
100
    }
101
102
    /**
103
     * After validate.
104
     *
105
     * @return void
106
     */
107
    public function afterValidate()
108
    {
109
        $this->validateDates();
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
        $categoryProductIds = $this->categories()
161
            ->select('id')
162
            ->with(['products' => function ($products) {
163
                return $products->select('id');
164
            }])
165
            ->lists('categories.products.id');
166
167
        $productIds = $this->products()->lists('id');
168
169
        return array_unique(array_merge($productIds, $categoryProductIds));
170
    }
171
172
    /**
173
     * Save the prices created by this discount.
174
     *
175
     * @return void
176
     */
177
    public function savePrices()
178
    {
179
        $id = $this->id;
180
        Queue::push(function ($job) use ($id) {
181
            $discount = Discount::findOrFail($id);
182
            $productIds = $discount->getAllProductIds();
183
            $products = Product::whereIn('id', $productIds)->select('id', 'base_price')->get();
184
            $discount->prices()->delete();
185
186
            foreach ($productIds as $productId) {
187
                $product = $products->find($productId);
188
                if ($product) {
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
199
            $job->delete();
200
        });
201
    }
202
203
    /**
204
     * Sync the prices of all non-expired discounts.
205
     *
206
     * @return void
207
     */
208
    public static function syncAllPrices()
209
    {
210
        $data = [];
211
        $discounts = self::isNotExpired()->with('categories.products', 'products')->get();
212
        foreach ($discounts as $discount) {
213
            $products = $discount->products;
214
            foreach ($discount->categories as $category) {
215
                $products = $products->merge($category->products);
216
            }
217
218 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...
219
                $data[] = [
220
                    'discount_id' => $discount->id,
221
                    'end_at' => $discount->end_at,
222
                    'price' => $discount->calculatePrice($product->base_price),
223
                    'product_id' => $product->id,
224
                    'start_at' => $discount->start_at,
225
                ];
226
            }
227
        }
228
229
        Price::whereNotNull('discount_id')->delete();
230
        Price::insert($data);
231
    }
232
233
    public static function syncProductPrice(Product $product, array $categoryIds)
234
    {
235
        $discounts = self::isNotExpired()
236
            ->whereHas('categories', function($category) use ($categoryIds) {
237
                return $category->whereIn('id', $categoryIds);
238
            })
239
            ->get();
240
241
        $data = [];
242 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...
243
            $data[] = [
244
                'discount_id' => $discount->id,
245
                'end_at' => $discount->end_at,
246
                'price' => $discount->calculatePrice($product->base_price),
247
                'product_id' => $product->id,
248
                'start_at' => $discount->start_at,
249
            ];
250
        }
251
252
        Price::whereProductId($product->id)->whereIn('discount_id', $discounts->lists('id'))->delete();
253
        Price::insert($data);
254
    }
255
256
    /**
257
     * This exists to makes statuses sortable by assigning them a value.
258
     *
259
     * Expired  0
260
     * Running  1
261
     * Upcoming 2
262
     *
263
     * @param  \October\Rain\Database\Builder   $query
264
     * @return \October\Rain\Database\Builder
265
     */
266
    public function scopeSelectStatus($query)
267
    {
268
        $grammar = $query->getQuery()->getGrammar();
269
        $start_at = $grammar->wrap($this->table.'.start_at');
270
        $end_at = $grammar->wrap($this->table.'.end_at');
271
        $now = Carbon::now();
272
273
        $subquery = 'CASE '.
274
            "WHEN ({$end_at} IS NOT NULL AND {$end_at} < '{$now}') THEN 0 ".
275
            "WHEN ({$start_at} IS NOT NULL AND {$start_at} > '{$now}') THEN 2 ".
276
            'ELSE 1 '.
277
        'END';
278
279
        return $query->selectSubquery($subquery, 'status');
280
    }
281
282
    /**
283
     * Set the discount amount.
284
     *
285
     * @return  void
286
     */
287
    public function setAmount()
288
    {
289
        $exact = $this->getOriginalPurgeValue('amount_exact');
290
        $percentage = $this->getOriginalPurgeValue('amount_percentage');
291
292
        $this->amount = $this->is_percentage
293
            ? $percentage
294
            : $exact;
295
    }
296
297
    /**
298
     * Ensure the start and end dates are valid.
299
     *
300
     * @return void
301
     */
302
    public function validateDates()
303
    {
304
        // Start date must be after the end date
305
        if ($this->start_at !== null &&
306
            $this->end_at !== null &&
307
            $this->start_at >= $this->end_at) {
308
            Flash::error(Lang::get('bedard.shop::lang.discounts.form.start_at_invalid'));
309
            throw new ModelException($this);
310
        }
311
    }
312
}
313