Files
2025-08-02 16:30:27 +02:00

229 lines
11 KiB
PHP

<?php
namespace KupShop\OrderDiscountBundle\Util;
use DecimalConstants;
use KupShop\I18nBundle\Util\PriceConverter;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\VatContext;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\Price\Price;
use KupShop\KupShopBundle\Util\Price\PriceCalculator;
use KupShop\KupShopBundle\Util\Price\TotalPrice;
use KupShop\OrderingBundle\Entity\Purchase\ProductPurchaseItem;
class DiscountUtil
{
private $currencyContext;
private $priceConverter;
public function __construct(CurrencyContext $currencyContext, PriceConverter $priceConverter)
{
$this->currencyContext = $currencyContext;
$this->priceConverter = $priceConverter;
}
public function createDiscountPrice(\Decimal $price, $vatId = null, $forceVatValue = null): Price
{
$vatContext = Contexts::get(VatContext::class);
if ($vatContext->isCountryOssActive()) {
// use default vat of a country
$vatId = null;
}
$vat = $forceVatValue ?? getVat($vatId);
// $discountPrice = roundPrice($price->mul(DecimalConstants::negativeOne()), null, null, null, $this->currencyContext->getActive());
$discountPrice = $price->mul(\DecimalConstants::negativeOne());
$discountPrice = $discountPrice->removeVat($vat);
return new Price($discountPrice, $this->currencyContext->getActive(), $vat);
}
public function calculateDiscountPrice(\Decimal $price, array $data, &$name = null)
{
$discountPrice = null;
$discount = $data['discount'] ?? 0;
switch ($data['unit']) {
case 'perc':
$discountPrice = $price->mul(toDecimal($discount))->div(\DecimalConstants::hundred());
$name .= ' ('.$discount.'%)';
break;
case $this->currencyContext->getActiveId():
$discountPrice = toDecimal($discount);
break;
default:
if ($currency = $this->priceConverter->getCurrency($data['unit'])) {
$name .= ' ('.$discount.' '.$currency->getSymbol().')';
$discountPrice = $this->priceConverter->convert($currency->getId(), $this->currencyContext->getActiveId(), toDecimal($discount));
}
}
return $discountPrice;
}
public function getDiscountPerPiece(ProductPurchaseItem $purchaseItem, array $data, $min_discount = null)
{
if (!$min_discount) {
$min_discount = \DecimalConstants::zero();
if (($data['unit'] == 'perc') && ('N' == ($data['combine'] ?? 'Y'))) {
// u slevy je nastaveno: NEsčítat se slevou u produktu => uplatní se pouze vyšší sleva, minimalne % z teto slevy
$min_discount = toDecimal($data['discount']);
}
}
$piecePrice = $purchaseItem->getPriceWithDiscountsWithVat()->div(toDecimal($purchaseItem->getPieces()));
if ($piecePrice->isZero()) {
return null;
}
if ($min_discount->isZero()) {
$discountPerPiece = $this->calculateDiscountPrice($piecePrice, $data);
} else {
// u slevy je nastaveno NEsčítat se slevou u produktu => uplatní se pouze vyšší sleva
$product = $purchaseItem->getProduct();
if ($product->getProductPrice()->getValue()->isZero()) {
return null;
}
$piecePriceWithoutDiscount = $product->getProductPrice()->getPriceWithoutDiscount();
$p = $purchaseItem->getPrice();
PriceCalculator::makeCompatible($p, $piecePriceWithoutDiscount);
$piecePriceWithoutDiscount = $piecePriceWithoutDiscount->getPriceWithVat();
// produkt muze mit konfigurovatelne parametry => pridat priplatky k puvodni cene
$config = $purchaseItem->getNote()['configurations'] ?? null;
if ($config && findModule(\Modules::PRODUCTS_PARAMETERS, \Modules::SUB_CONFIGURATIONS)) {
$product->fetchParameters();
foreach ($product->configurations as $id_parameter => $parameter) {
if (!empty($config[$id_parameter]) && !empty($parameter->configuration_price)) {
$values = $parameter->fetchValues();
if (array_key_exists($config[$id_parameter], $values)) {
$configurationPriceWithVat = $values[$config[$id_parameter]]->configuration_price;
$piecePriceWithoutDiscount = $piecePriceWithoutDiscount->add($configurationPriceWithVat);
}
}
}
}
// piecePriceWithoutDiscount = puvodni cena bez slev, piecePrice = cena v kosiku, se slevami
$discountPerPiecePrice = $piecePriceWithoutDiscount->sub($piecePrice);
$productDiscount = $discountPerPiecePrice->div($piecePriceWithoutDiscount)->mul(\DecimalConstants::hundred());
if ($productDiscount->isZero()) { // zatim zadna sleva na produktu
$discountPerPiece = $this->calculateDiscountPrice($piecePrice, $data);
} else {
if ($min_discount->lowerThanOrEqual($productDiscount)) {
// sleva na produktu je vetsi nez tato sleva -> uplatní se pouze sleva na produktu
return null;
}
// sleva na produktu je mensi nez tato sleva -> uplatní se tato sleva od puvodni ceny
$discountPerPiece = $this->calculateDiscountPrice($piecePriceWithoutDiscount, $data);
// snizit o castku, ktera uz byla odectena od puvodni ceny
$discountPerPiece = $discountPerPiece->sub($discountPerPiecePrice);
}
}
$piecePriceDiscounted = $piecePrice->sub($discountPerPiece, 2);
$discountPerPiece = $piecePrice->sub($piecePriceDiscounted, 2);
return $discountPerPiece;
}
/**
* @param ProductPurchaseItem[] $products
* @param string|null $name
* @param \Decimal|null $min_discount
*
* @return \Decimal
*/
public function divideDiscountPrice($products, array $data, &$name = null, $min_discount = null)
{
$discountPrice = \DecimalConstants::zero();
if ($data['unit'] == 'perc') {
$name .= ' ('.$data['discount'].'%)';
$discount_data = array_merge(
['id' => $data['id'], 'discount' => $data['discount'], 'unit' => $data['unit'], 'name' => $name],
$data['trigger_data'] ?? []
);
foreach ($products as $productPurchaseItem) {
$discountPerPiece = $this->getDiscountPerPiece($productPurchaseItem, $data, $min_discount);
if (is_null($discountPerPiece)) {
continue;
}
$discountPrice = $discountPrice->add($discountPerPiece->mul(toDecimal($productPurchaseItem->getPieces())));
$discountPerPiece = $discountPerPiece->removeVat($productPurchaseItem->getPrice()->getVat());
$discount_data['discountPrice'] = \Decimal::fromDecimal($discountPerPiece, \Decimal::$default_scale);
$productPurchaseItem->addDiscount($discount_data);
}
return $discountPrice;
}
$discountSum = \DecimalConstants::zero();
$apply_discount_on_delivery = findModule(\Modules::DELIVERY_TYPES, \Modules::SUB_APPLY_DISCOUNT_ON_DELIVERY);
if ($currency = $this->currencyContext->getSupported()[$data['unit']] ?? null) {
$discountPrice = toDecimal($data['discount']);
if ($data['unit'] != $this->currencyContext->getActiveId()) {
$discountPrice = $this->priceConverter->convert($currency->getId(), $this->currencyContext->getActiveId(), $discountPrice);
$discountPrice = roundPrice($discountPrice);
}
$totalPrice = $this->getProductsTotalPriceWithDiscounts($products)->getPriceWithVat();
$discountPrice = $apply_discount_on_delivery ? $discountPrice : min($discountPrice, $totalPrice);
$discount_data = array_merge(
['id' => $data['id'], 'discount' => $data['discount'], 'unit' => $data['unit'], 'name' => $name],
$data['trigger_data'] ?? []
);
$last = array_key_last($products);
foreach ($products as $key => $productPurchaseItem) {
$price = $productPurchaseItem->getPriceWithDiscountsWithVat();
if ($price->isZero()) {
continue;
}
$pieces = toDecimal($productPurchaseItem->getPieces());
$piecePrice = $price->div($pieces);
if ($key === $last) {
$discount = $discountPrice->sub($discountSum);
} else {
$discount_perc = $price->div($totalPrice);
$discount = $discountPrice->mul($discount_perc);
}
if ($price->lessThan($discount)) {
$discount = $price;
}
$discountPerPiece = $discount->div($pieces, 2);
$piecePriceDiscounted = $piecePrice->sub($discountPerPiece, 2);
// $piecePriceDiscounted = roundPrice($piecePriceDiscounted, null, null, null, $productPurchaseItem->getPrice()->getCurrency());
$discountPerPiece = $piecePrice->sub($piecePriceDiscounted, 2);
$discount = $discountPerPiece->mul($pieces);
$discountPerPiece = $discountPerPiece->removeVat($productPurchaseItem->getPrice()->getVat());
$discount_perc = $discount->div($price)->mul(\DecimalConstants::hundred())->round(\Decimal::$default_scale);
$discount_data['discount'] = $discount_perc;
$discount_data['unit'] = 'perc';
$discount_data['discountPrice'] = \Decimal::fromDecimal($discountPerPiece, \Decimal::$default_scale);
$productPurchaseItem->addDiscount($discount_data);
$discountSum = $discountSum->add($discount);
}
if ($discountPrice->lowerThan($discountSum)) {
$discountSum = $discountPrice;
}
}
return $apply_discount_on_delivery ? $discountPrice : $discountSum;
}
/**
* @param ProductPurchaseItem[] $items
*/
public function getProductsTotalPriceWithDiscounts($items): TotalPrice
{
$totalPriceWithVat = \DecimalConstants::zero();
$totalPriceWithoutVat = \DecimalConstants::zero();
foreach ($items as $item) {
$priceWithVat = $item->getPriceWithDiscountsWithVat();
$priceWithoutVat = $priceWithVat->removeVat($item->getPrice()->getVat());
$totalPriceWithVat = $totalPriceWithVat->add($priceWithVat);
$totalPriceWithoutVat = $totalPriceWithoutVat->add($priceWithoutVat);
}
return new TotalPrice($totalPriceWithVat, $totalPriceWithoutVat, $this->currencyContext->getActive());
}
}