Passed
Push — master ( 03c0bd...d9a7d5 )
by
unknown
05:08
created

Calculator::getCartTaxFactor()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 28
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
c 0
b 0
f 0
dl 0
loc 28
rs 9.5555
cc 5
nc 3
nop 3
1
<?php
2
/*************************************************************************************/
3
/*      This file is part of the Thelia package.                                     */
4
/*                                                                                   */
5
/*      Copyright (c) OpenStudio                                                     */
6
/*      email : [email protected]                                                       */
7
/*      web : http://www.thelia.net                                                  */
8
/*                                                                                   */
9
/*      For the full copyright and license information, please view the LICENSE.txt  */
10
/*      file that was distributed with this source code.                             */
11
/*************************************************************************************/
12
13
namespace Thelia\TaxEngine;
14
15
use Thelia\Exception\TaxEngineException;
16
use Thelia\Model\Cart;
17
use Thelia\Model\CartItem;
18
use Thelia\Model\Country;
19
use Thelia\Model\Order;
20
use Thelia\Model\OrderProductTax;
21
use Thelia\Model\Product;
22
use Thelia\Model\State;
23
use Thelia\Model\Tax;
24
use Thelia\Model\TaxI18n;
25
use Thelia\Model\TaxRule;
26
use Thelia\Model\TaxRuleQuery;
27
use Thelia\Tools\I18n;
28
29
/**
30
 * Class Calculator
31
 * @package Thelia\TaxEngine
32
 * @author Etienne Roudeix <[email protected]>
33
 * @author Franck Allimant <[email protected]>
34
 * @author Vincent Lopes <[email protected]>
35
 */
36
class Calculator
37
{
38
    /**
39
     * @var TaxRuleQuery
40
     */
41
    protected $taxRuleQuery;
42
43
    /**
44
     * @var null|\Propel\Runtime\Collection\ObjectCollection
45
     */
46
    protected $taxRulesCollection;
47
48
    protected $product;
49
    protected $country;
50
    protected $state;
51
52
    public function __construct()
53
    {
54
        $this->taxRuleQuery = new TaxRuleQuery();
55
    }
56
57
    /**
58
     * @param Cart $cart
59
     * @param Country $country
60
     * @param State|null $state
61
     *
62
     * @return float
63
     * @throws \Propel\Runtime\Exception\PropelException
64
     */
65
    public static function getUntaxedCartDiscount(Cart $cart, Country $country, State $state = null)
66
    {
67
        return $cart->getDiscount() / self::getCartTaxFactor($cart, $country, $state);
68
    }
69
70
    /**
71
     * @param Cart $cart
72
     * @param Country $country
73
     * @param State|null $state
74
     *
75
     * @return float|int
76
     * @throws \Propel\Runtime\Exception\PropelException
77
     */
78
    /**
79
     * @param Order $order
80
     * @return float
81
     * @throws \Propel\Runtime\Exception\PropelException
82
     */
83
    public static function getUntaxedOrderDiscount(Order $order)
84
    {
85
        return $order->getDiscount() / self::getOrderTaxFactor($order);
86
    }
87
88
    /**
89
     * @param Order $order
90
     * @return float
91
     * @throws \Propel\Runtime\Exception\PropelException
92
     */
93
    public static function getOrderTaxFactor(Order $order)
94
    {
95
        // Cache the result in a local variable
96
        static $orderTaxFactor;
97
98
        if (null === $orderTaxFactor) {
99
            if ((float)$order->getDiscount() === 0.0) {
0 ignored issues
show
introduced by
The condition (double)$order->getDiscount() === 0.0 is always false.
Loading history...
100
                return 1;
101
            }
102
103
            // Find the average Tax rate (see \Thelia\TaxEngine\Calculator::getCartTaxFactor())
104
            $orderTaxFactors = [];
105
106
            /** @var \Thelia\Model\OrderProduct $orderProduct */
107
            foreach ($order->getOrderProducts() as $orderProduct) {
108
                /** @var \Thelia\Core\Template\Loop\OrderProductTax $orderProductTax */
109
                foreach ($orderProduct->getOrderProductTaxes() as $orderProductTax) {
110
                    $orderTaxFactors[] = 1 + $orderProductTax->getAmount() / $orderProduct->getPrice();
111
                }
112
            }
113
114
            $orderTaxFactor = array_sum($orderTaxFactors) / count($orderTaxFactors);
115
        }
116
117
        return $orderTaxFactor;
118
    }
119
120
    /**
121
     * @param Cart $cart
122
     * @param Country $country
123
     * @param State|null $state
124
     *
125
     * @return float
126
     * @throws \Propel\Runtime\Exception\PropelException
127
     */
128
    public static function getCartTaxFactor(Cart $cart, Country $country, State $state = null)
129
    {
130
        // Cache the result in a local variable
131
        static $cartFactor;
132
133
        if (null === $cartFactor) {
134
            if ((float)$cart->getDiscount() === 0.0) {
0 ignored issues
show
introduced by
The condition (double)$cart->getDiscount() === 0.0 is always false.
Loading history...
135
                return 1;
136
            }
137
138
            $cartItems = $cart->getCartItems();
139
140
            // Get the average of tax factor to apply it to the discount
141
            $cartTaxFactors = [];
142
143
            /** @var CartItem $cartItem */
144
            foreach ($cartItems as $cartItem) {
145
                $taxRulesCollection = TaxRuleQuery::create()->getTaxCalculatorCollection($cartItem->getProduct()->getTaxRule(), $country, $state);
146
                /** @var TaxRule $taxRule */
147
                foreach ($taxRulesCollection as $taxRule) {
148
                    $cartTaxFactors[] = 1 + $taxRule->getTypeInstance()->pricePercentRetriever();
149
                }
150
            }
151
152
            $cartFactor = array_sum($cartTaxFactors) / count($cartTaxFactors);
153
        }
154
155
        return $cartFactor;
156
    }
157
158
    /**
159
     * @param Product $product
160
     * @param Country $country
161
     * @param State|null $state
162
     * @return $this
163
     * @throws \Propel\Runtime\Exception\PropelException
164
     */
165
    public function load(Product $product, Country $country, State $state = null)
166
    {
167
        if ($product->getId() === null) {
168
            throw new TaxEngineException('Product id is empty in Calculator::load', TaxEngineException::UNDEFINED_PRODUCT);
169
        }
170
        if ($country->getId() === null) {
171
            throw new TaxEngineException('Country id is empty in Calculator::load', TaxEngineException::UNDEFINED_COUNTRY);
172
        }
173
174
        $this->product = $product;
175
        $this->country = $country;
176
        $this->state = $state;
177
178
        $this->taxRulesCollection = $this->taxRuleQuery->getTaxCalculatorCollection($product->getTaxRule(), $country, $state);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->taxRuleQuery->get...le(), $country, $state) can also be of type Propel\Runtime\ActiveRec...ActiveRecordInterface[] or Thelia\Model\Tax[] or array. However, the property $taxRulesCollection is declared as type Propel\Runtime\Collection\ObjectCollection|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
179
180
        return $this;
181
    }
182
183
    /**
184
     * @param TaxRule $taxRule
185
     * @param Country $country
186
     * @param Product $product
187
     * @param State|null $state
188
     * @return $this
189
     * @throws \Propel\Runtime\Exception\PropelException
190
     */
191
    public function loadTaxRule(TaxRule $taxRule, Country $country, Product $product, State $state = null)
192
    {
193
        if ($taxRule->getId() === null) {
194
            throw new TaxEngineException('TaxRule id is empty in Calculator::loadTaxRule', TaxEngineException::UNDEFINED_TAX_RULE);
195
        }
196
        if ($country->getId() === null) {
197
            throw new TaxEngineException('Country id is empty in Calculator::loadTaxRule', TaxEngineException::UNDEFINED_COUNTRY);
198
        }
199
        if ($product->getId() === null) {
200
            throw new TaxEngineException('Product id is empty in Calculator::load', TaxEngineException::UNDEFINED_PRODUCT);
201
        }
202
203
        $this->country = $country;
204
        $this->product = $product;
205
        $this->state = $state;
206
207
        $this->taxRulesCollection = $this->taxRuleQuery->getTaxCalculatorCollection($taxRule, $country, $state);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->taxRuleQuery->get...Rule, $country, $state) can also be of type Propel\Runtime\ActiveRec...ActiveRecordInterface[] or Thelia\Model\Tax[] or array. However, the property $taxRulesCollection is declared as type Propel\Runtime\Collection\ObjectCollection|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
208
209
        return $this;
210
    }
211
212
    /**
213
     * @param TaxRule $taxRule
214
     * @param Product $product
215
     * @return $this
216
     * @throws \Propel\Runtime\Exception\PropelException
217
     */
218
    public function loadTaxRuleWithoutCountry(TaxRule $taxRule, Product $product)
219
    {
220
        $this->product = null;
221
        $this->taxRulesCollection = null;
222
223
        if ($taxRule->getId() === null) {
224
            throw new TaxEngineException('TaxRule id is empty in Calculator::loadTaxRule', TaxEngineException::UNDEFINED_TAX_RULE);
225
        }
226
        if ($product->getId() === null) {
227
            throw new TaxEngineException('Product id is empty in Calculator::load', TaxEngineException::UNDEFINED_PRODUCT);
228
        }
229
230
        $this->product = $product;
231
232
        $this->taxRulesCollection = $this->taxRuleQuery->getTaxCalculatorCollection($taxRule);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->taxRuleQuery->get...torCollection($taxRule) can also be of type Propel\Runtime\ActiveRec...ActiveRecordInterface[] or Thelia\Model\Tax[] or array. However, the property $taxRulesCollection is declared as type Propel\Runtime\Collection\ObjectCollection|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
233
234
        return $this;
235
    }
236
237
    /**
238
     * @param TaxRule $taxRule
239
     * @param Country $country
240
     * @param State|null $state
241
     * @return $this
242
     * @throws \Propel\Runtime\Exception\PropelException
243
     * @since 2.4
244
    */
245
    public function loadTaxRuleWithoutProduct(TaxRule $taxRule, Country $country, State $state = null)
246
    {
247
        if ($taxRule->getId() === null) {
248
            throw new TaxEngineException('TaxRule id is empty in Calculator::loadTaxRule', TaxEngineException::UNDEFINED_TAX_RULE);
249
        }
250
        if ($country->getId() === null) {
251
            throw new TaxEngineException('Country id is empty in Calculator::loadTaxRule', TaxEngineException::UNDEFINED_COUNTRY);
252
        }
253
254
        $this->country = $country;
255
        $this->product = new Product();
256
        $this->state = $state;
257
258
        $this->taxRulesCollection = $this->taxRuleQuery->getTaxCalculatorCollection($taxRule, $country, $state);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->taxRuleQuery->get...Rule, $country, $state) can also be of type Propel\Runtime\ActiveRec...ActiveRecordInterface[] or Thelia\Model\Tax[] or array. However, the property $taxRulesCollection is declared as type Propel\Runtime\Collection\ObjectCollection|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
259
260
        return $this;
261
    }
262
263
    /**
264
     * @param $untaxedPrice
265
     * @param null $taxCollection
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $taxCollection is correct as it would always require null to be passed?
Loading history...
266
     * @return float
267
     * @throws \Propel\Runtime\Exception\PropelException
268
     */
269
    public function getTaxAmountFromUntaxedPrice($untaxedPrice, &$taxCollection = null)
270
    {
271
        return $this->getTaxedPrice($untaxedPrice, $taxCollection) - $untaxedPrice;
272
    }
273
274
    /**
275
     * @param $taxedPrice
276
     * @return float
277
     */
278
    public function getTaxAmountFromTaxedPrice($taxedPrice)
279
    {
280
        return $taxedPrice - $this->getUntaxedPrice($taxedPrice);
281
    }
282
283
    /**
284
     * @param float $untaxedPrice
285
     * @param OrderProductTaxCollection|null $taxCollection returns OrderProductTaxCollection
286
     * @param string|null $askedLocale
287
     * @return float
288
     * @throws \Propel\Runtime\Exception\PropelException
289
     */
290
    public function getTaxedPrice($untaxedPrice, &$taxCollection = null, $askedLocale = null)
291
    {
292
        if (null === $this->taxRulesCollection) {
293
            throw new TaxEngineException('Tax rules collection is empty in Calculator::getTaxedPrice', TaxEngineException::UNDEFINED_TAX_RULES_COLLECTION);
294
        }
295
296
        if (null === $this->product) {
297
            throw new TaxEngineException('Product is empty in Calculator::getTaxedPrice', TaxEngineException::UNDEFINED_PRODUCT);
298
        }
299
300
        if (false === filter_var($untaxedPrice, FILTER_VALIDATE_FLOAT)) {
301
            throw new TaxEngineException('BAD AMOUNT FORMAT', TaxEngineException::BAD_AMOUNT_FORMAT);
302
        }
303
304
        $taxedPrice = $untaxedPrice;
305
        $currentPosition = 1;
306
        $currentTax = 0;
307
308
        if (null !== $taxCollection) {
309
            $taxCollection = new OrderProductTaxCollection();
310
        }
311
312
        /** @var Tax $taxRule */
313
        foreach ($this->taxRulesCollection as $taxRule) {
314
            $position = (int) $taxRule->getTaxRuleCountryPosition();
315
316
            /** @var BaseTaxType $taxType */
317
            $taxType = $taxRule->getTypeInstance();
318
319
            if ($currentPosition !== $position) {
320
                $taxedPrice += $currentTax;
321
                $currentTax = 0;
322
                $currentPosition = $position;
323
            }
324
325
            $taxAmount = $taxType->calculate($this->product, $taxedPrice);
326
            $currentTax += $taxAmount;
327
328
            if (null !== $taxCollection) {
329
                /** @var TaxI18n $taxI18n */
330
                $taxI18n = I18n::forceI18nRetrieving($askedLocale, 'Tax', $taxRule->getId());
331
332
                $orderProductTax = (new OrderProductTax())
333
                    ->setTitle($taxI18n->getTitle())
334
                    ->setDescription($taxI18n->getDescription())
335
                    ->setAmount($taxAmount)
336
                ;
337
338
                $taxCollection->addTax($orderProductTax);
339
            }
340
        }
341
342
        $taxedPrice += $currentTax;
343
344
        return $taxedPrice;
345
    }
346
347
    /**
348
     * @param $taxedPrice
349
     * @return float|int|number
350
     */
351
    public function getUntaxedPrice($taxedPrice)
352
    {
353
        if (null === $this->taxRulesCollection) {
354
            throw new TaxEngineException('Tax rules collection is empty in Calculator::getTaxAmount', TaxEngineException::UNDEFINED_TAX_RULES_COLLECTION);
355
        }
356
357
        if (null === $this->product) {
358
            throw new TaxEngineException('Product is empty in Calculator::getTaxedPrice', TaxEngineException::UNDEFINED_PRODUCT);
359
        }
360
361
        if (false === filter_var($taxedPrice, FILTER_VALIDATE_FLOAT)) {
362
            throw new TaxEngineException('BAD AMOUNT FORMAT', TaxEngineException::BAD_AMOUNT_FORMAT);
363
        }
364
365
        $taxRule = $this->taxRulesCollection->getLast();
366
367
        if (null === $taxRule) {
368
            throw new TaxEngineException('Tax rules collection got no tax ', TaxEngineException::NO_TAX_IN_TAX_RULES_COLLECTION);
369
        }
370
371
        $untaxedPrice = $taxedPrice;
372
        $currentPosition = (int) $taxRule->getTaxRuleCountryPosition();
373
        $currentFixTax = 0;
374
        $currentTaxFactor = 0;
375
376
        do {
377
            $position = (int) $taxRule->getTaxRuleCountryPosition();
378
379
            /** @var BaseTaxType $taxType */
380
            $taxType = $taxRule->getTypeInstance();
381
382
            if ($currentPosition !== $position) {
383
                $untaxedPrice -= $currentFixTax;
384
                $untaxedPrice /= (1 + $currentTaxFactor);
385
                $currentFixTax = 0;
386
                $currentTaxFactor = 0;
387
                $currentPosition = $position;
388
            }
389
390
            $currentFixTax += $taxType->fixAmountRetriever($this->product);
391
            $currentTaxFactor += $taxType->pricePercentRetriever();
392
        } while ($taxRule = $this->taxRulesCollection->getPrevious());
0 ignored issues
show
Deprecated Code introduced by
The function Propel\Runtime\Collectio...llection::getPrevious() has been deprecated: This class is no longer an iterator. Use getIterator() to obtain an iterator to work on. ( Ignorable by Annotation )

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

392
        } while ($taxRule = /** @scrutinizer ignore-deprecated */ $this->taxRulesCollection->getPrevious());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
393
394
        $untaxedPrice -= $currentFixTax;
395
        $untaxedPrice /= (1 + $currentTaxFactor);
396
397
        return $untaxedPrice;
398
    }
399
}
400