Passed
Push — master ( a7c50c...ab63ad )
by Nic
03:11
created

DiscountHelper::resolveDiscountTiers()   B

Complexity

Conditions 8
Paths 17

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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