229 lines
11 KiB
PHP
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());
|
|
}
|
|
}
|