mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 17:10:53 +00:00
Resolve conflicts between 1.12 and 1.13
This commit is contained in:
commit
9701a7b4d9
12 changed files with 404 additions and 58 deletions
|
|
@ -145,11 +145,24 @@ which has the services that implement the `OrderTaxesApplicatorInterface <https:
|
|||
Calculators
|
||||
'''''''''''
|
||||
|
||||
For calculating Taxes **Sylius** is using the `DefaultCalculator <https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Taxation/Calculator/DefaultCalculator.php>`_.
|
||||
For calculating Taxes **Sylius** is using tax calculators.
|
||||
|
||||
To select a proper service we have a one that decides for us
|
||||
- the `DelegatingCalculator <https://github.com/Sylius/Sylius/blob/{current_version}/src/Sylius/Component/Taxation/Calculator/DelegatingCalculator.php>`_.
|
||||
Basing on the **TaxRate** assigned on the Product it will get its calculator type calculate the amount properly.
|
||||
|
||||
You can create your custom calculator for taxes by creating a class that implements
|
||||
the `CalculatorInterface <https://github.com/Sylius/Sylius/blob/master/src/Sylius/Component/Taxation/Calculator/CalculatorInterface.php>`_
|
||||
the `CalculatorInterface <https://github.com/Sylius/Sylius/blob/{current_version}/src/Sylius/Component/Taxation/Calculator/CalculatorInterface.php>`_
|
||||
and registering it as a ``sylius.tax_calculator.your_calculator_name`` service.
|
||||
|
||||
Built-in Calculators
|
||||
''''''''''''''''''''
|
||||
|
||||
The already defined calculators in Sylius:
|
||||
|
||||
* **DefaultCalculator** - calculates the ``amount`` with rounding.
|
||||
* **DecimalCalculator** - calculates the ``amount`` without rounding, which results in a distribution of decimal values among the items.
|
||||
|
||||
Learn more
|
||||
----------
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
@applying_taxes
|
||||
Feature: Apply correct taxes for items with tax rate included in price
|
||||
Feature: Applying correct taxes for items with tax rate included in price
|
||||
In order to pay proper amount when buying goods with tax rate included in price
|
||||
As a Visitor
|
||||
I want to have correct taxes applied to my order
|
||||
|
|
@ -7,14 +7,36 @@ Feature: Apply correct taxes for items with tax rate included in price
|
|||
Background:
|
||||
Given the store operates on a single channel in "United States"
|
||||
And default tax zone is "US"
|
||||
And the store has included in price "VAT" tax rate of 23% for "Clothes" within the "US" zone
|
||||
And the store has a product "PHP T-Shirt" priced at "$100.00"
|
||||
And the store has included in price "VAT" tax rate of 20% for "Clothes" within the "US" zone
|
||||
And the store has a product "PHP T-Shirt" priced at "$19.70"
|
||||
And it belongs to "Clothes" tax category
|
||||
And the store has a product "Symfony T-Shirt" priced at "$19.70"
|
||||
And it belongs to "Clothes" tax category
|
||||
|
||||
@ui @api
|
||||
Scenario: Proper taxes for taxed product
|
||||
Scenario: Applying correct taxes for a single item with tax rate included in price
|
||||
When I add product "PHP T-Shirt" to the cart
|
||||
Then my cart total should be "$100.00"
|
||||
And my included in price taxes should be "$18.70"
|
||||
Then my cart total should be "$19.70"
|
||||
And my included in price taxes should be "$3.28"
|
||||
And there should be one item in my cart
|
||||
And total price of "PHP T-Shirt" item should be "$100.00"
|
||||
And total price of "PHP T-Shirt" item should be "$19.70"
|
||||
|
||||
@ui @api
|
||||
Scenario: Applying correct taxes for a single item with multiple units with tax rate included in price
|
||||
When I add 2 products "PHP T-Shirt" to the cart
|
||||
Then my cart total should be "$39.40"
|
||||
And my included in price taxes should be "$6.57"
|
||||
And total price of "PHP T-Shirt" item should be "$39.40"
|
||||
|
||||
@ui @api
|
||||
Scenario: Applying correct taxes for multiple items with tax rate included in price and default calculator
|
||||
When I add products "PHP T-Shirt" and "Symfony T-Shirt" to the cart
|
||||
Then my cart total should be "$39.40"
|
||||
And my included in price taxes should be "$6.56"
|
||||
|
||||
@ui @api
|
||||
Scenario: Applying correct taxes for multiple items with tax rate included in price and decimal calculator
|
||||
Given the "VAT" tax rate has decimal calculator configured
|
||||
When I add products "PHP T-Shirt" and "Symfony T-Shirt" to the cart
|
||||
Then my cart total should be "$39.40"
|
||||
And my included in price taxes should be "$6.57"
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ final class CartContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @When /^I add (products "([^"]+)" and "([^"]+)") to the cart$/
|
||||
* @When /^I add (products "([^"]+)", "([^"]+)" and "([^"]+)") to the cart$/
|
||||
*/
|
||||
public function iAddMultipleProductsToTheCart(array $products): void
|
||||
|
|
|
|||
|
|
@ -156,6 +156,16 @@ final class TaxationContext implements Context
|
|||
$this->objectManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given the :taxRate tax rate has :calculator calculator configured
|
||||
*/
|
||||
public function theTaxRateHasCalculatorConfigured(TaxRateInterface $taxRate, string $calculator): void
|
||||
{
|
||||
$taxRate->setCalculator($calculator);
|
||||
|
||||
$this->objectManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $taxCategoryName
|
||||
*
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ default:
|
|||
- sylius.behat.context.transform.shared_storage
|
||||
- sylius.behat.context.transform.shipping_method
|
||||
- sylius.behat.context.transform.tax_category
|
||||
- sylius.behat.context.transform.tax_rate
|
||||
- sylius.behat.context.transform.zone
|
||||
|
||||
- sylius.behat.context.setup.channel
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ default:
|
|||
- sylius.behat.context.transform.shared_storage
|
||||
- sylius.behat.context.transform.shipping_method
|
||||
- sylius.behat.context.transform.tax_category
|
||||
- sylius.behat.context.transform.tax_rate
|
||||
- sylius.behat.context.transform.zone
|
||||
|
||||
- sylius.behat.context.setup.channel
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
<argument type="service" id="sylius.factory.adjustment" />
|
||||
<argument type="service" id="sylius.integer_distributor" />
|
||||
<argument type="service" id="sylius.tax_rate_resolver" />
|
||||
<argument type="service" id="sylius.proportional_integer_distributor" />
|
||||
<tag name="sylius.taxation.items.applicator" />
|
||||
</service>
|
||||
<service id="sylius.taxation.order_item_units_taxes_applicator" class="Sylius\Component\Core\Taxation\Applicator\OrderItemUnitsTaxesApplicator">
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@
|
|||
<service id="sylius.tax_calculator.default" class="Sylius\Component\Taxation\Calculator\DefaultCalculator">
|
||||
<tag name="sylius.tax_calculator" calculator="default" />
|
||||
</service>
|
||||
<service id="sylius.tax_calculator.decimal" class="Sylius\Component\Taxation\Calculator\DecimalCalculator">
|
||||
<tag name="sylius.tax_calculator" calculator="decimal" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.tax_rate_resolver" class="Sylius\Component\Taxation\Resolver\TaxRateResolver">
|
||||
<argument type="service" id="sylius.repository.tax_rate" />
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ namespace Sylius\Component\Core\Taxation\Applicator;
|
|||
|
||||
use Sylius\Component\Addressing\Model\ZoneInterface;
|
||||
use Sylius\Component\Core\Distributor\IntegerDistributorInterface;
|
||||
use Sylius\Component\Core\Distributor\ProportionalIntegerDistributorInterface;
|
||||
use Sylius\Component\Core\Model\AdjustmentInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Model\OrderItemInterface;
|
||||
use Sylius\Component\Core\Model\OrderItemUnitInterface;
|
||||
use Sylius\Component\Core\Model\TaxRateInterface;
|
||||
use Sylius\Component\Order\Factory\AdjustmentFactoryInterface;
|
||||
|
|
@ -31,18 +33,54 @@ class OrderItemsTaxesApplicator implements OrderTaxesApplicatorInterface
|
|||
private AdjustmentFactoryInterface $adjustmentFactory,
|
||||
private IntegerDistributorInterface $distributor,
|
||||
private TaxRateResolverInterface $taxRateResolver,
|
||||
private ?ProportionalIntegerDistributorInterface $proportionalIntegerDistributor = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
/** @throws \InvalidArgumentException */
|
||||
public function apply(OrderInterface $order, ZoneInterface $zone): void
|
||||
{
|
||||
foreach ($order->getItems() as $item) {
|
||||
$quantity = $item->getQuantity();
|
||||
Assert::notSame($quantity, 0, 'Cannot apply tax to order item with 0 quantity.');
|
||||
$this->checkItemsQuantities($order);
|
||||
|
||||
if ($this->proportionalIntegerDistributor === null) {
|
||||
$this->applyWithoutDistributionToItems($order, $zone);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$items = $order->getItems()->getValues();
|
||||
$itemTaxFloatAmounts = [];
|
||||
$itemTaxRates = [];
|
||||
|
||||
foreach ($items as $index => $item) {
|
||||
/** @var TaxRateInterface|null $taxRate */
|
||||
$taxRate = $this->taxRateResolver->resolve($item->getVariant(), ['zone' => $zone]);
|
||||
if (null === $taxRate) {
|
||||
$itemTaxFloatAmounts[$index] = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$itemTaxFloatAmounts[$index] = $this->calculator->calculate($item->getTotal(), $taxRate);
|
||||
$itemTaxRates[$index] = $taxRate;
|
||||
}
|
||||
|
||||
$itemTaxWholeAmounts = array_map(fn (float $amount) => (int) round($amount), $itemTaxFloatAmounts);
|
||||
$itemTotalTaxWholeAmount = (int) round(array_sum($itemTaxFloatAmounts));
|
||||
$itemSplitTaxes = $this->proportionalIntegerDistributor->distribute($itemTaxWholeAmounts, $itemTotalTaxWholeAmount);
|
||||
|
||||
foreach ($items as $index => $item) {
|
||||
if (0 === $itemSplitTaxes[$index] || !isset($itemTaxRates[$index])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->distributeTaxesToUnits($itemSplitTaxes[$index], $item->getQuantity(), $item, $itemTaxRates[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
private function applyWithoutDistributionToItems(OrderInterface $order, ZoneInterface $zone): void
|
||||
{
|
||||
foreach ($order->getItems() as $item) {
|
||||
/** @var TaxRateInterface|null $taxRate */
|
||||
$taxRate = $this->taxRateResolver->resolve($item->getVariant(), ['zone' => $zone]);
|
||||
if (null === $taxRate) {
|
||||
|
|
@ -50,19 +88,8 @@ class OrderItemsTaxesApplicator implements OrderTaxesApplicatorInterface
|
|||
}
|
||||
|
||||
$totalTaxAmount = $this->calculator->calculate($item->getTotal(), $taxRate);
|
||||
$splitTaxes = $this->distributor->distribute($totalTaxAmount, $quantity);
|
||||
|
||||
$i = 0;
|
||||
|
||||
/** @var OrderItemUnitInterface $unit */
|
||||
foreach ($item->getUnits() as $unit) {
|
||||
if (0 === $splitTaxes[$i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addAdjustment($unit, $splitTaxes[$i], $taxRate);
|
||||
++$i;
|
||||
}
|
||||
$this->distributeTaxesToUnits($totalTaxAmount, $item->getQuantity(), $item, $taxRate);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,4 +108,33 @@ class OrderItemsTaxesApplicator implements OrderTaxesApplicatorInterface
|
|||
);
|
||||
$unit->addAdjustment($unitTaxAdjustment);
|
||||
}
|
||||
|
||||
private function distributeTaxesToUnits(
|
||||
float $totalTaxAmount,
|
||||
int $quantity,
|
||||
OrderItemInterface $item,
|
||||
TaxRateInterface $taxRate
|
||||
): void {
|
||||
$unitSplitTaxes = $this->distributor->distribute($totalTaxAmount, $quantity);
|
||||
|
||||
$units = $item->getUnits()->getValues();
|
||||
foreach ($units as $index => $unit) {
|
||||
if (!array_key_exists($index, $unitSplitTaxes)) {
|
||||
$index = count($unitSplitTaxes) - 1;
|
||||
}
|
||||
|
||||
if (0 === $unitSplitTaxes[$index]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->addAdjustment($unit, $unitSplitTaxes[$index], $taxRate);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkItemsQuantities(OrderInterface $order): void
|
||||
{
|
||||
foreach ($order->getItems() as $item) {
|
||||
Assert::notSame($item->getQuantity(), 0, 'Cannot apply tax to order item with 0 quantity.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Sylius\Component\Core\Test\Tests\Taxation\Applicator;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Sylius\Component\Addressing\Model\Zone;
|
||||
use Sylius\Component\Core\Distributor\IntegerDistributor;
|
||||
use Sylius\Component\Core\Distributor\ProportionalIntegerDistributor;
|
||||
use Sylius\Component\Core\Model\Adjustment;
|
||||
use Sylius\Component\Core\Model\Order;
|
||||
use Sylius\Component\Core\Model\OrderItem;
|
||||
use Sylius\Component\Core\Model\OrderItemInterface;
|
||||
use Sylius\Component\Core\Model\OrderItemUnit;
|
||||
use Sylius\Component\Core\Model\ProductVariant;
|
||||
use Sylius\Component\Core\Model\TaxRate;
|
||||
use Sylius\Component\Core\Model\TaxRateInterface;
|
||||
use Sylius\Component\Core\Taxation\Applicator\OrderItemsTaxesApplicator;
|
||||
use Sylius\Component\Order\Factory\AdjustmentFactory;
|
||||
use Sylius\Component\Resource\Factory\Factory;
|
||||
use Sylius\Component\Taxation\Calculator\DecimalCalculator;
|
||||
use Sylius\Component\Taxation\Resolver\TaxRateResolverInterface;
|
||||
|
||||
final class OrderItemsTaxesApplicatorTest extends TestCase
|
||||
{
|
||||
public function test_it_calculates_tax_with_decimal_precision(): void
|
||||
{
|
||||
$applicator = new OrderItemsTaxesApplicator(
|
||||
new DecimalCalculator(),
|
||||
new AdjustmentFactory(new Factory(Adjustment::class)),
|
||||
new IntegerDistributor(),
|
||||
$this->createConfiguredMock(TaxRateResolverInterface::class, [
|
||||
'resolve' => $this->createTaxRate(),
|
||||
]),
|
||||
new ProportionalIntegerDistributor(),
|
||||
);
|
||||
|
||||
$order = new Order();
|
||||
for ($i = 0; $i < 20; $i++) {
|
||||
$order->addItem($this->createOrderItem());
|
||||
}
|
||||
|
||||
$applicator->apply($order, new Zone());
|
||||
|
||||
$this->assertEquals(39400, $order->getTotal());
|
||||
$this->assertEquals(6567, $order->getTaxTotal());
|
||||
$this->assertEquals(32833, $order->getTotal() - $order->getTaxTotal());
|
||||
}
|
||||
|
||||
public function createOrderItem(): OrderItemInterface
|
||||
{
|
||||
$item = new OrderItem();
|
||||
$item->setVariant(new ProductVariant());
|
||||
$item->setUnitPrice(1970);
|
||||
$item->addUnit(new OrderItemUnit($item));
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function createTaxRate(): TaxRateInterface
|
||||
{
|
||||
$taxRate = new TaxRate();
|
||||
$taxRate->setCode('standard');
|
||||
$taxRate->setName('Standard');
|
||||
$taxRate->setAmount(0.2);
|
||||
$taxRate->setIncludedInPrice(true);
|
||||
|
||||
return $taxRate;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,10 +14,11 @@ declare(strict_types=1);
|
|||
namespace spec\Sylius\Component\Core\Taxation\Applicator;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Component\Addressing\Model\ZoneInterface;
|
||||
use Sylius\Component\Core\Distributor\IntegerDistributorInterface;
|
||||
use Sylius\Component\Core\Distributor\ProportionalIntegerDistributorInterface;
|
||||
use Sylius\Component\Core\Model\AdjustmentInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Model\OrderItemInterface;
|
||||
|
|
@ -35,7 +36,7 @@ final class OrderItemsTaxesApplicatorSpec extends ObjectBehavior
|
|||
CalculatorInterface $calculator,
|
||||
AdjustmentFactoryInterface $adjustmentsFactory,
|
||||
IntegerDistributorInterface $distributor,
|
||||
TaxRateResolverInterface $taxRateResolver,
|
||||
TaxRateResolverInterface $taxRateResolver
|
||||
): void {
|
||||
$this->beConstructedWith($calculator, $adjustmentsFactory, $distributor, $taxRateResolver);
|
||||
}
|
||||
|
|
@ -45,15 +46,13 @@ final class OrderItemsTaxesApplicatorSpec extends ObjectBehavior
|
|||
$this->shouldImplement(OrderTaxesApplicatorInterface::class);
|
||||
}
|
||||
|
||||
function it_applies_taxes_on_units_based_on_item_total_and_rate(
|
||||
function it_applies_taxes_on_units_based_on_item_total_and_rate_without_distribution_on_items(
|
||||
CalculatorInterface $calculator,
|
||||
AdjustmentFactoryInterface $adjustmentsFactory,
|
||||
IntegerDistributorInterface $distributor,
|
||||
TaxRateResolverInterface $taxRateResolver,
|
||||
AdjustmentInterface $taxAdjustment1,
|
||||
AdjustmentInterface $taxAdjustment2,
|
||||
Collection $items,
|
||||
Collection $units,
|
||||
OrderInterface $order,
|
||||
OrderItemInterface $orderItem,
|
||||
OrderItemUnitInterface $unit1,
|
||||
|
|
@ -62,10 +61,7 @@ final class OrderItemsTaxesApplicatorSpec extends ObjectBehavior
|
|||
TaxRateInterface $taxRate,
|
||||
ZoneInterface $zone,
|
||||
): void {
|
||||
$order->getItems()->willReturn($items);
|
||||
|
||||
$items->count()->willReturn(1);
|
||||
$items->getIterator()->willReturn(new \ArrayIterator([$orderItem->getWrappedObject()]));
|
||||
$order->getItems()->willReturn(new ArrayCollection([$orderItem->getWrappedObject()]));
|
||||
|
||||
$orderItem->getQuantity()->willReturn(2);
|
||||
|
||||
|
|
@ -81,8 +77,7 @@ final class OrderItemsTaxesApplicatorSpec extends ObjectBehavior
|
|||
$taxRate->getAmount()->willReturn(0.1);
|
||||
$taxRate->isIncludedInPrice()->willReturn(false);
|
||||
|
||||
$orderItem->getUnits()->willReturn($units);
|
||||
$units->getIterator()->willReturn(new \ArrayIterator([$unit1->getWrappedObject(), $unit2->getWrappedObject()]));
|
||||
$orderItem->getUnits()->willReturn(new ArrayCollection([$unit1->getWrappedObject(), $unit2->getWrappedObject()]));
|
||||
|
||||
$distributor->distribute(100, 2)->willReturn([50, 50]);
|
||||
|
||||
|
|
@ -122,24 +117,13 @@ final class OrderItemsTaxesApplicatorSpec extends ObjectBehavior
|
|||
|
||||
function it_does_nothing_if_tax_rate_cannot_be_resolved(
|
||||
TaxRateResolverInterface $taxRateResolver,
|
||||
Collection $items,
|
||||
\Iterator $iterator,
|
||||
OrderInterface $order,
|
||||
OrderItemInterface $orderItem,
|
||||
ProductVariantInterface $productVariant,
|
||||
ZoneInterface $zone,
|
||||
): void {
|
||||
$order->getItems()->willReturn($items);
|
||||
|
||||
$items->count()->willReturn(1);
|
||||
$items->getIterator()->willReturn($iterator);
|
||||
$iterator->rewind()->shouldBeCalled();
|
||||
$iterator->valid()->willReturn(true, false)->shouldBeCalled();
|
||||
$iterator->current()->willReturn($orderItem);
|
||||
$iterator->next()->shouldBeCalled();
|
||||
|
||||
$order->getItems()->willReturn(new ArrayCollection([$orderItem->getWrappedObject()]));
|
||||
$orderItem->getQuantity()->willReturn(5);
|
||||
|
||||
$orderItem->getVariant()->willReturn($productVariant);
|
||||
$taxRateResolver->resolve($productVariant, ['zone' => $zone])->willReturn(null);
|
||||
|
||||
|
|
@ -153,8 +137,6 @@ final class OrderItemsTaxesApplicatorSpec extends ObjectBehavior
|
|||
AdjustmentFactoryInterface $adjustmentsFactory,
|
||||
IntegerDistributorInterface $distributor,
|
||||
TaxRateResolverInterface $taxRateResolver,
|
||||
Collection $items,
|
||||
Collection $units,
|
||||
OrderInterface $order,
|
||||
OrderItemInterface $orderItem,
|
||||
OrderItemUnitInterface $unit1,
|
||||
|
|
@ -163,10 +145,7 @@ final class OrderItemsTaxesApplicatorSpec extends ObjectBehavior
|
|||
TaxRateInterface $taxRate,
|
||||
ZoneInterface $zone,
|
||||
): void {
|
||||
$order->getItems()->willReturn($items);
|
||||
|
||||
$items->count()->willReturn(1);
|
||||
$items->getIterator()->willReturn(new \ArrayIterator([$orderItem->getWrappedObject()]));
|
||||
$order->getItems()->willReturn(new ArrayCollection([$orderItem->getWrappedObject()]));
|
||||
|
||||
$orderItem->getQuantity()->willReturn(2);
|
||||
$orderItem->getVariant()->willReturn($productVariant);
|
||||
|
|
@ -179,8 +158,7 @@ final class OrderItemsTaxesApplicatorSpec extends ObjectBehavior
|
|||
$taxRate->getLabel()->willReturn('Simple tax (0%)');
|
||||
$taxRate->isIncludedInPrice()->willReturn(false);
|
||||
|
||||
$orderItem->getUnits()->willReturn($units);
|
||||
$units->getIterator()->willReturn(new \ArrayIterator([$unit1->getWrappedObject(), $unit2->getWrappedObject()]));
|
||||
$orderItem->getUnits()->willReturn(new ArrayCollection([$unit1->getWrappedObject(), $unit2->getWrappedObject()]));
|
||||
|
||||
$distributor->distribute(0, 2)->willReturn([0, 0]);
|
||||
|
||||
|
|
@ -191,4 +169,158 @@ final class OrderItemsTaxesApplicatorSpec extends ObjectBehavior
|
|||
|
||||
$this->apply($order, $zone);
|
||||
}
|
||||
|
||||
function it_applies_taxes_on_units_based_on_item_total_and_rate_with_distribution_on_items(
|
||||
CalculatorInterface $calculator,
|
||||
AdjustmentFactoryInterface $adjustmentsFactory,
|
||||
IntegerDistributorInterface $distributor,
|
||||
TaxRateResolverInterface $taxRateResolver,
|
||||
ProportionalIntegerDistributorInterface $proportionalIntegerDistributor,
|
||||
AdjustmentInterface $taxAdjustment1,
|
||||
AdjustmentInterface $taxAdjustment2,
|
||||
AdjustmentInterface $taxAdjustment3,
|
||||
OrderInterface $order,
|
||||
OrderItemInterface $orderItem1,
|
||||
OrderItemInterface $orderItem2,
|
||||
OrderItemUnitInterface $unit1,
|
||||
OrderItemUnitInterface $unit2,
|
||||
OrderItemUnitInterface $unit3,
|
||||
ProductVariantInterface $productVariant1,
|
||||
ProductVariantInterface $productVariant2,
|
||||
TaxRateInterface $taxRate,
|
||||
ZoneInterface $zone,
|
||||
): void {
|
||||
$this->beConstructedWith($calculator, $adjustmentsFactory, $distributor, $taxRateResolver, $proportionalIntegerDistributor);
|
||||
|
||||
$order->getItems()->willReturn(new ArrayCollection([
|
||||
$orderItem1->getWrappedObject(),
|
||||
$orderItem2->getWrappedObject(),
|
||||
]));
|
||||
|
||||
$orderItem1->getQuantity()->willReturn(2);
|
||||
$orderItem1->getTotal()->willReturn(1000);
|
||||
$orderItem1->getUnits()->willReturn(new ArrayCollection([$unit1->getWrappedObject(), $unit2->getWrappedObject()]));
|
||||
$orderItem1->getVariant()->willReturn($productVariant1);
|
||||
$taxRateResolver->resolve($productVariant1, ['zone' => $zone])->willReturn($taxRate);
|
||||
|
||||
$orderItem2->getQuantity()->willReturn(1);
|
||||
$orderItem2->getTotal()->willReturn(1000);
|
||||
$orderItem2->getUnits()->willReturn(new ArrayCollection([$unit3->getWrappedObject()]));
|
||||
$orderItem2->getVariant()->willReturn($productVariant2);
|
||||
$taxRateResolver->resolve($productVariant2, ['zone' => $zone])->willReturn($taxRate);
|
||||
|
||||
$calculator->calculate(1000, $taxRate)->willReturn(100.40);
|
||||
|
||||
$proportionalIntegerDistributor->distribute([100, 100], 201)->willReturn([101, 100]);
|
||||
|
||||
$taxRate->getLabel()->willReturn('Simple tax (10%)');
|
||||
$taxRate->getCode()->willReturn('simple_tax');
|
||||
$taxRate->getName()->willReturn('Simple tax');
|
||||
$taxRate->getAmount()->willReturn(0.1);
|
||||
$taxRate->isIncludedInPrice()->willReturn(false);
|
||||
|
||||
$distributor->distribute(101, 2)->willReturn([51, 50]);
|
||||
$distributor->distribute(100, 1)->willReturn([100]);
|
||||
|
||||
$adjustmentsFactory
|
||||
->createWithData(
|
||||
AdjustmentInterface::TAX_ADJUSTMENT,
|
||||
'Simple tax (10%)',
|
||||
51,
|
||||
false,
|
||||
[
|
||||
'taxRateCode' => 'simple_tax',
|
||||
'taxRateName' => 'Simple tax',
|
||||
'taxRateAmount' => 0.1,
|
||||
],
|
||||
)
|
||||
->willReturn($taxAdjustment1)
|
||||
;
|
||||
$adjustmentsFactory
|
||||
->createWithData(
|
||||
AdjustmentInterface::TAX_ADJUSTMENT,
|
||||
'Simple tax (10%)',
|
||||
50,
|
||||
false,
|
||||
[
|
||||
'taxRateCode' => 'simple_tax',
|
||||
'taxRateName' => 'Simple tax',
|
||||
'taxRateAmount' => 0.1,
|
||||
],
|
||||
)
|
||||
->willReturn($taxAdjustment2)
|
||||
;
|
||||
$adjustmentsFactory
|
||||
->createWithData(
|
||||
AdjustmentInterface::TAX_ADJUSTMENT,
|
||||
'Simple tax (10%)',
|
||||
100,
|
||||
false,
|
||||
[
|
||||
'taxRateCode' => 'simple_tax',
|
||||
'taxRateName' => 'Simple tax',
|
||||
'taxRateAmount' => 0.1,
|
||||
],
|
||||
)
|
||||
->willReturn($taxAdjustment3)
|
||||
;
|
||||
|
||||
$unit1->addAdjustment($taxAdjustment1)->shouldBeCalled();
|
||||
$unit2->addAdjustment($taxAdjustment2)->shouldBeCalled();
|
||||
$unit3->addAdjustment($taxAdjustment3)->shouldBeCalled();
|
||||
|
||||
$this->apply($order, $zone);
|
||||
}
|
||||
|
||||
function it_does_not_apply_taxes_with_distribution_on_items_if_the_given_item_has_no_tax_rate(
|
||||
CalculatorInterface $calculator,
|
||||
AdjustmentFactoryInterface $adjustmentsFactory,
|
||||
IntegerDistributorInterface $distributor,
|
||||
TaxRateResolverInterface $taxRateResolver,
|
||||
ProportionalIntegerDistributorInterface $proportionalIntegerDistributor,
|
||||
OrderInterface $order,
|
||||
OrderItemInterface $orderItem,
|
||||
OrderItemUnitInterface $unit,
|
||||
ProductVariantInterface $productVariant,
|
||||
ZoneInterface $zone,
|
||||
): void {
|
||||
$this->beConstructedWith($calculator, $adjustmentsFactory, $distributor, $taxRateResolver, $proportionalIntegerDistributor);
|
||||
|
||||
$order->getItems()->willReturn(new ArrayCollection([$orderItem->getWrappedObject()]));
|
||||
|
||||
$orderItem->getQuantity()->willReturn(1);
|
||||
$orderItem->getTotal()->willReturn(1000);
|
||||
$orderItem->getUnits()->willReturn(new ArrayCollection([$unit->getWrappedObject()]));
|
||||
$orderItem->getVariant()->willReturn($productVariant);
|
||||
$taxRateResolver->resolve($productVariant, ['zone' => $zone])->willReturn(null);
|
||||
|
||||
$calculator->calculate(1000, Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$proportionalIntegerDistributor->distribute([0], 0)->willReturn([0, 0]);
|
||||
|
||||
$distributor->distribute(Argument::any())->shouldNotBeCalled();
|
||||
$adjustmentsFactory->createWithData(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->apply($order, $zone);
|
||||
}
|
||||
|
||||
function it_throws_an_invalid_argument_exception_if_order_item_has_0_quantity_during_distribution_on_items(
|
||||
CalculatorInterface $calculator,
|
||||
AdjustmentFactoryInterface $adjustmentsFactory,
|
||||
IntegerDistributorInterface $distributor,
|
||||
TaxRateResolverInterface $taxRateResolver,
|
||||
ProportionalIntegerDistributorInterface $proportionalIntegerDistributor,
|
||||
OrderInterface $order,
|
||||
OrderItemInterface $orderItem,
|
||||
ZoneInterface $zone,
|
||||
): void {
|
||||
$this->beConstructedWith($calculator, $adjustmentsFactory, $distributor, $taxRateResolver, $proportionalIntegerDistributor);
|
||||
|
||||
$items = new ArrayCollection([$orderItem->getWrappedObject()]);
|
||||
$order->getItems()->willReturn($items);
|
||||
|
||||
$orderItem->getQuantity()->willReturn(0);
|
||||
|
||||
$this->shouldThrow(\InvalidArgumentException::class)->during('apply', [$order, $zone]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Component\Taxation\Calculator;
|
||||
|
||||
use Sylius\Component\Taxation\Model\TaxRateInterface;
|
||||
|
||||
final class DecimalCalculator implements CalculatorInterface
|
||||
{
|
||||
public function calculate(float $base, TaxRateInterface $rate): float
|
||||
{
|
||||
if ($rate->isIncludedInPrice()) {
|
||||
return $base - ($base / (1 + $rate->getAmount()));
|
||||
}
|
||||
|
||||
return $base * $rate->getAmount();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue