Skip to content

Commit

Permalink
Split the generic sales order data mapper into multiple smaller mappers
Browse files Browse the repository at this point in the history
  • Loading branch information
loevgaard committed Apr 12, 2024
1 parent e3b3780 commit 36af851
Show file tree
Hide file tree
Showing 8 changed files with 336 additions and 75 deletions.
50 changes: 50 additions & 0 deletions src/DataMapper/OrderLinesSalesOrderDataMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusShipmondoPlugin\DataMapper;

use Psr\EventDispatcher\EventDispatcherInterface;
use Setono\Shipmondo\Request\SalesOrders\OrderLine;
use Setono\Shipmondo\Request\SalesOrders\SalesOrder;
use Setono\SyliusShipmondoPlugin\Event\MapOrderLineEvent;
use Setono\SyliusShipmondoPlugin\Model\OrderInterface;
use Sylius\Component\Core\Model\OrderItemUnitInterface;
use Webmozart\Assert\Assert;

final class OrderLinesSalesOrderDataMapper implements SalesOrderDataMapperInterface
{
public function __construct(private readonly EventDispatcherInterface $eventDispatcher)
{
}

public function map(OrderInterface $order, SalesOrder $salesOrder): void
{
foreach ($order->getItems() as $orderItem) {
/** @var OrderItemUnitInterface|false $orderItemUnit */
$orderItemUnit = $orderItem->getUnits()->first();
Assert::isInstanceOf($orderItemUnit, OrderItemUnitInterface::class);

$unitPriceExcludingVat = $orderItemUnit->getTotal() - $orderItemUnit->getTaxTotal();

$orderLine = new OrderLine(
itemName: sprintf('%s (%s)', (string) $orderItem->getProductName(), (string) $orderItem->getVariantName()),
itemSku: $orderItem->getVariant()?->getCode(),
quantity: $orderItem->getQuantity(),
unitPriceExcludingVat: self::formatAmount($unitPriceExcludingVat),
vatPercent: (string) ($orderItemUnit->getTaxTotal() / $unitPriceExcludingVat),
currencyCode: $order->getCurrencyCode(),
unitWeight: null === $orderItem->getVariant()?->getWeight() ? null : (int) $orderItem->getVariant()?->getWeight(),
);

$this->eventDispatcher->dispatch(new MapOrderLineEvent($orderLine, $orderItem, $order));

$salesOrder->orderLines[] = $orderLine;
}
}

private static function formatAmount(int $amount): string
{
return (string) round($amount / 100, 2);
}
}
44 changes: 44 additions & 0 deletions src/DataMapper/PaymentDetailsSalesOrderDataMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Setono\SyliusShipmondoPlugin\DataMapper;

use Setono\Shipmondo\Request\SalesOrders\PaymentDetails;
use Setono\Shipmondo\Request\SalesOrders\SalesOrder;
use Setono\SyliusShipmondoPlugin\Model\OrderInterface;
use Setono\SyliusShipmondoPlugin\Model\PaymentMethodInterface;
use Webmozart\Assert\Assert;

final class PaymentDetailsSalesOrderDataMapper implements SalesOrderDataMapperInterface
{
public function map(OrderInterface $order, SalesOrder $salesOrder): void
{
$paymentMethod = self::getPaymentMethod($order);

$salesOrder->paymentDetails = new PaymentDetails(
amountIncludingVat: self::formatAmount($order->getTotal()), // todo this is not necessarily correct
currencyCode: $order->getCurrencyCode(),
vatAmount: self::formatAmount($order->getTaxTotal()),
paymentMethod: $paymentMethod?->getName(),
paymentGatewayId: null === $paymentMethod ? null : (string) $paymentMethod->getShipmondoId(),
);
}

private static function formatAmount(int $amount): string
{
return (string) round($amount / 100, 2);
}

private static function getPaymentMethod(OrderInterface $order): ?PaymentMethodInterface
{
$paymentMethod = null;
$payment = $order->getPayments()->first();
if (false !== $payment) {
$paymentMethod = $payment->getMethod();
}
Assert::nullOrIsInstanceOf($paymentMethod, PaymentMethodInterface::class);

return $paymentMethod;
}
}
51 changes: 0 additions & 51 deletions src/DataMapper/SalesOrderDataMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,10 @@
use Psr\EventDispatcher\EventDispatcherInterface;
use Setono\Shipmondo\Request\SalesOrders\Address;
use Setono\Shipmondo\Request\SalesOrders\OrderLine;
use Setono\Shipmondo\Request\SalesOrders\PaymentDetails;
use Setono\Shipmondo\Request\SalesOrders\SalesOrder;
use Setono\SyliusShipmondoPlugin\Event\MapOrderLineEvent;
use Setono\SyliusShipmondoPlugin\Event\MapSalesOrderEvent;
use Setono\SyliusShipmondoPlugin\Event\MapShippingOrderLineEvent;
use Setono\SyliusShipmondoPlugin\Model\OrderInterface;
use Sylius\Component\Core\Model\AdjustmentInterface;
use Sylius\Component\Core\Model\OrderItemUnitInterface;
use Webmozart\Assert\Assert;

final class SalesOrderDataMapper implements SalesOrderDataMapperInterface
{
Expand Down Expand Up @@ -50,40 +45,6 @@ public function map(OrderInterface $order, SalesOrder $salesOrder): void
mobile: $order->getBillingAddress()?->getPhoneNumber(),
);

$salesOrder->paymentDetails = new PaymentDetails(
amountIncludingVat: self::formatAmount($order->getTotal()), // todo this is not necessarily correct
currencyCode: $order->getCurrencyCode(),
vatAmount: self::formatAmount($order->getTaxTotal()),
paymentMethod: self::getPaymentMethod($order),
);

foreach ($order->getItems() as $orderItem) {
/** @var OrderItemUnitInterface|false $orderItemUnit */
$orderItemUnit = $orderItem->getUnits()->first();
Assert::isInstanceOf($orderItemUnit, OrderItemUnitInterface::class);

// the following logic is a simple way of finding out whether the tax is included in the price or not
// if the tax adjustment is neutral it means the tax is included in the price
$tax = 0;
$taxAdjustment = $orderItemUnit->getAdjustments(AdjustmentInterface::TAX_ADJUSTMENT)->first();
if (false !== $taxAdjustment && $taxAdjustment->isNeutral()) {
$tax = $taxAdjustment->getAmount();
}

$orderLine = new OrderLine(
itemName: sprintf('%s (%s)', (string) $orderItem->getProductName(), (string) $orderItem->getProductName()),
itemSku: $orderItem->getVariant()?->getCode(),
quantity: $orderItem->getQuantity(),
unitPriceExcludingVat: self::formatAmount($orderItem->getUnitPrice() - $tax),
currencyCode: $order->getCurrencyCode(),
unitWeight: null === $orderItem->getVariant()?->getWeight() ? null : (int) $orderItem->getVariant()?->getWeight(),
);

$this->eventDispatcher->dispatch(new MapOrderLineEvent($orderLine, $orderItem, $order));

$salesOrder->orderLines[] = $orderLine;
}

$shippingAdjustments = $order->getAdjustments(AdjustmentInterface::SHIPPING_ADJUSTMENT);
foreach ($shippingAdjustments as $shippingAdjustment) {
$orderLine = new OrderLine(
Expand All @@ -98,22 +59,10 @@ public function map(OrderInterface $order, SalesOrder $salesOrder): void

$salesOrder->orderLines[] = $orderLine;
}

$this->eventDispatcher->dispatch(new MapSalesOrderEvent($salesOrder, $order));
}

private static function formatAmount(int $amount): string
{
return (string) round($amount / 100, 2);
}

private static function getPaymentMethod(OrderInterface $order): ?string
{
$payment = $order->getPayments()->first();
if (false === $payment) {
return null;
}

return $payment->getMethod()?->getName();
}
}
6 changes: 6 additions & 0 deletions src/DependencyInjection/SetonoSyliusShipmondoExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Setono\SyliusShipmondoPlugin\DependencyInjection;

use Setono\SyliusShipmondoPlugin\DataMapper\SalesOrderDataMapperInterface;
use Setono\SyliusShipmondoPlugin\Workflow\OrderWorkflow;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Extension\AbstractResourceExtension;
use Sylius\Bundle\ResourceBundle\SyliusResourceBundle;
Expand All @@ -24,6 +25,11 @@ public function load(array $configs, ContainerBuilder $container): void
$config = $this->processConfiguration($this->getConfiguration([], $container), $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));

$container
->registerForAutoconfiguration(SalesOrderDataMapperInterface::class)
->addTag('setono_sylius_shipmondo.sales_order_data_mapper')
;

$container->setParameter('setono_sylius_shipmondo.api.username', $config['api']['username']);
$container->setParameter('setono_sylius_shipmondo.api.key', $config['api']['key']);
$container->setParameter('setono_sylius_shipmondo.webhooks.key', $config['webhooks']['key']);
Expand Down
23 changes: 0 additions & 23 deletions src/Event/MapSalesOrderEvent.php

This file was deleted.

16 changes: 15 additions & 1 deletion src/Resources/config/services/data_mapper.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,21 @@
class="Setono\SyliusShipmondoPlugin\DataMapper\SalesOrderDataMapper">
<argument type="service" id="event_dispatcher"/>

<tag name="setono_sylius_shipmondo.sales_order_data_mapper"/>
<tag name="setono_sylius_shipmondo.sales_order_data_mapper" priority="-10"/>
</service>

<service id="setono_sylius_shipmondo.data_mapper.sales_order.payment_details"
class="Setono\SyliusShipmondoPlugin\DataMapper\PaymentDetailsSalesOrderDataMapper">
<argument type="service" id="sylius.repository.payment_method"/>

<tag name="setono_sylius_shipmondo.sales_order_data_mapper" priority="10"/>
</service>

<service id="setono_sylius_shipmondo.data_mapper.sales_order.order_lines"
class="Setono\SyliusShipmondoPlugin\DataMapper\OrderLinesSalesOrderDataMapper">
<argument type="service" id="event_dispatcher"/>

<tag name="setono_sylius_shipmondo.sales_order_data_mapper" priority="20"/>
</service>
</services>
</container>
114 changes: 114 additions & 0 deletions tests/DataMapper/OrderLinesSalesOrderDataMapperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

declare(strict_types=1);

namespace Tests\Setono\SyliusShipmondoPlugin\DataMapper;

use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\EventDispatcher\EventDispatcherInterface;
use Setono\Shipmondo\Request\SalesOrders\SalesOrder;
use Setono\SyliusShipmondoPlugin\DataMapper\OrderLinesSalesOrderDataMapper;
use Sylius\Component\Core\Model\Adjustment;
use Sylius\Component\Core\Model\AdjustmentInterface;
use Sylius\Component\Core\Model\OrderItem;
use Sylius\Component\Core\Model\OrderItemUnit;
use Sylius\Component\Core\Model\Payment;
use Tests\Setono\SyliusShipmondoPlugin\Application\Model\Order;
use Tests\Setono\SyliusShipmondoPlugin\Application\Model\PaymentMethod;

final class OrderLinesSalesOrderDataMapperTest extends TestCase
{
use ProphecyTrait;

/**
* @test
*/
public function it_maps_order_lines_with_tax_included_in_price(): void
{
$order = self::getOrder();
$order->addItem(self::getOrderItem(100, 20, true));
$order->addItem(self::getOrderItem(200, 40, true));

$salesOrder = new SalesOrder();

$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);

$mapper = new OrderLinesSalesOrderDataMapper($eventDispatcher->reveal());
$mapper->map($order, $salesOrder);

self::assertCount(2, $salesOrder->orderLines);
self::assertSame('0.8', $salesOrder->orderLines[0]->unitPriceExcludingVat);
self::assertSame(1, $salesOrder->orderLines[0]->quantity);
self::assertSame('0.25', $salesOrder->orderLines[0]->vatPercent);

self::assertSame('1.6', $salesOrder->orderLines[1]->unitPriceExcludingVat);
self::assertSame(1, $salesOrder->orderLines[1]->quantity);
self::assertSame('0.25', $salesOrder->orderLines[1]->vatPercent);
}

/**
* @test
*/
public function it_maps_order_lines_with_tax_not_included_in_price(): void
{
$order = self::getOrder();
$order->addItem(self::getOrderItem(100, 20, false));
$order->addItem(self::getOrderItem(200, 40, false));

$salesOrder = new SalesOrder();

$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);

$mapper = new OrderLinesSalesOrderDataMapper($eventDispatcher->reveal());
$mapper->map($order, $salesOrder);

self::assertCount(2, $salesOrder->orderLines);
self::assertSame('1', $salesOrder->orderLines[0]->unitPriceExcludingVat);
self::assertSame(1, $salesOrder->orderLines[0]->quantity);
self::assertSame('0.2', $salesOrder->orderLines[0]->vatPercent);
self::assertSame('T-Shirt (XL)', $salesOrder->orderLines[0]->itemName);

self::assertSame('2', $salesOrder->orderLines[1]->unitPriceExcludingVat);
self::assertSame(1, $salesOrder->orderLines[1]->quantity);
self::assertSame('0.2', $salesOrder->orderLines[1]->vatPercent);
self::assertSame('T-Shirt (XL)', $salesOrder->orderLines[1]->itemName);
}

private static function getOrder(): Order
{
$order = new Order();
$order->setCurrencyCode('USD');

$paymentMethod = new PaymentMethod();
$paymentMethod->setCurrentLocale('en_US');
$paymentMethod->setName('Credit Card');
$paymentMethod->setShipmondoId(123);

$payment = new Payment();
$payment->setMethod($paymentMethod);

$order->addPayment($payment);

return $order;
}

private static function getOrderItem(int $unitPrice, int $taxAmount, bool $taxNeutral): OrderItem
{
$orderItem = new OrderItem();
$orderItem->setUnitPrice($unitPrice);

$orderItem->setProductName('T-Shirt');
$orderItem->setVariantName('XL');

$taxAdjustment = new Adjustment();
$taxAdjustment->setAmount($taxAmount);
$taxAdjustment->setNeutral($taxNeutral);
$taxAdjustment->setType(AdjustmentInterface::TAX_ADJUSTMENT);

$orderItemUnit = new OrderItemUnit($orderItem);
$orderItemUnit->addAdjustment($taxAdjustment);

return $orderItem;
}
}
Loading

0 comments on commit 36af851

Please sign in to comment.