<?php
namespace App\Ecommerce\PriceSystem;
use App\Model\DataObject\Customer;
use Carbon\Carbon;
use Pimcore\Bundle\EcommerceFrameworkBundle\CartManager\CartPriceModificator\CartPriceModificatorInterface;
use Pimcore\Bundle\EcommerceFrameworkBundle\Model\CheckoutableInterface;
use Pimcore\Bundle\EcommerceFrameworkBundle\Model\Currency;
use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\PriceInfoInterface;
use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\PriceSystemInterface;
use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\TaxManagement\TaxCalculationService;
use Pimcore\Bundle\EcommerceFrameworkBundle\PriceSystem\TaxManagement\TaxEntry;
use Pimcore\Bundle\EcommerceFrameworkBundle\Type\Decimal;
use Pimcore\Db;
use Pimcore\Model\DataObject\OnlineShopTaxClass;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Security;
class PapyrusPriceSystem implements PriceSystemInterface
{
protected ContainerInterface $container;
protected ?Customer $customer;
protected array $customerPrices = [];
protected array $customerDiscounts = [];
public function __construct(Security $security, ContainerInterface $container)
{
$this->container = $container;
$this->customer = $security->getUser();
if( $this->customer ){
$customerPrices = [];
$customerDiscounts = [];
// Get ObjectMetadata for Customer
// Query becasuse fetching with Pimcore gives too many queries
$customPrices = Db::get()
->fetchAllAssociative('SELECT * FROM object_metadata_7 WHERE o_id = ? AND fieldname = ?'
, [
$this->customer->getId(),
'customerPrices',
]
);
foreach( $customPrices as $customPrice ){
if( $customPrice['column'] === 'price' ){
$customerPrices[$customPrice['dest_id']] = (float)$customPrice['data'];
}
if( $customPrice['column'] === 'discountPercentage' ){
$customerDiscounts[$customPrice['dest_id']] = (float)$customPrice['data'];
}
}
$this->customerPrices = $customerPrices;
$this->customerDiscounts = $customerDiscounts;
}
}
public function getPriceInfo(CheckoutableInterface $product, $quantityScale = null , $products = null): PriceInfoInterface
{
$currency = new Currency('EUR');
if( !$this->customer || $this->customer->getShowPrices() === false ){
$price = new Price(Decimal::create(0), $currency);
return new PapyrusPriceInfo($price, $quantityScale, $price, $price);
}
// Client specific price
[$clientDefaultPrice, $clientPrice, $clientDiscount] = $this->getCustomerPrice($product);
// Default Price
[$defaultPrice, $price, $discount] = $this->getDefaultPrice($product);
// If there is a client Price, always use the client price
if( $clientPrice ){
$defaultPrice = $clientDefaultPrice;
$price = $clientPrice;
$discount = $clientDiscount;
}
$totalPrice = Decimal::create($price->mul($quantityScale));
$totalPrice = new Price($totalPrice, $currency);
$price = new Price($price, $currency);
$defaultPrice = new Price($defaultPrice, $currency);
$taxClass = $this->getDefaultTaxClass();
if( $this->customerHasToPayTax($this->customer) ){
$taxClass = $this->getTaxClassForProduct($product);
}
$price->setTaxEntries(TaxEntry::convertTaxEntries($taxClass));
$defaultPrice->setTaxEntries(TaxEntry::convertTaxEntries($taxClass));
$totalPrice->setTaxEntries(TaxEntry::convertTaxEntries($taxClass));
$taxCalculationService = new TaxCalculationService();
$taxCalculationService->updateTaxes($price);
$taxCalculationService->updateTaxes($defaultPrice);
$taxCalculationService->updateTaxes($totalPrice);
return new PapyrusPriceInfo($price, $quantityScale, $totalPrice, $defaultPrice, $discount);
}
private function getCustomerPrice($product): array{
if( !$this->customer ){
return [null,null,0];
}
$customerPrice = $this->customerPrices[$product->getId()] ?? 0;
$customerDiscount = $this->customerDiscounts[$product->getId()] ?? 0;
if( !$customerPrice && !$customerDiscount ){
return [null,null,0];
}
$price = $customerPrice;
$discountPercentage = $customerDiscount;
return [
Decimal::create($price),
Decimal::create($price * (1 - ($discountPercentage / 100))),
$discountPercentage
];
}
private function getDefaultPrice($product): array
{
$priceCode = $this->customer->getPriceCode() ?: 1;
$functionName = 'getPrice'.$priceCode;
$price = call_user_func( array( $product, $functionName ) );
$price = Decimal::create($price);
$defaultPrice = $price;
$discount = 0;
[$promoPrice, $promoDiscount] = $this->getPapyrusPromoPrice($defaultPrice, $product);
if( $promoPrice && $promoPrice->lessThan($price) ){
$price = $promoPrice;
$discount = $promoDiscount;
}
// Verreken klantkorting
$customerDiscount = $this->customer->getDiscount();
if( $customerDiscount && $product->getAllowCustomerDiscount() )
{
$price = $price->asNumeric() * (1 - ($customerDiscount / 100));
$price = Decimal::create($price);
// When user has general discount
// Show promo price if available as price where general discount is applied
if( $promoPrice && !$discount ){
$defaultPrice = $promoPrice;
}
// Calculate total discount percentage from $discount en $customerDiscount
$discount = 100 - ((100 - $discount) * (100 - $customerDiscount) / 100);
}
return [$defaultPrice, $price, $discount];
}
private function getPapyrusPromoPrice($price, $product): array
{
$promoPercentage = $product->getPromoPercentage();
$promoPrice = $product->getPromoPrice();
$promoStart = $product->getPromoDate1()?->startOfDay();
$promoEnd = $product->getPromoDate2()?->endOfDay();
if( !$promoPercentage && !$promoPrice ){
return [0,0];
}
$now = new Carbon();
if( !$now->isBetween($promoStart, $promoEnd) ){
return [0,0];
}
$newPrice = $price;
// Promo Price
if( $promoPrice > 0 ){
$promoPrice = Decimal::create($promoPrice);
if( $promoPrice->lessThan($price) ) {
$newPrice = $promoPrice;
}
// Return, combination of price en percentage is nog possible
return [$newPrice,0];
}
$discount = 0;
$promoPercentagePrice = $price->asNumeric() * (1 - ($promoPercentage / 100));
$promoPercentagePrice = Decimal::create($promoPercentagePrice);
if ( $promoPercentagePrice->lessThan($price) ){
$newPrice = $promoPercentagePrice;
$discount = $promoPercentage;
}
return [$newPrice, $discount];
}
public function filterProductIds($productIds, $fromPrice, $toPrice, $order, $offset, $limit)
{
// TODO: Implement filterProductIds() method.
}
private function customerHasToPayTax(Customer $customer): bool
{
$vatNumber = $customer->getVAT();
if( $vatNumber && !str_starts_with($vatNumber, 'BE') ){
return false;
}
return true;
}
private function getDefaultTaxClass(): OnlineShopTaxClass
{
$defaultTaxClass = new OnlineShopTaxClass();
$defaultTaxClass->setTaxEntryCombinationType(TaxEntry::CALCULATION_MODE_COMBINE);
return $defaultTaxClass;
}
public function getTaxClassForProduct(CheckoutableInterface $product)
{
$defaultTaxClass = $this->getDefaultTaxClass();
return match((int)$product->getVatcode()){
1 => OnlineShopTaxClass::getById($this->container->getParameter('tax_6')),
2 => OnlineShopTaxClass::getById($this->container->getParameter('tax_12')),
3 => OnlineShopTaxClass::getById($this->container->getParameter('tax_21')),
default => $defaultTaxClass,
};
}
public function getTaxClassForPriceModification(CartPriceModificatorInterface $modificator)
{
}
}