Completed
Push — master ( a1e415...8aec58 )
by Scott
02:48
created

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