Completed
Push — master ( dc125c...f3f40e )
by Scott
02:45
created

Discount::syncAllPrices()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 24
rs 8.6845
cc 4
eloc 16
nc 5
nop 0
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 - ($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
        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
            foreach ($products as $product) {
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
    /**
233
     * This exists to makes statuses sortable by assigning them a value.
234
     *
235
     * Expired  0
236
     * Running  1
237
     * Upcoming 2
238
     *
239
     * @param  \October\Rain\Database\Builder   $query
240
     * @return \October\Rain\Database\Builder
241
     */
242
    public function scopeSelectStatus($query)
243
    {
244
        $grammar = $query->getQuery()->getGrammar();
245
        $start_at = $grammar->wrap($this->table.'.start_at');
246
        $end_at = $grammar->wrap($this->table.'.end_at');
247
        $now = Carbon::now();
248
249
        $subquery = 'CASE '.
250
            "WHEN ({$end_at} IS NOT NULL AND {$end_at} < '{$now}') THEN 0 ".
251
            "WHEN ({$start_at} IS NOT NULL AND {$start_at} > '{$now}') THEN 2 ".
252
            'ELSE 1 '.
253
        'END';
254
255
        return $query->selectSubquery($subquery, 'status');
256
    }
257
258
    /**
259
     * Set the discount amount.
260
     *
261
     * @return  void
262
     */
263
    public function setAmount()
264
    {
265
        $exact = $this->getOriginalPurgeValue('amount_exact');
266
        $percentage = $this->getOriginalPurgeValue('amount_percentage');
267
268
        $this->amount = $this->is_percentage
269
            ? $percentage
270
            : $exact;
271
    }
272
273
    /**
274
     * Ensure the start and end dates are valid.
275
     *
276
     * @return void
277
     */
278
    public function validateDates()
279
    {
280
        // Start date must be after the end date
281
        if ($this->start_at !== null &&
282
            $this->end_at !== null &&
283
            $this->start_at >= $this->end_at) {
284
            Flash::error(Lang::get('bedard.shop::lang.discounts.form.start_at_invalid'));
285
            throw new ModelException($this);
286
        }
287
    }
288
}
289