Completed
Pull Request — master (#49)
by Nic
09:03
created

DiscountHelper::getDiscountFieldValue()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 16
rs 9.9666
c 0
b 0
f 0
cc 4
nc 5
nop 0
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\ProductOption;
0 ignored issues
show
Bug introduced by
The type Dynamic\Foxy\Model\ProductOption was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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 ProductOption
34
     */
35
    private $product_option;
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 $discount
57
     * @param int $quantity
58
     * @param ProductOption|string|null $productOption
59
     */
60
    public function __construct($product, $quantity = 1, $productOption = null)
61
    {
62
        $this->setProduct($product);
63
        $this->setQuantity($quantity);
64
        $this->setDiscountTier();
65
66
        if ($productOption instanceof ProductOption || is_string($productOption)) {
67
            $this->setProductOption($productOption);
0 ignored issues
show
Bug introduced by
It seems like $productOption can also be of type string; however, parameter $productOption of Dynamic\Foxy\Discounts\D...per::setProductOption() does only seem to accept Dynamic\Foxy\Model\ProductOption, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

67
            $this->setProductOption(/** @scrutinizer ignore-type */ $productOption);
Loading history...
68
        }
69
    }
70
71
    /**
72
     * @return mixed
73
     */
74
    public function getProduct()
75
    {
76
        return $this->product;
77
    }
78
79
    /**
80
     * @param $product
81
     * @return $this
82
     */
83
    public function setProduct($product): self
84
    {
85
        $this->product = $product;
86
87
        $this->setAvailableDiscounts();
88
89
        return $this;
90
    }
91
92
    /**
93
     * Set the available discounts based on DiscountHelper::product
94
     *
95
     * @return $this
96
     */
97
    public function setAvailableDiscounts()
98
    {
99
        if (!$this->getProduct()->ExcludeFromDiscounts) {
100
            $now = date("Y-m-d H:i:s", strtotime('now'));
101
            //don't get discounts the product is excluded from
102
            $list = Discount::get()->exclude([
103
                'ExcludeProducts.ID' => $this->getProduct()->ID,
104
            ])->whereAny([
105
                "`StartTime` <= '{$now}' AND `EndTime` >= '{$now}'",
106
                "(`StartTime` = '' OR `StartTime` IS NULL) AND (`EndTime` = '' OR `EndTime` IS NULL)",
107
                "`StartTime` <= '{$now}' AND (`EndTime` = '' OR `EndTime` IS NULL)",
108
                "(`StartTime` = '' OR `StartTime` IS NULL) AND `EndTime` >= '{$now}'",
109
            ]);
110
111
            $strict = $list->filter([
112
                'Products.Count():GreaterThan' => 0,
113
                'Products.ID' => $this->getProduct()->ID,
114
            ]);
115
116
            $global = $list->filter('Products.Count()', 0);
117
118
            $merge = array_merge(array_values($strict->column()), array_values($global->column()));
119
            $discounts = Discount::get()->byIDs($merge);
120
121
            $this->available_discounts = $discounts->count() ? $discounts : null;
122
        }
123
124
        return $this;
125
    }
126
127
    /**
128
     * @return DataList|null
129
     */
130
    public function getAvailableDiscounts()
131
    {
132
        return $this->available_discounts;
133
    }
134
135
    /**
136
     * @return mixed
137
     */
138
    public function getProductOption()
139
    {
140
        return $this->product_option;
141
    }
142
143
    /**
144
     * @param ProductOption $productOption
145
     * @return $this
146
     */
147
    public function setProductOption($productOption): self
148
    {
149
        $this->product_option = ($productOption instanceof ProductOption)
150
            ? $productOption
151
            : $this->getProduct()->Options()->filter('OptionModifierKey', $productOption)->first();
152
153
        return $this;
154
    }
155
156
    /**
157
     * @param $quantity
158
     * @return $this
159
     */
160
    public function setQuantity($quantity)
161
    {
162
        $this->quantity = $quantity;
163
164
        $this->setDiscountTier();
165
166
        return $this;
167
    }
168
169
    /**
170
     * @return int
171
     */
172
    public function getQuantity()
173
    {
174
        return $this->quantity;
175
    }
176
177
    /**
178
     * @return $this
179
     */
180
    public function setDiscountTier()
181
    {
182
        $this->discount_tier = $this->findBestDiscount();
183
184
        return $this;
185
    }
186
187
    /**
188
     * @return DiscountTier
189
     */
190
    public function getDiscountTier()
191
    {
192
        if (!$this->discount_tier) {
193
            $this->setDiscountTier();
194
        }
195
196
        return $this->discount_tier;
197
    }
198
199
    /**
200
     * @return mixed
201
     */
202
    protected function findBestDiscount()
203
    {
204
        $appropriateTiers = ArrayList::create();
205
206
        /** @var Discount $discount */
207
        foreach ($this->getAvailableDiscounts() as $discount) {
208
            if ($tier = $discount->getTierByQuantity($this->getQuantity())) {
209
                $appropriateTiers->push($tier);
210
            }
211
        }
212
213
        return $this->resolveDiscountTiers($appropriateTiers);
214
    }
215
216
    /**
217
     * @param $discountTiers
218
     * @return DiscountTier|null
219
     */
220
    protected function resolveDiscountTiers($discountTiers)
221
    {
222
        if (!$discountTiers->count()) {
223
            return null;
224
        }
225
226
        $basePrice = $this->getProduct()->Price;
227
        $bestTier = null;
228
        $calculatePrice = function (DiscountTier $tier) use ($basePrice) {
229
            if ($tier->ParentType == 'Percent') {
230
                return $basePrice - ($basePrice * ($tier->Percentage / 100));
231
            } else {
232
                return $basePrice - $tier->Amount;
233
            }
234
        };
235
236
        /** @var DiscountTier $tier */
237
        foreach ($discountTiers as $tier) {
238
            if ($bestTier == null) {
239
                $bestTier = [
240
                    'price' => $calculatePrice($tier),
241
                    'discountTier' => $tier,
242
                ];
243
                continue;
244
            }
245
246
            if ($calculatePrice($tier) < $bestTier['price']) {
247
                $bestTier['price'] = $calculatePrice($tier);
248
                $bestTier['discountTier'] = $tier;
249
            }
250
        }
251
252
        return is_array($bestTier) && isset($bestTier['discountTier']) ? $bestTier['discountTier'] : null;
253
    }
254
255
    /**
256
     * @return DBCurrency
257
     */
258
    public function getDiscountedPrice()
259
    {
260
        $price = ($this->getProductOption())
261
            ? $this->getProductOption()->getPrice($this->getProduct())
262
            : $this->getProduct()->Price;
263
264
        $tier = $this->getDiscountTier();
265
266
        $price = ($this->getDiscountTier()->ParentType == 'Percent')
267
            ? $price - ($price * ($tier->Percentage / 100))
268
            : $price - $tier->Amount;
269
270
        return DBField::create_field(DBCurrency::class, $price);
271
    }
272
273
    /**
274
     * @return string
275
     */
276
    public function getFoxyDiscountType()
277
    {
278
        return $this->getDiscountTier()->ParentType == 'Percent'
279
            ? 'discount_quantity_percentage'
280
            : 'discount_quantity_amount';
281
    }
282
283
    /**
284
     * @return false|string
285
     */
286
    public function getDiscountFieldValue()
287
    {
288
        if ($this->getDiscountTier()) {
289
            $discount = $this->getDiscountTier()->Discount();
290
            $field = $discount->Type == 'Percent' ? 'Percentage' : 'Amount';
291
            $discountString = $discount->Title . '{allunits';
292
293
            foreach (DiscountTier::get()->filter('DiscountID', $this->getDiscountTier()->DiscountID) as $tier) {
294
                $discountString .= "|{$tier->Quantity}-{$tier->{$field}}";
295
            }
296
297
            $discountString .= '}';
298
            return $discountString;
299
        }
300
301
        return false;
302
    }
303
}
304