Skip to content

Commit

Permalink
Mol 1299/refund tax at net orders (#734)
Browse files Browse the repository at this point in the history
* MOL-1299: Refund Tax at Net Orders

* MOL-1299: Correct VAT Promotion Calculation

---------

Co-authored-by: Thilo Lindner <[email protected]>
  • Loading branch information
ThLind and Thilo Lindner authored Apr 16, 2024
1 parent c29bf21 commit 4b1986b
Show file tree
Hide file tree
Showing 16 changed files with 283 additions and 27 deletions.
103 changes: 97 additions & 6 deletions src/Components/RefundManager/Builder/RefundDataBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
use Kiener\MolliePayments\Struct\OrderLineItemEntity\OrderLineItemEntityAttributes;
use Mollie\Api\Resources\OrderLine;
use Mollie\Api\Resources\Refund;
use Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice;
use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryEntity;
use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Framework\Context;

Expand Down Expand Up @@ -112,15 +114,19 @@ public function buildRefundData(OrderEntity $order, Context $context): RefundDat
$alreadyRefundedQty = $this->getRefundedQuantity($mollieOrderLineId, $mollieOrder, $refunds);
}

$taxTotal = round($this->calculateLineItemTaxTotal($item), 2);
$taxPerItem = floor($taxTotal / $item->getQuantity() * 100) / 100;
$taxDiff = round($taxTotal - ($taxPerItem * $item->getQuantity()), 2);

# this is just a way to move the promotions to the last positions of our array.
# also, shipping-free promotions have their discount item in the deliveries,...so here would just
# be a 0,00 value line item, that we want to skip.
if ($lineItemAttribute->isPromotion()) {
if ($item->getTotalPrice() !== 0.0) {
$refundPromotionItems[] = PromotionItem::fromOrderLineItem($item, $alreadyRefundedQty);
$refundPromotionItems[] = PromotionItem::fromOrderLineItem($item, $alreadyRefundedQty, $taxTotal, $taxPerItem, $taxDiff);
}
} else {
$refundItems[] = new ProductItem($item, $promotionCompositions, $alreadyRefundedQty);
$refundItems[] = new ProductItem($item, $promotionCompositions, $alreadyRefundedQty, $taxTotal, $taxPerItem, $taxDiff);
}
}
}
Expand Down Expand Up @@ -151,10 +157,14 @@ public function buildRefundData(OrderEntity $order, Context $context): RefundDat
$alreadyRefundedQty = $this->getRefundedQuantity($mollieLineID, $mollieOrder, $refunds);
}

$taxTotal = round($this->calculateDeliveryEntityTaxTotal($delivery), 2);
$taxPerItem = floor($taxTotal / $delivery->getShippingCosts()->getQuantity() * 100) / 100;
$taxDiff = round($taxTotal - ($taxPerItem * $delivery->getShippingCosts()->getQuantity()), 2);

if ($delivery->getShippingCosts()->getTotalPrice() < 0) {
$refundPromotionItems[] = PromotionItem::fromOrderDeliveryItem($delivery, $alreadyRefundedQty);
$refundPromotionItems[] = PromotionItem::fromOrderDeliveryItem($delivery, $alreadyRefundedQty, $taxTotal, $taxPerItem, $taxDiff);
} else {
$refundDeliveryItems[] = new DeliveryItem($delivery, $alreadyRefundedQty);
$refundDeliveryItems[] = new DeliveryItem($delivery, $alreadyRefundedQty, $taxTotal, $taxPerItem, $taxDiff);
}
}
}
Expand All @@ -178,6 +188,8 @@ public function buildRefundData(OrderEntity $order, Context $context): RefundDat
# we first need products, then promotions and as last type we add the deliveries
$refundItems = array_merge($refundItems, $refundPromotionItems, $refundDeliveryItems);

// get the tax status of the order
$taxStatus = $order->getTaxStatus();

# now fetch some basic values from the API
# TODO: these API calls should be removed one day, once I have more time (this refund manager is indeed huge) for now it's fine
Expand Down Expand Up @@ -205,7 +217,8 @@ public function buildRefundData(OrderEntity $order, Context $context): RefundDat
$pendingRefundAmount,
$refundedTotal,
$remaining,
$roundingDiffTotal
$roundingDiffTotal,
$taxStatus
);
}

Expand Down Expand Up @@ -242,13 +255,47 @@ private function getAllPromotionCompositions(OrderEntity $order): array

foreach ($order->getLineItems() as $item) {
if (isset($item->getPayload()['composition'])) {
$promotionCompositions[] = $item->getPayload()['composition'];
$promotionComposition = $item->getPayload()['composition'];

$promotionComposition = $this->calculatePromotionCompositionTax($item, $promotionComposition);

$promotionCompositions[] = $promotionComposition;
}
}

return $promotionCompositions;
}

/**
* @param OrderLineItemEntity $item
* @param array<int, mixed> $promotionComposition
* @return array<int, mixed>
*/
private function calculatePromotionCompositionTax(OrderLineItemEntity $item, array $promotionComposition): array
{
$lineItemAttribute = new OrderLineItemEntityAttributes($item);
if ($lineItemAttribute->isPromotion()) {
$taxTotal = round($this->calculateLineItemTaxTotal($item), 2);
$lineItemTotal = $item->getTotalPrice();
$lastIndex = array_keys($promotionComposition)[count($promotionComposition) - 1];

$taxSum = 0;

foreach ($promotionComposition as $i => &$composition) {
$partialTax = round($taxTotal * $composition['discount'] / $lineItemTotal, 2);

if ($i === $lastIndex) {
$partialTax = -$taxTotal - $taxSum;
}

$composition['taxValue'] = $partialTax;
$taxSum += $partialTax;
}
}

return $promotionComposition;
}

/**
* @param string $mollieLineItemId
* @param \Mollie\Api\Resources\Order $mollieOrder
Expand Down Expand Up @@ -312,4 +359,48 @@ private function getRefundedQuantity(string $mollieLineItemId, \Mollie\Api\Resou

return $refundedQty;
}

/**
* @param OrderLineItemEntity $item
* @return float
*/
private function calculateLineItemTaxTotal(OrderLineItemEntity $item): float
{
$taxTotal = 0;

$price = $item->getPrice();

if (!$price instanceof CalculatedPrice) {
return $taxTotal;
}

return $this->calculateTax($price);
}

/**
* @param OrderDeliveryEntity $delivery
* @return float
*/
private function calculateDeliveryEntityTaxTotal(OrderDeliveryEntity $delivery): float
{
$shippingCosts = $delivery->getShippingCosts();

return $this->calculateTax($shippingCosts);
}

/**
* @param CalculatedPrice $price
* @return float
*/
private function calculateTax(CalculatedPrice $price): float
{
$calculatedTaxes = $price->getCalculatedTaxes();
$taxTotal = 0;

foreach ($calculatedTaxes as $calculatedTax) {
$taxTotal += $calculatedTax->getTax();
}

return $taxTotal;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@

abstract class AbstractItem
{
/**
* @var float
*/
private $taxTotal;

/**
* @var float
*/
private $taxPerItem;

/**
* @var float
*/
private $taxDiff;

/**
* @param float $taxTotal
* @param float $taxPerItem
* @param float $taxDiff
*/
public function __construct(float $taxTotal, float $taxPerItem, float $taxDiff)
{
$this->taxTotal = $taxTotal;
$this->taxPerItem = $taxPerItem;
$this->taxDiff = $taxDiff;
}

/**
* @param string $id
* @param string $label
Expand All @@ -15,10 +42,11 @@ abstract class AbstractItem
* @param float $totalPrice
* @param float $promotionDiscount
* @param int $promotionAffectedQty
* @param float $promotionTaxValue
* @param int $refundedQty
* @return array<mixed>
*/
protected function buildArray(string $id, string $label, string $referenceNumber, bool $isPromotion, bool $isDelivery, float $unitPrice, int $quantity, float $totalPrice, float $promotionDiscount, int $promotionAffectedQty, int $refundedQty): array
protected function buildArray(string $id, string $label, string $referenceNumber, bool $isPromotion, bool $isDelivery, float $unitPrice, int $quantity, float $totalPrice, float $promotionDiscount, int $promotionAffectedQty, float $promotionTaxValue, int $refundedQty): array
{
return [
'refunded' => $refundedQty,
Expand All @@ -33,9 +61,15 @@ protected function buildArray(string $id, string $label, string $referenceNumber
'promotion' => [
'discount' => $promotionDiscount,
'quantity' => $promotionAffectedQty,
'taxValue' => $promotionTaxValue,
],
'isPromotion' => $isPromotion,
'isDelivery' => $isDelivery,
'tax' => [
'totalItemTax' => round($this->taxTotal, 2),
'perItemTax' => round($this->taxPerItem, 2),
'totalToPerItemRoundingDiff' => round($this->taxDiff, 2),
],
],
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@ class DeliveryItem extends AbstractItem
/**
* @param OrderDeliveryEntity $delivery
* @param int $alreadyRefundedQuantity
* @param float $taxTotal
* @param float $taxPerItem
* @param float $taxDiff
*/
public function __construct(OrderDeliveryEntity $delivery, int $alreadyRefundedQuantity)
public function __construct(OrderDeliveryEntity $delivery, int $alreadyRefundedQuantity, float $taxTotal, float $taxPerItem, float $taxDiff)
{
$this->delivery = $delivery;
$this->alreadyRefundedQty = $alreadyRefundedQuantity;

parent::__construct($taxTotal, $taxPerItem, $taxDiff);
}


Expand All @@ -49,6 +54,7 @@ public function toArray(): array
$this->delivery->getShippingCosts()->getTotalPrice(),
0,
0,
0,
$this->alreadyRefundedQty
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ class ProductItem extends AbstractItem
*/
private $promotionAffectedQuantity;

/**
* @var float
*/
private $promotionTaxValue;

/**
* @var int
*/
Expand All @@ -31,13 +36,18 @@ class ProductItem extends AbstractItem
* @param OrderLineItemEntity $lineItem
* @param array<mixed> $promotionCompositions
* @param int $alreadyRefundedQuantity
* @param float $taxTotal
* @param float $taxPerItem
* @param float $taxDiff
*/
public function __construct(OrderLineItemEntity $lineItem, array $promotionCompositions, int $alreadyRefundedQuantity)
public function __construct(OrderLineItemEntity $lineItem, array $promotionCompositions, int $alreadyRefundedQuantity, float $taxTotal, float $taxPerItem, float $taxDiff)
{
$this->lineItem = $lineItem;
$this->alreadyRefundedQty = $alreadyRefundedQuantity;

$this->extractPromotionDiscounts($promotionCompositions);

parent::__construct($taxTotal, $taxPerItem, $taxDiff);
}

/**
Expand All @@ -48,6 +58,7 @@ private function extractPromotionDiscounts(array $promotionCompositions)
{
$this->promotionDiscount = 0;
$this->promotionAffectedQuantity = 0;
$this->promotionTaxValue = 0;

foreach ($promotionCompositions as $composition) {
foreach ($composition as $compItem) {
Expand All @@ -56,6 +67,7 @@ private function extractPromotionDiscounts(array $promotionCompositions)
if ($compItem['id'] === $this->lineItem->getReferencedId()) {
$this->promotionDiscount += round((float)$compItem['discount'], 2);
$this->promotionAffectedQuantity += (int)$compItem['quantity'];
$this->promotionTaxValue += round((float)$compItem['taxValue'], 2);
}
}
}
Expand All @@ -77,6 +89,7 @@ public function toArray(): array
$this->lineItem->getTotalPrice(),
$this->promotionDiscount,
$this->promotionAffectedQuantity,
$this->promotionTaxValue,
$this->alreadyRefundedQty
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ class PromotionItem extends AbstractItem
/**
* @param OrderDeliveryEntity|OrderLineItemEntity $lineItem
* @param int $alreadyRefundedQuantity
* @param float $taxTotal
* @param float $taxPerItem
* @param float $taxDiff
*/
private function __construct($lineItem, int $alreadyRefundedQuantity)
private function __construct($lineItem, int $alreadyRefundedQuantity, float $taxTotal, float $taxPerItem, float $taxDiff)
{
if ($lineItem instanceof OrderDeliveryEntity) {
$this->orderDeliveryItem = $lineItem;
Expand All @@ -39,26 +42,34 @@ private function __construct($lineItem, int $alreadyRefundedQuantity)
}

$this->alreadyRefundedQty = $alreadyRefundedQuantity;

parent::__construct($taxTotal, $taxPerItem, $taxDiff);
}

/**
* @param OrderLineItemEntity $lineItem
* @param int $alreadyRefundedQuantity
* @param float $taxTotal
* @param float $taxPerItem
* @param float $taxDiff
* @return PromotionItem
*/
public static function fromOrderLineItem(OrderLineItemEntity $lineItem, int $alreadyRefundedQuantity)
public static function fromOrderLineItem(OrderLineItemEntity $lineItem, int $alreadyRefundedQuantity, float $taxTotal, float $taxPerItem, float $taxDiff)
{
return new PromotionItem($lineItem, $alreadyRefundedQuantity);
return new PromotionItem($lineItem, $alreadyRefundedQuantity, $taxTotal, $taxPerItem, $taxDiff);
}

/**
* @param OrderDeliveryEntity $lineItem
* @param int $alreadyRefundedQuantity
* @param float $taxTotal
* @param float $taxPerItem
* @param float $taxDiff
* @return PromotionItem
*/
public static function fromOrderDeliveryItem(OrderDeliveryEntity $lineItem, int $alreadyRefundedQuantity)
public static function fromOrderDeliveryItem(OrderDeliveryEntity $lineItem, int $alreadyRefundedQuantity, float $taxTotal, float $taxPerItem, float $taxDiff)
{
return new PromotionItem($lineItem, $alreadyRefundedQuantity);
return new PromotionItem($lineItem, $alreadyRefundedQuantity, $taxTotal, $taxPerItem, $taxDiff);
}

/**
Expand All @@ -78,6 +89,7 @@ public function toArray(): array
$this->orderLineItem->getTotalPrice(),
0,
0,
0,
$this->alreadyRefundedQty
);
} else {
Expand All @@ -103,6 +115,7 @@ public function toArray(): array
$this->orderDeliveryItem->getShippingCosts()->getTotalPrice(),
0,
0,
0,
$this->alreadyRefundedQty
);
}
Expand Down
Loading

0 comments on commit 4b1986b

Please sign in to comment.