<?php
/**
* Created by Elements.at New Media Solutions GmbH
*
*/
namespace App\Ecommerce\PriceSystem\Event;
use App\Ecommerce\PricingManager\Environment;
use App\Model\Shop\Event\EventProduct;
use App\Model\Shop\Event\ShopDynamicPrice;
use Carbon\Carbon;
use Elements\Bundle\TicketShopFrameworkBundle\Service\CartService;
use Pimcore\Bundle\EcommerceFrameworkBundle\Factory;
use Pimcore\Bundle\EcommerceFrameworkBundle\Model\CheckoutableInterface;
use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\CachingPriceSystem;
use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\Price;
use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\PriceSystemInterface;
use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\TaxManagement\TaxCalculationService;
use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\TaxManagement\TaxEntry;
use Pimcore\Bundle\EcommerceFrameworkBundle\PricingManager\PriceInfoInterface;
use Pimcore\Bundle\EcommerceFrameworkBundle\PricingManager\PricingManagerLocatorInterface;
use Pimcore\Bundle\EcommerceFrameworkBundle\Type\Decimal;
use Pimcore\Model\DataObject\AbstractObject;
use Pimcore\Model\DataObject\OnlineShopTaxClass;
use Pimcore\Model\Exception\UnsupportedException;
class PriceSystem extends CachingPriceSystem implements PriceSystemInterface
{
public function __construct(
PricingManagerLocatorInterface $pricingManagers,
private CartService $cartService
) {
parent::__construct($pricingManagers);
}
protected function initPriceInfoInstance(mixed $quantityScale, CheckoutableInterface $product, $products = null, Carbon $date = null): \Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\PriceInfoInterface
{
$priceInfo = $this->createPriceInfoInstance($quantityScale, $product, $products, $date);
if ($quantityScale !== \Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\PriceInfoInterface::MIN_PRICE) {
$priceInfo->setQuantity($quantityScale);
}
$priceInfo->setProduct($product);
$priceInfo->setProducts($products);
$priceInfo->setPriceSystem($this);
$priceInfo->setBaseAmount($priceInfo->getPrice()->getAmount()->asNumeric());
// apply pricing rules
/** @var \Elements\Bundle\TicketShopFrameworkBundle\Ecommerce\PricingManager\PriceInfo $priceInfoWithRules */
$priceInfoWithRules = $this->getPricingManager()->applyProductRules($priceInfo);
$priceInfoWithRules->setBaseAmount($priceInfo->getPrice()->getAmount());
/** @var Environment $env */
$env = $priceInfoWithRules->getEnvironment();
$env->setCartForProduct($this->cartService->getCart());
if ($product instanceof EventProduct) {
$orderDate = $product->getProductStartDate();
if ($orderDate) {
$env->setStartDate($orderDate);
}
}
return $priceInfoWithRules;
}
/**
* @param mixed $quantityScale
* @param CheckoutableInterface $product
* @param CheckoutableInterface[] $products
* @param Carbon|null $date
*
* @return PriceInfo
*/
public function createPriceInfoInstance(
mixed $quantityScale,
CheckoutableInterface $product,
mixed $products,
?Carbon $date = null
): PriceInfo {
if (!$product instanceof EventProduct) {
throw new UnsupportedException('Object must be of type EventProduct');
}
$priceInfo = new PriceInfo($product);
$priceInfo->setQuantity($quantityScale);
$priceInfo->setPrices($this->getPrices($product, $date));
$priceInfo->setPrice($this->calculatePrice($product, $date));
$priceInfo->setDate($date);
$this->applyTaxes($product, $priceInfo);
return $priceInfo;
}
/**
* @param EventProduct $product
* @param null|int $quantityScale
* @param array<mixed>|null $products
* @param Carbon|null $date
*
* @return PriceInfoInterface
*/
public function getPriceInfo(CheckoutableInterface $product, $quantityScale = null, $products = null, Carbon $date = null): PriceInfoInterface
{
$pId = $product->getId();
if (empty($date)) {
$date = $product->getEventStartDate();
}
if ($date) {
$pId .= $date->getTimestamp();
}
if (!array_key_exists($pId, $this->priceInfos)) {
$this->priceInfos[$pId] = [];
}
$quantityScaleKey = (string)$quantityScale;
if (empty($this->priceInfos[$pId][$quantityScaleKey])) {
$priceInfo = $this->initPriceInfoInstance($quantityScale, $product, $products, $date);
$this->priceInfos[$pId][$quantityScaleKey] = $priceInfo;
}
/** @phpstan-ignore-next-line */
return $this->priceInfos[$pId][$quantityScaleKey];
}
/**
* Calculate price for order item configuration
*
* @param EventProduct $product
* @param Carbon|null $date
*
* @return Price
*/
protected function calculatePrice(EventProduct $product, Carbon $date = null): Price
{
$price = 0;
if ($product->getType() == AbstractObject::OBJECT_TYPE_VARIANT) {
/** @var ShopDynamicPrice $priceObj */
$priceObj = $product->getOrderedPriceItem();
$price = $priceObj->getPrice($date);
} else {
if ($priceObjects = $product->getPriceObjects()) {
$price = $priceObjects[0]->getPrice($date);
}
}
return new Price(Decimal::fromNumeric($price), Factory::getInstance()->getEnvironment()->getDefaultCurrency(), true);
}
/**
* Get prices from shopPrice descriptions
*
* @param EventProduct $product
* @param ?Carbon $date
*
* @return Price[]
*/
public function getPrices(EventProduct $product, Carbon $date = null): array
{
$prices = [];
foreach ($product->getPrices() as $priceRelation) {
/** @var ShopDynamicPrice $dynamicPriceObject */
$dynamicPriceObject = $priceRelation->getObject();
$prices[$dynamicPriceObject->getId()] = new Price(
Decimal::fromNumeric($dynamicPriceObject->getPrice($product->getProductStartDate() ?: $date) ?: 0),
Factory::getInstance()->getEnvironment()->getDefaultCurrency(), true
);
}
return $prices;
}
/**
* @param EventProduct $product
*
* @return OnlineShopTaxClass
*/
public function getTaxClassForProduct(CheckoutableInterface $product): OnlineShopTaxClass
{
$taxRate = $this->getDefaultTaxClass();
$startDate = $product->getEventStartDate() ?? Carbon::today();
if ($startDate->year == 2024 && $taxRate->getNewTaxRate()) {
$taxRate = $taxRate->getNewTaxRate();
}
return $taxRate;
}
/**
* @param EventProduct $product
* @param \Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\PriceInfoInterface $priceInfo
*
* @return void
*
* @throws \Pimcore\Bundle\EcommerceFrameworkBundle\Exception\UnsupportedException
*/
protected function applyTaxes(CheckoutableInterface $product, \Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\PriceInfoInterface $priceInfo): void
{
$price = $priceInfo->getPrice();
$taxClass = $this->getTaxClassForProduct($product);
$totalPrice = $priceInfo->getTotalPrice();
if ($taxClass->getTaxEntryCombinationType()) {
$price->setTaxEntryCombinationMode($taxClass->getTaxEntryCombinationType());
$price->setTaxEntries(TaxEntry::convertTaxEntries($taxClass));
$totalPrice->setTaxEntryCombinationMode($taxClass->getTaxEntryCombinationType());
$totalPrice->setTaxEntries(TaxEntry::convertTaxEntries($taxClass));
}
$taxCalculationService = $this->getTaxCalculationService();
$taxCalculationService->updateTaxes($price, TaxCalculationService::CALCULATION_FROM_GROSS);
$taxCalculationService->updateTaxes($totalPrice, TaxCalculationService::CALCULATION_FROM_GROSS);
}
}