Issues (42)

src/DiscountHelper.php (1 issue)

Severity
1
<?php
2
3
namespace Dynamic\Foxy\Discounts;
4
5
use Dynamic\Foxy\Discounts\Model\Discount;
6
use Dynamic\Foxy\Discounts\Model\DiscountTier;
7
use Dynamic\Foxy\Model\Variation;
8
use SilverStripe\Core\Injector\Injectable;
9
use SilverStripe\ORM\ArrayList;
10
use SilverStripe\ORM\DataList;
11
use SilverStripe\ORM\FieldType\DBCurrency;
12
use SilverStripe\ORM\FieldType\DBField;
13
use SilverStripe\Security\Member;
14
use SilverStripe\Security\Security;
15
16
/**
17
 * Class DiscountHelper
18
 * @package Dynamic\Foxy\Discounts
19
 */
20
class DiscountHelper
21
{
22
    use Injectable;
23
24
    /**
25
     * @var
26
     */
27
    private $product;
28
29
    /**
30
     * @var DataList|null
31
     */
32
    private $available_discounts = null;
33
34
    /**
35
     * @var Variation|null
36
     */
37
    private $variation = null;
38
39
40
    /**
41
     * @var DiscountTier
42
     */
43
    private $discount_tier;
44
45
    /**
46
     * @var int
47
     */
48
    private $quantity;
49
50
    /**
51
     * @var DBField|DBCurrency
52
     */
53
    private $discounted_price;
54
55
    /**
56
     * DiscountHelper constructor.
57
     * @param $product
58
     * @param int $quantity
59
     * @param Variation|null $variation
60
     */
61
    public function __construct($product, $quantity = 1, $variation = null)
62
    {
63
        $this->setProduct($product);
64
        $this->setQuantity($quantity);
65
        $this->setDiscountTier();
66
67
        if ($variation instanceof Variation) {
68
            $this->setVariation($variation);
69
        }
70
    }
71
72
    /**
73
     * @return mixed
74
     */
75
    public function getProduct()
76
    {
77
        return $this->product;
78
    }
79
80
    /**
81
     * @param $product
82
     * @return $this
83
     */
84
    public function setProduct($product): self
85
    {
86
        $this->product = $product;
87
88
        $this->setAvailableDiscounts();
89
90
        return $this;
91
    }
92
93
    /**
94
     * Set the available discounts based on DiscountHelper::product
95
     *
96
     * @return $this
97
     */
98
    public function setAvailableDiscounts()
99
    {
100
        if (!$this->getProduct()->ExcludeFromDiscounts) {
101
            $now = date("Y-m-d H:i:s", strtotime('now'));
102
            //don't get discounts the product is excluded from
103
            $list = Discount::get()->exclude([
104
                'ExcludeProducts.ID' => $this->getProduct()->ID,
105
            ])->whereAny([
106
                "`StartTime` <= '{$now}' AND `EndTime` >= '{$now}'",
107
                "(`StartTime` = '' OR `StartTime` IS NULL) AND (`EndTime` = '' OR `EndTime` IS NULL)",
108
                "`StartTime` <= '{$now}' AND (`EndTime` = '' OR `EndTime` IS NULL)",
109
                "(`StartTime` = '' OR `StartTime` IS NULL) AND `EndTime` >= '{$now}'",
110
            ]);
111
112
            $customerDiscountIDs = Member::get()->filter('DiscountID:GreaterThan', 0);
113
114
            if ($member = Security::getCurrentUser()) {
115
                $customerDiscountIDs = $customerDiscountIDs->exclude('ID', $member->ID);
116
            }
117
118
            $customerDiscountIDs = $customerDiscountIDs->column('DiscountID');
119
120
            if (count($customerDiscountIDs)) {
121
                $list = $list->exclude('ID', $customerDiscountIDs);
122
            }
123
124
            $strict = $list->filter([
125
                'Products.Count():GreaterThan' => 0,
126
                'Products.ID' => $this->getProduct()->ID,
127
            ]);
128
129
            $global = $list->filter('Products.Count()', 0);
130
131
            $merge = array_merge(array_values($strict->column()), array_values($global->column()));
132
133
            $this->available_discounts = count($merge) ? Discount::get()->byIDs($merge) : ArrayList::create();
134
        } else {
135
            $this->available_discounts = ArrayList::create();
136
        }
137
138
        return $this;
139
    }
140
141
    /**
142
     * @return DataList|null
143
     */
144
    public function getAvailableDiscounts()
145
    {
146
        return $this->available_discounts;
147
    }
148
149
    /**
150
     * @param Variation $variation
151
     * @return $this
152
     */
153
    public function setVariation($variation): self
154
    {
155
        if (!$variation instanceof Variation) {
0 ignored issues
show
$variation is always a sub-type of Dynamic\Foxy\Model\Variation.
Loading history...
156
            if (is_int($variation) && ($variation = $this->getProduct()->Variations()->byID($variation))) {
157
                $this->variation = $variation;
158
            }
159
        } else {
160
            $this->variation = $variation;
161
        }
162
163
        return $this;
164
    }
165
166
    /**
167
     * @return mixed
168
     */
169
    public function getVariation()
170
    {
171
        return $this->variation;
172
    }
173
174
    /**
175
     * @param $quantity
176
     * @return $this
177
     */
178
    public function setQuantity($quantity)
179
    {
180
        if ($quantity < 1) {
181
            user_error("\$quantity must be at least 1");
182
        }
183
184
        $this->quantity = $quantity;
185
186
        $this->setDiscountTier();
187
188
        return $this;
189
    }
190
191
    /**
192
     * @return int
193
     */
194
    public function getQuantity()
195
    {
196
        return $this->quantity;
197
    }
198
199
    /**
200
     * @return $this
201
     */
202
    public function setDiscountTier()
203
    {
204
        $this->discount_tier = $this->findBestDiscount();
205
206
        return $this;
207
    }
208
209
    /**
210
     * @return DiscountTier
211
     */
212
    public function getDiscountTier()
213
    {
214
        if (!$this->discount_tier) {
215
            $this->setDiscountTier();
216
        }
217
218
        return $this->discount_tier;
219
    }
220
221
    /**
222
     * @return mixed
223
     */
224
    protected function findBestDiscount()
225
    {
226
        $appropriateTiers = ArrayList::create();
227
228
        /** @var Discount $discount */
229
        foreach ($this->getAvailableDiscounts() as $discount) {
230
            if ($tier = $discount->getTierByQuantity($this->getQuantity())) {
231
                $appropriateTiers->push($tier);
232
            }
233
        }
234
235
        return $this->resolveDiscountTiers($appropriateTiers);
236
    }
237
238
    /**
239
     * @param $discountTiers
240
     * @return DiscountTier|null
241
     */
242
    protected function resolveDiscountTiers($discountTiers)
243
    {
244
        if (!$discountTiers->count()) {
245
            return null;
246
        }
247
248
        $basePrice = $this->getProduct()->Price;
249
        $bestTier = null;
250
        $calculatePrice = function (DiscountTier $tier) use ($basePrice) {
251
            if ($tier->ParentType == 'Percent') {
252
                return $basePrice - ($basePrice * ($tier->Percentage / 100));
253
            } else {
254
                return $basePrice - $tier->Amount;
255
            }
256
        };
257
258
        /** @var DiscountTier $tier */
259
        foreach ($discountTiers as $tier) {
260
            if ($bestTier == null) {
261
                $bestTier = [
262
                    'price' => $calculatePrice($tier),
263
                    'discountTier' => $tier,
264
                ];
265
                continue;
266
            }
267
268
            if ($calculatePrice($tier) < $bestTier['price']) {
269
                $bestTier['price'] = $calculatePrice($tier);
270
                $bestTier['discountTier'] = $tier;
271
            }
272
        }
273
274
        return is_array($bestTier) && isset($bestTier['discountTier']) ? $bestTier['discountTier'] : null;
275
    }
276
277
    /**
278
     * @return DBCurrency
279
     */
280
    public function getDiscountedPrice()
281
    {
282
        $price = ($this->getVariation())
283
            ? $this->getVariation()->FinalPrice
284
            : $this->getProduct()->Price;
285
286
        $tier = $this->getDiscountTier();
287
288
        $price = ($this->getDiscountTier()->ParentType == 'Percent')
289
            ? $price - ($price * ($tier->Percentage / 100))
290
            : $price - $tier->Amount;
291
292
        return DBField::create_field(DBCurrency::class, $price);
293
    }
294
295
    /**
296
     * @return string
297
     */
298
    public function getFoxyDiscountType()
299
    {
300
        return $this->getDiscountTier()->ParentType == 'Percent'
301
            ? 'discount_quantity_percentage'
302
            : 'discount_quantity_amount';
303
    }
304
305
    /**
306
     * @return false|string
307
     */
308
    public function getDiscountFieldValue()
309
    {
310
        if ($this->getDiscountTier()) {
311
            $discount = $this->getDiscountTier()->Discount();
312
            $field = $discount->Type == 'Percent' ? 'Percentage' : 'Amount';
313
            $discountString = $discount->Title . '{allunits';
314
315
            foreach (DiscountTier::get()->filter('DiscountID', $this->getDiscountTier()->DiscountID) as $tier) {
316
                $discountString .= "|{$tier->Quantity}-{$tier->{$field}}";
317
            }
318
319
            $discountString .= '}';
320
            return $discountString;
321
        }
322
323
        return false;
324
    }
325
}
326