Passed
Pull Request — master (#49)
by Nic
02:53
created

DiscountHelper   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 286
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 37
eloc 94
c 3
b 0
f 0
dl 0
loc 286
rs 9.44

16 Methods

Rating   Name   Duplication   Size   Complexity  
A setVariation() 0 7 2
A getProduct() 0 3 1
A setQuantity() 0 11 2
A getQuantity() 0 3 1
A getDiscountTier() 0 7 2
A setProduct() 0 7 1
A getDiscountedPrice() 0 13 3
A setAvailableDiscounts() 0 27 3
A getAvailableDiscounts() 0 3 1
A __construct() 0 8 2
A getFoxyDiscountType() 0 5 2
A setDiscountTier() 0 5 1
B resolveDiscountTiers() 0 33 8
A getDiscountFieldValue() 0 16 4
A getVariation() 0 3 1
A findBestDiscount() 0 12 3
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
34
     */
35
    private $variation;
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
        $this->variation = ($variation instanceof Variation)
0 ignored issues
show
introduced by
$variation is always a sub-type of Dynamic\Foxy\Model\Variation.
Loading history...
140
            ? $variation
141
            : $this->getProduct()->Variations()->filter('', $variation)->first();//TODO fix this string thing, is it needed?
142
143
        return $this;
144
    }
145
146
    /**
147
     * @return mixed
148
     */
149
    public function getVariation()
150
    {
151
        return $this->variation;
152
    }
153
154
    /**
155
     * @param $quantity
156
     * @return $this
157
     */
158
    public function setQuantity($quantity)
159
    {
160
        if ($quantity < 1) {
161
            user_error("\$quantity must be at least 1");
162
        }
163
164
        $this->quantity = $quantity;
165
166
        $this->setDiscountTier();
167
168
        return $this;
169
    }
170
171
    /**
172
     * @return int
173
     */
174
    public function getQuantity()
175
    {
176
        return $this->quantity;
177
    }
178
179
    /**
180
     * @return $this
181
     */
182
    public function setDiscountTier()
183
    {
184
        $this->discount_tier = $this->findBestDiscount();
185
186
        return $this;
187
    }
188
189
    /**
190
     * @return DiscountTier
191
     */
192
    public function getDiscountTier()
193
    {
194
        if (!$this->discount_tier) {
195
            $this->setDiscountTier();
196
        }
197
198
        return $this->discount_tier;
199
    }
200
201
    /**
202
     * @return mixed
203
     */
204
    protected function findBestDiscount()
205
    {
206
        $appropriateTiers = ArrayList::create();
207
208
        /** @var Discount $discount */
209
        foreach ($this->getAvailableDiscounts() as $discount) {
210
            if ($tier = $discount->getTierByQuantity($this->getQuantity())) {
211
                $appropriateTiers->push($tier);
212
            }
213
        }
214
215
        return $this->resolveDiscountTiers($appropriateTiers);
216
    }
217
218
    /**
219
     * @param $discountTiers
220
     * @return DiscountTier|null
221
     */
222
    protected function resolveDiscountTiers($discountTiers)
223
    {
224
        if (!$discountTiers->count()) {
225
            return null;
226
        }
227
228
        $basePrice = $this->getProduct()->Price;
229
        $bestTier = null;
230
        $calculatePrice = function (DiscountTier $tier) use ($basePrice) {
231
            if ($tier->ParentType == 'Percent') {
232
                return $basePrice - ($basePrice * ($tier->Percentage / 100));
233
            } else {
234
                return $basePrice - $tier->Amount;
235
            }
236
        };
237
238
        /** @var DiscountTier $tier */
239
        foreach ($discountTiers as $tier) {
240
            if ($bestTier == null) {
241
                $bestTier = [
242
                    'price' => $calculatePrice($tier),
243
                    'discountTier' => $tier,
244
                ];
245
                continue;
246
            }
247
248
            if ($calculatePrice($tier) < $bestTier['price']) {
249
                $bestTier['price'] = $calculatePrice($tier);
250
                $bestTier['discountTier'] = $tier;
251
            }
252
        }
253
254
        return is_array($bestTier) && isset($bestTier['discountTier']) ? $bestTier['discountTier'] : null;
255
    }
256
257
    /**
258
     * @return DBCurrency
259
     */
260
    public function getDiscountedPrice()
261
    {
262
        $price = ($this->getVariation())
263
            ? $this->getVariation()->FinalPrice
264
            : $this->getProduct()->Price;
265
266
        $tier = $this->getDiscountTier();
267
268
        $price = ($this->getDiscountTier()->ParentType == 'Percent')
269
            ? $price - ($price * ($tier->Percentage / 100))
270
            : $price - $tier->Amount;
271
272
        return DBField::create_field(DBCurrency::class, $price);
273
    }
274
275
    /**
276
     * @return string
277
     */
278
    public function getFoxyDiscountType()
279
    {
280
        return $this->getDiscountTier()->ParentType == 'Percent'
281
            ? 'discount_quantity_percentage'
282
            : 'discount_quantity_amount';
283
    }
284
285
    /**
286
     * @return false|string
287
     */
288
    public function getDiscountFieldValue()
289
    {
290
        if ($this->getDiscountTier()) {
291
            $discount = $this->getDiscountTier()->Discount();
292
            $field = $discount->Type == 'Percent' ? 'Percentage' : 'Amount';
293
            $discountString = $discount->Title . '{allunits';
294
295
            foreach (DiscountTier::get()->filter('DiscountID', $this->getDiscountTier()->DiscountID) as $tier) {
296
                $discountString .= "|{$tier->Quantity}-{$tier->{$field}}";
297
            }
298
299
            $discountString .= '}';
300
            return $discountString;
301
        }
302
303
        return false;
304
    }
305
}
306