<?php
namespace Boldr\Shop\ShopBundle\Presenter\Order;
use Boldr\Shop\ShopBundle\Presenter\Order\ChangeState\{ ChangeStateView, ChangeStateStepView };
use Boldr\Shop\ShopBundle\Entity\{ Order, OrderItem, Option, Discount, Invoice };
use Boldr\Shop\ShopBundle\OrderFlow\OrderFlowManagerInterface;
use Boldr\Shop\ShopBundle\OrderFlow\ChangeStateSteps;
use Boldr\Shop\ShopBundle\OrderFlow\ChangeStateStep\ChangeStateStepInterface;
use Boldr\Shop\ShopBundle\Presenter\Product\ProductPresenter;
use Boldr\Shop\ShopBundle\Presenter\Customer\CustomerPresenter;
use Boldr\Shop\ShopBundle\Presenter\Currency\CurrencyPresenter;
use Boldr\Shop\ShopBundle\Price\Order\{ OrderPriceCalculator, OrderPriceQuery, OrderItemPrice };
use Boldr\Shop\ShopBundle\Price\PriceModifierManager;
use Boldr\Shop\ShopBundle\Presenter\Option\OptionPresenter;
use Boldr\Shop\ShopBundle\Presenter\PriceModifierView;
use Boldr\Cms\CmsBundle\Content\Assets;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Psr\Container\ContainerInterface;
use Boldr\Shop\ShopBundle\Presenter\Price\PricePresenter;
use Boldr\Shop\ShopBundle\DiscountProvider;
use Boldr\Shop\ShopBundle\Presenter\Payment\PaymentOptionPresenter;
class OrderPresenter implements ServiceSubscriberInterface
{
public static function getSubscribedServices(): array
{
return [
OrderFlowManagerInterface::class,
ProductPresenter::class,
CurrencyPresenter::class,
CustomerPresenter::class,
OrderPriceCalculator::class,
PriceModifierManager::class,
UrlGeneratorInterface::class,
TranslatorInterface::class,
OptionPresenter::class,
EntityManagerInterface::class,
PricePresenter::class,
DiscountProvider::class,
PaymentOptionPresenter::class
];
}
private ContainerInterface $container;
/** @var iterable<OrderViewProcessorInterface> */
private iterable $orderViewProcessors;
/** @var iterable<OrderItemViewProcessorInterface> */
private iterable $orderItemViewProcessors;
private array $priceModifierCategories;
private iterable $changeStateStepPresenters;
public function __construct(
ContainerInterface $container,
iterable $orderViewProcessors,
iterable $orderItemViewProcessors,
iterable $changeStateStepPresenters,
)
{
$this->container = $container;
$this->orderViewProcessors = $orderViewProcessors;
$this->orderItemViewProcessors = $orderItemViewProcessors;
$this->changeStateStepPresenters = $changeStateStepPresenters;
}
/**
* Creates a view object for the whole order
*/
public function createOrderView(Order $order, string $locale): OrderView
{
// Create sub-views
$orderItemViews = array_map(
fn($orderItem) => $this->createOrderItemView($orderItem, $locale),
$order->getItems()->toArray()
);
$customerView = $this->container->get(CustomerPresenter::class)->createCustomerView($order->getCustomer(), $locale);
$currencyView = $this->container->get(CurrencyPresenter::class)->createCurrencyView($order->getCurrency(), $locale);
$orderFlowManager = $this->container->get(OrderFlowManagerInterface::class);
// Calculate dynamic options
$nextStateViews = [];
foreach ($orderFlowManager->getNextStates($order) as $nextState)
$nextStateViews[] = $this->createNextStateView($order, $nextState, $locale);
$orderPrice = $this->container->get(OrderPriceCalculator::class)->calculateOrderPrice(new OrderPriceQuery($order));
$stateName = $this->container->get(TranslatorInterface::class)->trans('order_states.'. $order->getState(), [], 'BoldrShopBundle');
$canItemsBeModified = $this->container->get(OrderFlowManagerInterface::class)->canItemsBeModified($order);
$priceModifierManager = $this->container->get(PriceModifierManager::class);
$translator = $this->container->get(TranslatorInterface::class);
$priceModifierCategories = $priceModifierManager->getCategories();
$discountViews = [];
$automaticDiscountViews = [];
$orderDiscounts = $order->getDiscounts()
->filter(fn($orderDiscount) => $orderDiscount->getDiscount() !== null)
->map(fn($orderDiscount) => $orderDiscount->getDiscount())
;
foreach ($this->container->get(DiscountProvider::class)->getOrderDiscounts($order) as $discount)
{
if ($orderDiscounts->contains($discount))
{
$discountViews[] = $this->createDiscountView($discount, $locale);
}
else
{
$automaticDiscountViews[] = $this->createDiscountView($discount, $locale);
}
}
$invoiceAddressView = null;
if ($order->getInvoiceAddress() !== null)
{
$invoiceAddressView = $this->container->get(CustomerPresenter::class)->createAddressView($order->getInvoiceAddress()->getAddress(), null);
}
$invoiceUrls = array_map(
fn($invoice) => $this->container->get(UrlGeneratorInterface::class)->generate('shop_customer_invoice_download', ['invoiceId' => $invoice->getId()]),
array_filter(
$order->getInvoices(),
fn($invoice) => $invoice->getStatus() !== Invoice::STATUS_DRAFT && !$invoice->isProForma(),
)
);
$selectedPaymentOptionView = $order->getSelectedPaymentOption() === null
? null
: $this->container->get(PaymentOptionPresenter::class)->createPaymentOptionView($order->getSelectedPaymentOption(), $locale);
$orderView = new OrderView(
$order->getId(),
$order->getNumber(),
$customerView,
$currencyView,
$this->container->get(PricePresenter::class)->createOrderPriceView($orderPrice, $locale),
$orderItemViews,
$stateName,
$nextStateViews,
$order->getTimeCustomerConfirmed(),
$order->getCustomerNote(),
$canItemsBeModified,
$discountViews,
$automaticDiscountViews,
$invoiceAddressView,
$invoiceUrls,
$order->getCustomer()->getLocale(),
$selectedPaymentOptionView
);
// Process views
return array_reduce(
iterator_to_array($this->orderViewProcessors),
fn($orderView, $orderViewProcessor) => $orderViewProcessor->processOrderView($orderView, $order, $locale),
$orderView
);
}
public function createOrderItemView(OrderItem $orderItem, string $locale)
{
// Create sub-views
$productPresenter = $this->container->get(ProductPresenter::class);
$productView = $productPresenter->createProductView($orderItem->getProductVariant()->getProduct(), $locale);
$productVariantView = $productPresenter->createProductVariantView($orderItem->getProductVariant(), $locale);
// Calculate item price if not determined yet
$invoiceItem = $orderItem->getInvoiceItem();
if ($invoiceItem)
{
$price = new OrderItemPrice($invoiceItem->getUnitPrice(), $orderItem->getQuantity(), $invoiceItem->getModifiers());
}
else
{
$priceCalculator = $this->container->get(OrderPriceCalculator::class);
$orderPrice = $priceCalculator->calculateOrderPrice(new OrderPriceQuery($orderItem->getOrder()));
$price = $orderPrice->getOrderItemPrices()[$orderItem->getId()];
}
$em = $this->container->get(EntityManagerInterface::class);
$optionRepository = $em->getRepository(Option::class);
$optionPresenter = $this->container->get(OptionPresenter::class);
$optionValueViews = [];
$customer = $orderItem->getOrder()->getCustomer();
$productVariant = $orderItem->getProductVariant();
foreach ($orderItem->getOptionValues() as $optionId => $optionValue)
{
$option = $optionRepository->find($optionId);
if ($option !== null)
{
$optionValueView = $optionPresenter->createOptionValueView($option, $productVariant, $customer, $optionValue, $locale);
$optionValueViews[] = $optionValueView;
}
}
$priceModifierManager = $this->container->get(PriceModifierManager::class);
$currency = $orderItem->getOrder()->getCurrency();
$customer = $orderItem->getOrder()->getCustomer();
$priceView = $this->container->get(PricePresenter::class)->createOrderItemPriceView($price, $currency, $customer, $locale);
$orderItemView = new OrderItemView(
$orderItem->getId(),
$productView,
$productVariantView,
$orderItem->getQuantity(),
$priceView,
$optionValueViews
);
// Process view
return array_reduce(
iterator_to_array($this->orderItemViewProcessors),
fn($orderItemView, $orderItemViewProcessor) => $orderItemViewProcessor->processOrderItemView($orderItemView, $orderItem, $locale),
$orderItemView
);
}
public function createChangeStateStepView(ChangeStateStepInterface $changeStateStep, Order $order, string $locale): ?ChangeStateStepView
{
foreach ($this->changeStateStepPresenters as $changeStateStepPresenter)
{
if ($changeStateStepPresenter->supports($changeStateStep))
{
return $changeStateStepPresenter->createChangeStateStepView($changeStateStep, $order, $locale);
}
}
throw new \Exception('No presenter supports '.get_class($changeStateStep));
}
public function createChangeStateView(string $nextState, ChangeStateSteps $changeStateSteps, Order $order, bool $cantConfirm, string $locale): ChangeStateView
{
$orderView = $this->createOrderView($order, $locale);
$changeStateStepViews = [];
$assets = new Assets;
foreach ($changeStateSteps->getSteps() as $changeStateStep)
{
$changeStateStepView = $this->createChangeStateStepView($changeStateStep, $order, $locale);
if ($changeStateStepView !== null)
{
$changeStateStepViews[] = $changeStateStepView;
$assets->addAll($changeStateStepView->getAssets());
}
}
$title = $this->container->get(TranslatorInterface::class)->trans('next_state_buttons.'. $nextState, [], 'BoldrShopBundle', $locale);
return new ChangeStateView(
$nextState,
$title,
$orderView,
$changeStateStepViews,
$assets,
$cantConfirm
);
}
public function createNextStateView(Order $order, string $nextState, string $locale): NextStateView
{
$currentOrder = $this->container->get(OrderFlowManagerInterface::class)->getCurrentOrder($order->getCustomer());
$translator = $this->container->get(TranslatorInterface::class);
$name = $translator->trans('order_states.'. $nextState, [], 'BoldrShopBundle', $locale);
$key = 'next_state_buttons_from.'. $order->getState() .'.'.$nextState;
if ($translator->getCatalogue($locale)->defines($key, 'BoldrShopBundle'))
{
$buttonText = $translator->trans($key, [], 'BoldrShopBundle', $locale);
}
else
{
$buttonText = $translator->trans('next_state_buttons.'. $nextState, [], 'BoldrShopBundle', $locale);
}
$urlGenerator = $this->container->get(UrlGeneratorInterface::class);
if ($currentOrder === $order)
{
$url = $urlGenerator->generate('shop_change_cart_state', [
'_locale' => $locale,
'nextState' => $nextState
], UrlGeneratorInterface::ABSOLUTE_URL);
}
else
{
$url = $urlGenerator->generate('shop_change_order_state', [
'_locale' => $locale,
'orderId' => $order->getId(),
'nextState' => $nextState
], UrlGeneratorInterface::ABSOLUTE_URL);
}
return new NextStateView($nextState, $name, $buttonText, $url);
}
public function createDiscountView(Discount $discount, string $locale): DiscountView
{
$translation = $discount->getTranslations()->get($locale);
return new DiscountView(
$discount->getId(),
$translation->getName(),
$discount->getCode(),
[
Discount::TYPE_AMOUNT => 'amount',
Discount::TYPE_PERCENTAGE => 'percentage',
Discount::TYPE_N_PLUS_N => 'n_plus_n'
][$discount->getType()]
);
}
}