|
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) { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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
|
|
|
|
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.