<?php declare(strict_types=1);
namespace Acris\DiscountGroup\Components;
use Acris\DiscountGroup\Components\Struct\LineItemDiscountStruct;
use Acris\DiscountGroup\Components\Struct\ScalePriceCollection;
use Acris\DiscountGroup\Components\Struct\ScalePriceStruct;
use Acris\DiscountGroup\Custom\DiscountGroupCollection;
use Acris\DiscountGroup\Custom\DiscountGroupDefinition;
use Acris\DiscountGroup\Custom\DiscountGroupEntity;
use Shopware\Core\Checkout\Cart\Price\AbsolutePriceCalculator;
use Shopware\Core\Checkout\Cart\Price\CashRounding;
use Shopware\Core\Checkout\Cart\Price\PercentagePriceCalculator;
use Shopware\Core\Checkout\Cart\Price\QuantityPriceCalculator;
use Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice;
use Shopware\Core\Checkout\Cart\Price\Struct\CartPrice;
use Shopware\Core\Checkout\Cart\Price\Struct\ListPrice;
use Shopware\Core\Checkout\Cart\Price\Struct\PriceCollection;
use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition;
use Shopware\Core\Checkout\Cart\Price\Struct\ReferencePrice;
use Shopware\Core\Checkout\Cart\Price\Struct\ReferencePriceDefinition;
use Shopware\Core\Checkout\Cart\Price\Struct\RegulationPrice;
use Shopware\Core\Checkout\Cart\Tax\TaxDetector;
use Shopware\Core\Checkout\Promotion\Exception\DiscountCalculatorNotFoundException;
use Shopware\Core\Content\Product\DataAbstractionLayer\CheapestPrice\CalculatedCheapestPrice;
use Shopware\Core\Content\Product\SalesChannel\Price\ReferencePriceDto;
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
use Shopware\Core\Framework\DataAbstractionLayer\Entity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Pricing\CashRoundingConfig;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
use Shopware\Core\Framework\Struct\ArrayEntity;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\Unit\UnitCollection;
class DiscountGroupService
{
public const ACRIS_STREAM_IDS_EXTENSION = 'acrisStreamIds';
const ACRIS_DISCOUNT_GROUP_LINE_ITEM_DISCOUNT = 'acrisDiscountGroupLineItemDiscount';
const ACRIS_RRP_PRICE_EXTENSION_KEY = 'acris_rrp_price_price_struct';
private DiscountGroupGateway $discountGroupGateway;
private AbsolutePriceCalculator $absolutePriceCalculator;
private PercentagePriceCalculator $percentagePriceCalculator;
private EntityRepositoryInterface $unitRepository;
private QuantityPriceCalculator $calculator;
private ?UnitCollection $units = null;
private CashRounding $priceRounding;
private TaxDetector $taxDetector;
public function __construct(
DiscountGroupGateway $discountGroupGateway,
AbsolutePriceCalculator $absolutePriceCalculator,
PercentagePriceCalculator $percentagePriceCalculator,
EntityRepositoryInterface $unitRepository,
QuantityPriceCalculator $calculator,
CashRounding $priceRounding,
TaxDetector $taxDetector
) {
$this->discountGroupGateway = $discountGroupGateway;
$this->absolutePriceCalculator = $absolutePriceCalculator;
$this->percentagePriceCalculator = $percentagePriceCalculator;
$this->unitRepository = $unitRepository;
$this->calculator = $calculator;
$this->priceRounding = $priceRounding;
$this->taxDetector = $taxDetector;
}
public function calculateProductPrices(iterable $products, SalesChannelContext $salesChannelContext): void
{
/** @var SalesChannelProductEntity $product */
foreach ($products as $product) {
if ($product->hasExtension(self::ACRIS_STREAM_IDS_EXTENSION) && !empty($product->getExtension(self::ACRIS_STREAM_IDS_EXTENSION))) {
$productStreamIds = $product->getExtension(self::ACRIS_STREAM_IDS_EXTENSION)->get('ids');
} else {
$productStreamIds = $this->discountGroupGateway->getProductStreamIds([$product->getId()], $salesChannelContext->getContext());
$product->addExtension(self::ACRIS_STREAM_IDS_EXTENSION, new ArrayEntity(['ids' => $productStreamIds]));
}
$discountGroupResult = $this->discountGroupGateway->getAllDiscountGroups($salesChannelContext, $product->getId(), $productStreamIds);
if ($discountGroupResult->count() === 0) {
continue;
}
$this->enrichDiscountGroups($product, $discountGroupResult, $salesChannelContext);
$this->calculateProductPricesByDiscountGroups($product, $discountGroupResult, $salesChannelContext);
}
}
public function reset(): void
{
$this->units = null;
}
private function enrichDiscountGroups(SalesChannelProductEntity $product, EntitySearchResult $discountGroupResult, SalesChannelContext $salesChannelContext)
{
/** @var DiscountGroupEntity $discountGroup */
foreach ($discountGroupResult->getElements() as $discountGroup) {
$discountGroup->setProductIds(array_unique(array_merge(!empty($discountGroup->getProductIds()) ? $discountGroup->getProductIds() : [], [$product->getId()])));
}
}
private function calculateProductPricesByDiscountGroups(SalesChannelProductEntity $product, EntitySearchResult $discountGroupResult, SalesChannelContext $salesChannelContext): void
{
$productId = $product->getId();
// product assignment
$discountGroupCollection = $discountGroupResult->getEntities()->filter(function (DiscountGroupEntity $discountGroupEntity) use ($productId, $product) {
return ($discountGroupEntity->getProductAssignmentType() === DiscountGroupDefinition::PRODUCT_ASSIGNMENT_TYPE_PRODUCT && !empty($discountGroupEntity->getProductId()) && $productId === $discountGroupEntity->getProductId())
|| (!empty($discountGroupEntity->getProductIds()) && $discountGroupEntity->getProductAssignmentType() !== DiscountGroupDefinition::PRODUCT_ASSIGNMENT_TYPE_MATERIAL_GROUP && in_array($productId, $discountGroupEntity->getProductIds()))
|| ($discountGroupEntity->getProductAssignmentType() === DiscountGroupDefinition::PRODUCT_ASSIGNMENT_TYPE_MATERIAL_GROUP
&& !empty($discountGroupEntity->getMaterialGroup()) && !empty($product->getTranslated()) && array_key_exists('customFields', $product->getTranslated())
&& !empty($product->getTranslated()['customFields']) && (array_key_exists('acris_discount_group_value', $product->getTranslated()['customFields'])
&& !empty($product->getTranslated()['customFields']['acris_discount_group_value'])
&& $product->getTranslated()['customFields']['acris_discount_group_value'] === $discountGroupEntity->getMaterialGroup())
|| array_key_exists('acris_discount_group_product_value', $product->getTranslated()['customFields'])
&& !empty($product->getTranslated()['customFields']['acris_discount_group_product_value'])
&& $product->getTranslated()['customFields']['acris_discount_group_product_value'] === $discountGroupEntity->getMaterialGroup());
});
if($discountGroupCollection->count() === 0) {
return;
}
$this->sortDiscounts($discountGroupCollection);
$this->addLineItemDiscountData( $product, $discountGroupCollection );
$this->calculatePrices($product, $discountGroupCollection, $salesChannelContext);
}
private function calculatePrices(SalesChannelProductEntity $product, EntityCollection $discountGroupCollection, SalesChannelContext $salesChannelContext)
{
$this->buildCalculatedPrices($product, $discountGroupCollection, $salesChannelContext);
/** @var DiscountGroupEntity $discountGroupEntity */
foreach ($discountGroupCollection->getElements() as $discountGroupEntity) {
if($discountGroupEntity->getDiscount() <= 0) continue;
if(empty($discountGroupEntity->getMinQuantity())) $discountGroupEntity->setMinQuantity(1);
$product->setCalculatedPrices($this->calculatePriceCollection($product->getCalculatedPrices(), $discountGroupEntity, $salesChannelContext));
$product->setCalculatedPrice($this->calculatePrice($product->getCalculatedPrice(), $discountGroupEntity, $salesChannelContext));
$cheapestPriceNew = CalculatedCheapestPrice::createFrom($this->calculatePrice($product->getCalculatedCheapestPrice(), $discountGroupEntity, $salesChannelContext));
$cheapestPriceNew->setHasRange($product->getCalculatedCheapestPrice()->hasRange());
$product->setCalculatedCheapestPrice($cheapestPriceNew);
}
}
private function calculatePriceCollection(PriceCollection $calculatedPrices, DiscountGroupEntity $discountGroupEntity, SalesChannelContext $salesChannelContext): PriceCollection
{
if($calculatedPrices->count() === 0) return $calculatedPrices;
$calculatedPricesNew = new PriceCollection();
$lastQuantity = $calculatedPrices->last()->getQuantity();
foreach ($calculatedPrices as $calculatedPrice) {
if(!$calculatedPrice instanceof CalculatedPrice) {
return $calculatedPrices;
}
$lastQuantity === $calculatedPrice->getQuantity() ? $isLast = true : $isLast = false;
$calculatedPricesNew->add($this->calculatePrice($calculatedPrice, $discountGroupEntity, $salesChannelContext, $isLast));
}
return $calculatedPricesNew;
}
private function calculatePrice(CalculatedPrice $calculatedPrice, DiscountGroupEntity $discountGroupEntity, SalesChannelContext $salesChannelContext, $isLast = true): CalculatedPrice
{
// before we built the prices, so this check is the only check which is needed
if(($isLast === true && ($discountGroupEntity->getMinQuantity() >= $calculatedPrice->getQuantity() || $discountGroupEntity->getMaxQuantity() === null))
|| ($isLast === false && ($discountGroupEntity->getMinQuantity() <= $calculatedPrice->getQuantity() && ($discountGroupEntity->getMaxQuantity() === null || $discountGroupEntity->getMaxQuantity() >= $calculatedPrice->getQuantity())))) {
// continue here
} else {
return $calculatedPrice;
}
$discount = $this->getPositive($discountGroupEntity->getDiscount());
if($discountGroupEntity->getDiscountType() === DiscountGroupDefinition::DISCOUNT_TYPE_ABSOLUTE) {
// prevent product prices smaller then 0
if($discount > $calculatedPrice->getUnitPrice()) {
$discount = $calculatedPrice->getUnitPrice();
}
$this->setCalculationBase($calculatedPrice, $discountGroupEntity, $salesChannelContext);
$calculatedDiscount = $this->absolutePriceCalculator->calculate($this->getSurchargeOrDiscount($discountGroupEntity, $discount), new PriceCollection([$calculatedPrice]), $salesChannelContext);
} elseif($discountGroupEntity->getDiscountType() === DiscountGroupDefinition::DISCOUNT_TYPE_PERCENTAGE) {
// prevent percentage bigger then 100
if($discount > 100) {
$discount = 100;
}
$this->setCalculationBase($calculatedPrice, $discountGroupEntity, $salesChannelContext);
$newCalculatedPrice = new CalculatedPrice(
$calculatedPrice->getUnitPrice(),
$calculatedPrice->getUnitPrice(),
$calculatedPrice->getCalculatedTaxes(),
$calculatedPrice->getTaxRules(),
1,
$calculatedPrice->getReferencePrice(),
$calculatedPrice->getListPrice(),
$calculatedPrice->getRegulationPrice()
);
$extensions = $calculatedPrice->getExtensions();
$newCalculatedPrice->setExtensions($extensions);
$calculatedDiscount = $this->percentagePriceCalculator->calculate($this->getSurchargeOrDiscount($discountGroupEntity, $discount), new PriceCollection([$newCalculatedPrice]), $salesChannelContext);
} else {
throw new DiscountCalculatorNotFoundException($discountGroupEntity->getDiscountType());
}
$calculatedPriceNew = (new PriceCollection([$calculatedPrice, $calculatedDiscount]))->sum();
$listPriceNew = null;
if ($calculatedPriceNew->getUnitPrice() < 0) {
$calculatedPriceZero = new CalculatedPrice(
0,
$calculatedPriceNew->getUnitPrice(),
$calculatedPriceNew->getCalculatedTaxes(),
$calculatedPriceNew->getTaxRules(),
$calculatedPriceNew->getQuantity(),
$calculatedPriceNew->getReferencePrice(),
$calculatedPriceNew->getListPrice(),
$calculatedPriceNew->getRegulationPrice()
);
$calculatedPriceNew = $calculatedPriceZero;
}
if ($calculatedPriceNew->getUnitPrice() > 0) {
switch ($discountGroupEntity->getListPriceType()) {
case DiscountGroupDefinition::LIST_PRICE_TYPE_IGNORE:
if($calculatedPrice->getListPrice() && $calculatedPrice->getListPrice()->getPrice()) {
$listPriceNew = ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $calculatedPrice->getListPrice()->getPrice());
} else {
$listPriceNew = null;
}
break;
case DiscountGroupDefinition::LIST_PRICE_TYPE_RRP:
$tax = $this->taxDetector->getTaxState($salesChannelContext);
$rrp = 0;
if($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_GROSS
|| ($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_AUTO && $tax === CartPrice::TAX_STATE_GROSS)) {
$rrp = $this->getRrp($calculatedPrice, CartPrice::TAX_STATE_GROSS);
} elseif ($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_NET
|| ($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_AUTO && $tax !== CartPrice::TAX_STATE_GROSS)) {
$rrp = $this->getRrp($calculatedPrice, CartPrice::TAX_STATE_NET);
}
if($rrp > 0) {
$listPriceNew = ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $rrp);
} else {
$listPriceNew = null;
}
break;
case DiscountGroupDefinition::LIST_PRICE_TYPE_SET:
if($calculatedPrice->getListPrice() && $calculatedPrice->getListPrice()->getPrice()) {
$listPriceNew = ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $calculatedPrice->getListPrice()->getPrice());
} else {
$listPriceNew = ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $calculatedPrice->getUnitPrice());
}
break;
case DiscountGroupDefinition::LIST_PRICE_TYPE_SET_RRP:
$tax = $this->taxDetector->getTaxState($salesChannelContext);
$rrp = 0;
if($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_GROSS
|| ($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_AUTO && $tax === CartPrice::TAX_STATE_GROSS)) {
$rrp = $this->getRrp($calculatedPrice, CartPrice::TAX_STATE_GROSS);
} elseif ($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_NET
|| ($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_AUTO && $tax !== CartPrice::TAX_STATE_GROSS)) {
$rrp = $this->getRrp($calculatedPrice, CartPrice::TAX_STATE_NET);
}
if($rrp > 0) {
$listPriceNew = ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $rrp);
} else {
$listPriceNew = ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $calculatedPrice->getUnitPrice());
}
break;
case DiscountGroupDefinition::LIST_PRICE_TYPE_SET_PRICE:
$listPriceNew = ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $calculatedPrice->getUnitPrice());
break;
case DiscountGroupDefinition::LIST_PRICE_TYPE_REMOVE:
$listPriceNew = null;
break;
default:
$listPriceNew = null;
}
}
return new CalculatedPrice(
$calculatedPriceNew->getUnitPrice(),
$calculatedPriceNew->getTotalPrice(),
$calculatedPriceNew->getCalculatedTaxes(),
$calculatedPriceNew->getTaxRules(),
$calculatedPrice->getQuantity(),
$this->calculateReferencePriceByReferencePrice($calculatedPriceNew->getUnitPrice(), $calculatedPrice->getReferencePrice(), $salesChannelContext->getItemRounding()),
$listPriceNew,
$calculatedPrice->getRegulationPrice()
);
}
private function getSurchargeOrDiscount(DiscountGroupEntity $discountGroupEntity, float $discount): float
{
if($discountGroupEntity->getCalculationType() === DiscountGroupDefinition::CALCULATION_TYPE_SURCHARGE) {
return $this->getPositive($discount);
} else {
return $this->getNegative($discount);
}
}
private function getNegative(float $value): float
{
return -1 * abs($value);
}
private function getPositive(float $value): float
{
return abs($value);
}
private function sortDiscounts(DiscountGroupCollection $discountGroupCollection): void
{
$priority = null;
$exclude = false;
foreach ($discountGroupCollection->getElements() as $discountGroupEntity) {
if ($exclude && !empty($priority) && $discountGroupEntity->getPriority() < $priority) {
$discountGroupCollection->remove($discountGroupEntity->getId());
}
if ($discountGroupEntity->getExcluded()) {
$exclude = true;
}
$priority = $discountGroupEntity->getPriority();
}
}
private function addLineItemDiscountData(SalesChannelProductEntity $product, DiscountGroupCollection $discountGroupCollection)
{
/** @var LineItemDiscountStruct $lineItemDiscount */
$lineItemDiscount = new LineItemDiscountStruct( $product->getCalculatedPrice()->getUnitPrice(), $discountGroupCollection);
$product->addExtension( self::ACRIS_DISCOUNT_GROUP_LINE_ITEM_DISCOUNT, $lineItemDiscount );
}
private function buildCalculatedPrices(SalesChannelProductEntity $product, DiscountGroupCollection $discountGroupCollection, SalesChannelContext $salesChannelContext): void
{
if($this->isScalePriceRebuildNeeded($discountGroupCollection) === false) {
return;
}
// if product has no calculated prices, we have to set
if(empty($product->getCalculatedPrices()) || $product->getCalculatedPrices()->count() === 0) {
$product->setCalculatedPrices(new PriceCollection([$product->getCalculatedPrice()]));
}
// build price struct
$scalePriceCollection = new ScalePriceCollection();
$quantityLast = $product->getCalculatedPrices()->last()->getQuantity();
$quantityFromBefore = 1;
foreach ($product->getCalculatedPrices() as $calculatedPrice) {
if($calculatedPrice->getQuantity() === $quantityLast) {
$scalePriceCollection->add(new ScalePriceStruct($calculatedPrice->getQuantity(), null, $calculatedPrice));
} else {
$scalePriceCollection->add(new ScalePriceStruct($quantityFromBefore, $calculatedPrice->getQuantity(), $calculatedPrice));
$quantityFromBefore = $calculatedPrice->getQuantity() + 1;
}
}
// now we add our discounts
// first we add minimum
foreach ($discountGroupCollection->getElements() as $discountGroup) {
$minValue = $discountGroup->getMinQuantity();
if(empty($minValue) === true) {
$minValue = 1;
}
/** @var ScalePriceStruct $scalePrice */
foreach ($scalePriceCollection->getElements() as $scalePrice) {
if($minValue === $scalePrice->getFrom()) {
continue 2;
}
}
/** @var ScalePriceStruct $scalePrice */
foreach ($scalePriceCollection->getElements() as $scalePriceKey => $scalePrice) {
if($minValue > $scalePrice->getFrom()
&& ($scalePrice->getTo() === null || $minValue <= $scalePrice->getTo())) {
$scalePriceCollection->add(new ScalePriceStruct($scalePrice->getFrom(), $minValue - 1, $scalePrice->getCalculatedPrice()));
$scalePriceCollection->add(new ScalePriceStruct($minValue, $scalePrice->getTo(), $scalePrice->getCalculatedPrice()));
$scalePriceCollection->remove($scalePriceKey);
}
}
}
// now we add our discounts
// afterwards we add maximum
foreach ($discountGroupCollection->getElements() as $discountGroup) {
$maxValue = $discountGroup->getMaxQuantity();
/** @var ScalePriceStruct $scalePrice */
foreach ($scalePriceCollection->getElements() as $scalePrice) {
if($maxValue === $scalePrice->getTo()) {
continue 2;
}
}
/** @var ScalePriceStruct $scalePrice */
foreach ($scalePriceCollection->getElements() as $scalePriceKey => $scalePrice) {
if($maxValue >= $scalePrice->getFrom()
&& ($maxValue < $scalePrice->getTo() || $scalePrice->getTo() === null)) {
$scalePriceCollection->add(new ScalePriceStruct($scalePrice->getFrom(), $maxValue, $scalePrice->getCalculatedPrice()));
$scalePriceCollection->add(new ScalePriceStruct($maxValue + 1, $scalePrice->getTo(), $scalePrice->getCalculatedPrice()));
$scalePriceCollection->remove($scalePriceKey);
}
}
}
// sort scale prices quantity asc
$scalePriceCollection->sort(function (ScalePriceStruct $a, ScalePriceStruct $b) {
return $a->getFrom() <=> $b->getFrom();
});
// at the end we build back the calculated prices
$priceCollection = new PriceCollection();
$quantityLast = $scalePriceCollection->last()->getFrom();
/** @var ScalePriceStruct $scalePrice */
foreach ($scalePriceCollection as $scalePrice) {
$calculatedPrice = $scalePrice->getCalculatedPrice();
if($quantityLast === $scalePrice->getFrom()) {
$quantity = $scalePrice->getFrom();
} else {
$quantity = $scalePrice->getTo();
}
if($quantity === $calculatedPrice->getQuantity()) {
$priceCollection->add($calculatedPrice);
} else {
$units = $this->getUnits($salesChannelContext);
$reference = ReferencePriceDto::createFromProduct($product);
$calculatedPrice->getListPrice() instanceof ListPrice ? $listPrice = $calculatedPrice->getListPrice()->getPrice() : $listPrice = null;
$calculatedPrice->getRegulationPrice() instanceof RegulationPrice ? $regulationPrice = $calculatedPrice->getRegulationPrice()->getPrice() : $regulationPrice = null;
$definition = $this->buildDefinition($product, $calculatedPrice->getUnitPrice(), $salesChannelContext, $units, $reference, $quantity, $listPrice, $regulationPrice);
$priceCollection->add($this->calculator->calculate($definition, $salesChannelContext));
}
}
$product->setCalculatedPrices($priceCollection);
}
private function isScalePriceRebuildNeeded(DiscountGroupCollection $discountGroupCollection): bool
{
/** @var DiscountGroupEntity $discountGroup */
foreach ($discountGroupCollection->getElements() as $discountGroup) {
if($discountGroup->getMinQuantity() > 1 || $discountGroup->getMaxQuantity() !== null) {
return true;
}
}
return false;
}
/*
* Copied and adapted from ProductPriceCalculator
* */
private function buildDefinition(
Entity $product,
float $price,
SalesChannelContext $context,
UnitCollection $units,
ReferencePriceDto $reference,
int $quantity = 1,
?float $listPrice = null,
?float $regulationPrice = null
): QuantityPriceDefinition {
$taxId = $product->get('taxId');
$definition = new QuantityPriceDefinition($price, $context->buildTaxRules($taxId), $quantity);
$definition->setReferencePriceDefinition(
$this->buildReferencePriceDefinition($reference, $units)
);
$definition->setListPrice($listPrice);
$definition->setRegulationPrice($regulationPrice);
return $definition;
}
private function buildReferencePriceDefinition(ReferencePriceDto $definition, UnitCollection $units): ?ReferencePriceDefinition
{
if ($definition->getPurchase() === null || $definition->getPurchase() <= 0) {
return null;
}
if ($definition->getUnitId() === null) {
return null;
}
if ($definition->getReference() === null || $definition->getReference() <= 0) {
return null;
}
if ($definition->getPurchase() === $definition->getReference()) {
return null;
}
$unit = $units->get($definition->getUnitId());
if ($unit === null) {
return null;
}
return new ReferencePriceDefinition(
$definition->getPurchase(),
$definition->getReference(),
$unit->getTranslation('name')
);
}
private function getUnits(SalesChannelContext $context): UnitCollection
{
if ($this->units !== null) {
return $this->units;
}
/** @var UnitCollection $units */
$units = $this->unitRepository
->search(new Criteria(), $context->getContext())
->getEntities();
return $this->units = $units;
}
/**
* Copied and adapted from GrossPriceCalculator and NetPriceCalculator
*/
private function calculateReferencePriceByReferencePrice(float $price, ?ReferencePrice $referencePrice, CashRoundingConfig $config): ?ReferencePrice
{
if (!$referencePrice instanceof ReferencePrice) {
return $referencePrice;
}
if ($referencePrice->getPurchaseUnit() <= 0 || $referencePrice->getReferenceUnit() <= 0) {
return null;
}
$price = $price / $referencePrice->getPurchaseUnit() * $referencePrice->getReferenceUnit();
$price = $this->priceRounding->mathRound($price, $config);
return new ReferencePrice(
$price,
$referencePrice->getPurchaseUnit(),
$referencePrice->getReferenceUnit(),
$referencePrice->getUnitName()
);
}
private function setCalculationBase(CalculatedPrice $calculatedPrice, DiscountGroupEntity $discountGroupEntity, SalesChannelContext $salesChannelContext)
{
$newUnitPrice = null;
if($discountGroupEntity->getCalculationBase() === DiscountGroupDefinition::CALCULATION_BASE_LIST_PRICE) {
if($calculatedPrice->getListPrice() instanceof ListPrice && $calculatedPrice->getListPrice()->getPrice() > 0) {
$newUnitPrice = $calculatedPrice->getListPrice()->getPrice();
}
} elseif($discountGroupEntity->getCalculationBase() === DiscountGroupDefinition::CALCULATION_BASE_RRP) {
$tax = $this->taxDetector->getTaxState($salesChannelContext);
$rrp = 0;
if($discountGroupEntity->getRrpTax() === DiscountGroupDefinition::RRP_TAX_GROSS
|| ($discountGroupEntity->getRrpTax() === DiscountGroupDefinition::RRP_TAX_AUTO && $tax === CartPrice::TAX_STATE_GROSS)) {
$rrp = $this->getRrp($calculatedPrice, CartPrice::TAX_STATE_GROSS);
} elseif ($discountGroupEntity->getRrpTax() === DiscountGroupDefinition::RRP_TAX_NET
|| ($discountGroupEntity->getRrpTax() === DiscountGroupDefinition::RRP_TAX_AUTO && $tax !== CartPrice::TAX_STATE_GROSS)) {
$rrp = $this->getRrp($calculatedPrice, CartPrice::TAX_STATE_NET);
}
if($rrp > 0) {
$newUnitPrice = $rrp;
}
}
if($newUnitPrice !== null) {
$calculatedPrice->assign(['unitPrice' => $newUnitPrice]);
$calculatedPrice->assign(['totalPrice' => $newUnitPrice * $calculatedPrice->getQuantity()]);
}
}
private function getRrp(CalculatedPrice $calculatedPrice, string $tax = CartPrice::TAX_STATE_GROSS): float
{
$rrp = 0;
if($calculatedPrice->hasExtension(self::ACRIS_RRP_PRICE_EXTENSION_KEY) && $calculatedPrice->getExtension(self::ACRIS_RRP_PRICE_EXTENSION_KEY)->getRrpPricePrice()) {
$rrpPrice = $calculatedPrice->getExtension(self::ACRIS_RRP_PRICE_EXTENSION_KEY)->getRrpPricePrice();
if($tax === CartPrice::TAX_STATE_GROSS && $rrpPrice->getGross() > 0) {
$rrp = $rrpPrice->getGross();
} elseif($tax !== CartPrice::TAX_STATE_GROSS && $rrpPrice->getNet() > 0) {
$rrp = $rrpPrice->getNet();
}
}
return (float) $rrp;
}
}