custom/plugins/AcrisDiscountGroupCS/src/Components/DiscountGroupService.php line 77

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Acris\DiscountGroup\Components;
  3. use Acris\DiscountGroup\Components\Struct\LineItemDiscountStruct;
  4. use Acris\DiscountGroup\Components\Struct\ScalePriceCollection;
  5. use Acris\DiscountGroup\Components\Struct\ScalePriceStruct;
  6. use Acris\DiscountGroup\Custom\DiscountGroupCollection;
  7. use Acris\DiscountGroup\Custom\DiscountGroupDefinition;
  8. use Acris\DiscountGroup\Custom\DiscountGroupEntity;
  9. use Shopware\Core\Checkout\Cart\Price\AbsolutePriceCalculator;
  10. use Shopware\Core\Checkout\Cart\Price\CashRounding;
  11. use Shopware\Core\Checkout\Cart\Price\PercentagePriceCalculator;
  12. use Shopware\Core\Checkout\Cart\Price\QuantityPriceCalculator;
  13. use Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice;
  14. use Shopware\Core\Checkout\Cart\Price\Struct\CartPrice;
  15. use Shopware\Core\Checkout\Cart\Price\Struct\ListPrice;
  16. use Shopware\Core\Checkout\Cart\Price\Struct\PriceCollection;
  17. use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition;
  18. use Shopware\Core\Checkout\Cart\Price\Struct\ReferencePrice;
  19. use Shopware\Core\Checkout\Cart\Price\Struct\ReferencePriceDefinition;
  20. use Shopware\Core\Checkout\Cart\Price\Struct\RegulationPrice;
  21. use Shopware\Core\Checkout\Cart\Tax\TaxDetector;
  22. use Shopware\Core\Checkout\Promotion\Exception\DiscountCalculatorNotFoundException;
  23. use Shopware\Core\Content\Product\DataAbstractionLayer\CheapestPrice\CalculatedCheapestPrice;
  24. use Shopware\Core\Content\Product\SalesChannel\Price\ReferencePriceDto;
  25. use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity;
  26. use Shopware\Core\Framework\DataAbstractionLayer\Entity;
  27. use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
  28. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  29. use Shopware\Core\Framework\DataAbstractionLayer\Pricing\CashRoundingConfig;
  30. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  31. use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
  32. use Shopware\Core\Framework\Struct\ArrayEntity;
  33. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  34. use Shopware\Core\System\Unit\UnitCollection;
  35. class DiscountGroupService
  36. {
  37.     public const ACRIS_STREAM_IDS_EXTENSION 'acrisStreamIds';
  38.     const ACRIS_DISCOUNT_GROUP_LINE_ITEM_DISCOUNT 'acrisDiscountGroupLineItemDiscount';
  39.     const ACRIS_RRP_PRICE_EXTENSION_KEY 'acris_rrp_price_price_struct';
  40.     private DiscountGroupGateway $discountGroupGateway;
  41.     private AbsolutePriceCalculator $absolutePriceCalculator;
  42.     private PercentagePriceCalculator $percentagePriceCalculator;
  43.     private EntityRepositoryInterface $unitRepository;
  44.     private QuantityPriceCalculator $calculator;
  45.     private ?UnitCollection $units null;
  46.     private CashRounding $priceRounding;
  47.     private TaxDetector $taxDetector;
  48.     public function __construct(
  49.         DiscountGroupGateway $discountGroupGateway,
  50.         AbsolutePriceCalculator $absolutePriceCalculator,
  51.         PercentagePriceCalculator $percentagePriceCalculator,
  52.         EntityRepositoryInterface $unitRepository,
  53.         QuantityPriceCalculator $calculator,
  54.         CashRounding $priceRounding,
  55.         TaxDetector $taxDetector
  56.     ) {
  57.         $this->discountGroupGateway $discountGroupGateway;
  58.         $this->absolutePriceCalculator $absolutePriceCalculator;
  59.         $this->percentagePriceCalculator $percentagePriceCalculator;
  60.         $this->unitRepository $unitRepository;
  61.         $this->calculator $calculator;
  62.         $this->priceRounding $priceRounding;
  63.         $this->taxDetector $taxDetector;
  64.     }
  65.     public function calculateProductPrices(iterable $productsSalesChannelContext $salesChannelContext): void
  66.     {
  67.         /** @var SalesChannelProductEntity $product */
  68.         foreach ($products as $product) {
  69.             if ($product->hasExtension(self::ACRIS_STREAM_IDS_EXTENSION) && !empty($product->getExtension(self::ACRIS_STREAM_IDS_EXTENSION))) {
  70.                 $productStreamIds $product->getExtension(self::ACRIS_STREAM_IDS_EXTENSION)->get('ids');
  71.             } else {
  72.                 $productStreamIds $this->discountGroupGateway->getProductStreamIds([$product->getId()], $salesChannelContext->getContext());
  73.                 $product->addExtension(self::ACRIS_STREAM_IDS_EXTENSION, new ArrayEntity(['ids' => $productStreamIds]));
  74.             }
  75.             $discountGroupResult $this->discountGroupGateway->getAllDiscountGroups($salesChannelContext$product->getId(), $productStreamIds);
  76.             if ($discountGroupResult->count() === 0) {
  77.                 continue;
  78.             }
  79.             $this->enrichDiscountGroups($product$discountGroupResult$salesChannelContext);
  80.             $this->calculateProductPricesByDiscountGroups($product$discountGroupResult$salesChannelContext);
  81.         }
  82.     }
  83.     public function reset(): void
  84.     {
  85.         $this->units null;
  86.     }
  87.     private function enrichDiscountGroups(SalesChannelProductEntity $productEntitySearchResult $discountGroupResultSalesChannelContext $salesChannelContext)
  88.     {
  89.         /** @var DiscountGroupEntity $discountGroup */
  90.         foreach ($discountGroupResult->getElements() as $discountGroup) {
  91.             $discountGroup->setProductIds(array_unique(array_merge(!empty($discountGroup->getProductIds()) ? $discountGroup->getProductIds() : [], [$product->getId()])));
  92.         }
  93.     }
  94.     private function calculateProductPricesByDiscountGroups(SalesChannelProductEntity $productEntitySearchResult $discountGroupResultSalesChannelContext $salesChannelContext): void
  95.     {
  96.         $productId $product->getId();
  97.         // product assignment
  98.         $discountGroupCollection $discountGroupResult->getEntities()->filter(function (DiscountGroupEntity $discountGroupEntity) use ($productId$product) {
  99.             return ($discountGroupEntity->getProductAssignmentType() === DiscountGroupDefinition::PRODUCT_ASSIGNMENT_TYPE_PRODUCT && !empty($discountGroupEntity->getProductId()) && $productId === $discountGroupEntity->getProductId())
  100.                 || (!empty($discountGroupEntity->getProductIds()) && $discountGroupEntity->getProductAssignmentType() !== DiscountGroupDefinition::PRODUCT_ASSIGNMENT_TYPE_MATERIAL_GROUP && in_array($productId$discountGroupEntity->getProductIds()))
  101.                 || ($discountGroupEntity->getProductAssignmentType() === DiscountGroupDefinition::PRODUCT_ASSIGNMENT_TYPE_MATERIAL_GROUP
  102.                     && !empty($discountGroupEntity->getMaterialGroup()) && !empty($product->getTranslated()) && array_key_exists('customFields'$product->getTranslated())
  103.                     && !empty($product->getTranslated()['customFields']) && (array_key_exists('acris_discount_group_value'$product->getTranslated()['customFields'])
  104.                         && !empty($product->getTranslated()['customFields']['acris_discount_group_value'])
  105.                         && $product->getTranslated()['customFields']['acris_discount_group_value'] === $discountGroupEntity->getMaterialGroup())
  106.                     || array_key_exists('acris_discount_group_product_value'$product->getTranslated()['customFields'])
  107.                     && !empty($product->getTranslated()['customFields']['acris_discount_group_product_value'])
  108.                     && $product->getTranslated()['customFields']['acris_discount_group_product_value'] === $discountGroupEntity->getMaterialGroup());
  109.         });
  110.         if($discountGroupCollection->count() === 0) {
  111.             return;
  112.         }
  113.         $this->sortDiscounts($discountGroupCollection);
  114.         $this->addLineItemDiscountData$product$discountGroupCollection );
  115.         $this->calculatePrices($product$discountGroupCollection$salesChannelContext);
  116.     }
  117.     private function calculatePrices(SalesChannelProductEntity $productEntityCollection $discountGroupCollectionSalesChannelContext $salesChannelContext)
  118.     {
  119.         $this->buildCalculatedPrices($product$discountGroupCollection$salesChannelContext);
  120.         /** @var DiscountGroupEntity $discountGroupEntity */
  121.         foreach ($discountGroupCollection->getElements() as $discountGroupEntity) {
  122.             if($discountGroupEntity->getDiscount() <= 0) continue;
  123.             if(empty($discountGroupEntity->getMinQuantity())) $discountGroupEntity->setMinQuantity(1);
  124.             $product->setCalculatedPrices($this->calculatePriceCollection($product->getCalculatedPrices(), $discountGroupEntity$salesChannelContext));
  125.             $product->setCalculatedPrice($this->calculatePrice($product->getCalculatedPrice(), $discountGroupEntity$salesChannelContext));
  126.             $cheapestPriceNew CalculatedCheapestPrice::createFrom($this->calculatePrice($product->getCalculatedCheapestPrice(), $discountGroupEntity$salesChannelContext));
  127.             $cheapestPriceNew->setHasRange($product->getCalculatedCheapestPrice()->hasRange());
  128.             $product->setCalculatedCheapestPrice($cheapestPriceNew);
  129.         }
  130.     }
  131.     private function calculatePriceCollection(PriceCollection $calculatedPricesDiscountGroupEntity $discountGroupEntitySalesChannelContext $salesChannelContext): PriceCollection
  132.     {
  133.         if($calculatedPrices->count() === 0) return $calculatedPrices;
  134.         $calculatedPricesNew = new PriceCollection();
  135.         $lastQuantity $calculatedPrices->last()->getQuantity();
  136.         foreach ($calculatedPrices as $calculatedPrice) {
  137.             if(!$calculatedPrice instanceof CalculatedPrice) {
  138.                 return $calculatedPrices;
  139.             }
  140.             $lastQuantity === $calculatedPrice->getQuantity() ? $isLast true $isLast false;
  141.             $calculatedPricesNew->add($this->calculatePrice($calculatedPrice$discountGroupEntity$salesChannelContext$isLast));
  142.         }
  143.         return $calculatedPricesNew;
  144.     }
  145.     private function calculatePrice(CalculatedPrice $calculatedPriceDiscountGroupEntity $discountGroupEntitySalesChannelContext $salesChannelContext$isLast true): CalculatedPrice
  146.     {
  147.         // before we built the prices, so this check is the only check which is needed
  148.         if(($isLast === true && ($discountGroupEntity->getMinQuantity() >= $calculatedPrice->getQuantity() || $discountGroupEntity->getMaxQuantity() === null))
  149.             || ($isLast === false && ($discountGroupEntity->getMinQuantity() <= $calculatedPrice->getQuantity() && ($discountGroupEntity->getMaxQuantity() === null || $discountGroupEntity->getMaxQuantity() >= $calculatedPrice->getQuantity())))) {
  150.             // continue here
  151.         } else {
  152.             return $calculatedPrice;
  153.         }
  154.         $discount $this->getPositive($discountGroupEntity->getDiscount());
  155.         if($discountGroupEntity->getDiscountType() === DiscountGroupDefinition::DISCOUNT_TYPE_ABSOLUTE) {
  156.             // prevent product prices smaller then 0
  157.             if($discount $calculatedPrice->getUnitPrice()) {
  158.                 $discount $calculatedPrice->getUnitPrice();
  159.             }
  160.             $this->setCalculationBase($calculatedPrice$discountGroupEntity$salesChannelContext);
  161.             $calculatedDiscount $this->absolutePriceCalculator->calculate($this->getSurchargeOrDiscount($discountGroupEntity$discount), new PriceCollection([$calculatedPrice]), $salesChannelContext);
  162.         } elseif($discountGroupEntity->getDiscountType() === DiscountGroupDefinition::DISCOUNT_TYPE_PERCENTAGE) {
  163.             // prevent percentage bigger then 100
  164.             if($discount 100) {
  165.                 $discount 100;
  166.             }
  167.             $this->setCalculationBase($calculatedPrice$discountGroupEntity$salesChannelContext);
  168.             $newCalculatedPrice = new CalculatedPrice(
  169.                 $calculatedPrice->getUnitPrice(),
  170.                 $calculatedPrice->getUnitPrice(),
  171.                 $calculatedPrice->getCalculatedTaxes(),
  172.                 $calculatedPrice->getTaxRules(),
  173.                 1,
  174.                 $calculatedPrice->getReferencePrice(),
  175.                 $calculatedPrice->getListPrice(),
  176.                 $calculatedPrice->getRegulationPrice()
  177.             );
  178.             $extensions $calculatedPrice->getExtensions();
  179.             $newCalculatedPrice->setExtensions($extensions);
  180.             $calculatedDiscount $this->percentagePriceCalculator->calculate($this->getSurchargeOrDiscount($discountGroupEntity$discount), new PriceCollection([$newCalculatedPrice]), $salesChannelContext);
  181.         } else {
  182.             throw new DiscountCalculatorNotFoundException($discountGroupEntity->getDiscountType());
  183.         }
  184.         $calculatedPriceNew = (new PriceCollection([$calculatedPrice$calculatedDiscount]))->sum();
  185.         $listPriceNew null;
  186.         if ($calculatedPriceNew->getUnitPrice() < 0) {
  187.             $calculatedPriceZero = new CalculatedPrice(
  188.                 0,
  189.                 $calculatedPriceNew->getUnitPrice(),
  190.                 $calculatedPriceNew->getCalculatedTaxes(),
  191.                 $calculatedPriceNew->getTaxRules(),
  192.                 $calculatedPriceNew->getQuantity(),
  193.                 $calculatedPriceNew->getReferencePrice(),
  194.                 $calculatedPriceNew->getListPrice(),
  195.                 $calculatedPriceNew->getRegulationPrice()
  196.             );
  197.             $calculatedPriceNew $calculatedPriceZero;
  198.         }
  199.         if ($calculatedPriceNew->getUnitPrice() > 0) {
  200.             switch ($discountGroupEntity->getListPriceType()) {
  201.                 case DiscountGroupDefinition::LIST_PRICE_TYPE_IGNORE:
  202.                     if($calculatedPrice->getListPrice() && $calculatedPrice->getListPrice()->getPrice()) {
  203.                         $listPriceNew ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $calculatedPrice->getListPrice()->getPrice());
  204.                     } else {
  205.                         $listPriceNew null;
  206.                     }
  207.                     break;
  208.                 case DiscountGroupDefinition::LIST_PRICE_TYPE_RRP:
  209.                     $tax $this->taxDetector->getTaxState($salesChannelContext);
  210.                     $rrp 0;
  211.                     if($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_GROSS
  212.                         || ($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_AUTO && $tax === CartPrice::TAX_STATE_GROSS)) {
  213.                         $rrp $this->getRrp($calculatedPriceCartPrice::TAX_STATE_GROSS);
  214.                     } elseif ($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_NET
  215.                         || ($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_AUTO && $tax !== CartPrice::TAX_STATE_GROSS)) {
  216.                         $rrp $this->getRrp($calculatedPriceCartPrice::TAX_STATE_NET);
  217.                     }
  218.                     if($rrp 0) {
  219.                         $listPriceNew ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $rrp);
  220.                     } else {
  221.                         $listPriceNew null;
  222.                     }
  223.                     break;
  224.                 case DiscountGroupDefinition::LIST_PRICE_TYPE_SET:
  225.                     if($calculatedPrice->getListPrice() && $calculatedPrice->getListPrice()->getPrice()) {
  226.                         $listPriceNew ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $calculatedPrice->getListPrice()->getPrice());
  227.                     } else {
  228.                         $listPriceNew ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $calculatedPrice->getUnitPrice());
  229.                     }
  230.                     break;
  231.                 case DiscountGroupDefinition::LIST_PRICE_TYPE_SET_RRP:
  232.                     $tax $this->taxDetector->getTaxState($salesChannelContext);
  233.                     $rrp 0;
  234.                     if($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_GROSS
  235.                         || ($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_AUTO && $tax === CartPrice::TAX_STATE_GROSS)) {
  236.                         $rrp $this->getRrp($calculatedPriceCartPrice::TAX_STATE_GROSS);
  237.                     } elseif ($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_NET
  238.                         || ($discountGroupEntity->getRrpTaxDisplay() === DiscountGroupDefinition::RRP_TAX_AUTO && $tax !== CartPrice::TAX_STATE_GROSS)) {
  239.                         $rrp $this->getRrp($calculatedPriceCartPrice::TAX_STATE_NET);
  240.                     }
  241.                     if($rrp 0) {
  242.                         $listPriceNew ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $rrp);
  243.                     } else {
  244.                         $listPriceNew ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $calculatedPrice->getUnitPrice());
  245.                     }
  246.                     break;
  247.                 case DiscountGroupDefinition::LIST_PRICE_TYPE_SET_PRICE:
  248.                     $listPriceNew ListPrice::createFromUnitPrice($calculatedPriceNew->getUnitPrice(), $calculatedPrice->getUnitPrice());
  249.                     break;
  250.                 case DiscountGroupDefinition::LIST_PRICE_TYPE_REMOVE:
  251.                     $listPriceNew null;
  252.                     break;
  253.                 default:
  254.                     $listPriceNew null;
  255.             }
  256.         }
  257.         return new CalculatedPrice(
  258.             $calculatedPriceNew->getUnitPrice(),
  259.             $calculatedPriceNew->getTotalPrice(),
  260.             $calculatedPriceNew->getCalculatedTaxes(),
  261.             $calculatedPriceNew->getTaxRules(),
  262.             $calculatedPrice->getQuantity(),
  263.             $this->calculateReferencePriceByReferencePrice($calculatedPriceNew->getUnitPrice(), $calculatedPrice->getReferencePrice(), $salesChannelContext->getItemRounding()),
  264.             $listPriceNew,
  265.             $calculatedPrice->getRegulationPrice()
  266.         );
  267.     }
  268.     private function getSurchargeOrDiscount(DiscountGroupEntity $discountGroupEntityfloat $discount): float
  269.     {
  270.         if($discountGroupEntity->getCalculationType() === DiscountGroupDefinition::CALCULATION_TYPE_SURCHARGE) {
  271.             return $this->getPositive($discount);
  272.         } else {
  273.             return $this->getNegative($discount);
  274.         }
  275.     }
  276.     private function getNegative(float $value): float
  277.     {
  278.         return -abs($value);
  279.     }
  280.     private function getPositive(float $value): float
  281.     {
  282.         return abs($value);
  283.     }
  284.     private function sortDiscounts(DiscountGroupCollection $discountGroupCollection): void
  285.     {
  286.         $priority null;
  287.         $exclude false;
  288.         foreach ($discountGroupCollection->getElements() as $discountGroupEntity) {
  289.             if ($exclude && !empty($priority) && $discountGroupEntity->getPriority() < $priority) {
  290.                 $discountGroupCollection->remove($discountGroupEntity->getId());
  291.             }
  292.             if ($discountGroupEntity->getExcluded()) {
  293.                 $exclude true;
  294.             }
  295.             $priority $discountGroupEntity->getPriority();
  296.         }
  297.     }
  298.     private function addLineItemDiscountData(SalesChannelProductEntity $productDiscountGroupCollection $discountGroupCollection)
  299.     {
  300.         /** @var LineItemDiscountStruct $lineItemDiscount */
  301.         $lineItemDiscount = new LineItemDiscountStruct$product->getCalculatedPrice()->getUnitPrice(), $discountGroupCollection);
  302.         $product->addExtensionself::ACRIS_DISCOUNT_GROUP_LINE_ITEM_DISCOUNT$lineItemDiscount );
  303.     }
  304.     private function buildCalculatedPrices(SalesChannelProductEntity $productDiscountGroupCollection $discountGroupCollectionSalesChannelContext $salesChannelContext): void
  305.     {
  306.         if($this->isScalePriceRebuildNeeded($discountGroupCollection) === false) {
  307.             return;
  308.         }
  309.         // if product has no calculated prices, we have to set
  310.         if(empty($product->getCalculatedPrices()) || $product->getCalculatedPrices()->count() === 0) {
  311.             $product->setCalculatedPrices(new PriceCollection([$product->getCalculatedPrice()]));
  312.         }
  313.         // build price struct
  314.         $scalePriceCollection = new ScalePriceCollection();
  315.         $quantityLast $product->getCalculatedPrices()->last()->getQuantity();
  316.         $quantityFromBefore 1;
  317.         foreach ($product->getCalculatedPrices() as $calculatedPrice) {
  318.             if($calculatedPrice->getQuantity() === $quantityLast) {
  319.                 $scalePriceCollection->add(new ScalePriceStruct($calculatedPrice->getQuantity(), null$calculatedPrice));
  320.             } else {
  321.                 $scalePriceCollection->add(new ScalePriceStruct($quantityFromBefore$calculatedPrice->getQuantity(), $calculatedPrice));
  322.                 $quantityFromBefore $calculatedPrice->getQuantity() + 1;
  323.             }
  324.         }
  325.         // now we add our discounts
  326.         // first we add minimum
  327.         foreach ($discountGroupCollection->getElements() as $discountGroup) {
  328.             $minValue $discountGroup->getMinQuantity();
  329.             if(empty($minValue) === true) {
  330.                 $minValue 1;
  331.             }
  332.             /** @var ScalePriceStruct $scalePrice */
  333.             foreach ($scalePriceCollection->getElements() as $scalePrice) {
  334.                 if($minValue === $scalePrice->getFrom()) {
  335.                     continue 2;
  336.                 }
  337.             }
  338.             /** @var ScalePriceStruct $scalePrice */
  339.             foreach ($scalePriceCollection->getElements() as $scalePriceKey => $scalePrice) {
  340.                 if($minValue $scalePrice->getFrom()
  341.                     && ($scalePrice->getTo() === null || $minValue <= $scalePrice->getTo())) {
  342.                     $scalePriceCollection->add(new ScalePriceStruct($scalePrice->getFrom(), $minValue 1$scalePrice->getCalculatedPrice()));
  343.                     $scalePriceCollection->add(new ScalePriceStruct($minValue$scalePrice->getTo(), $scalePrice->getCalculatedPrice()));
  344.                     $scalePriceCollection->remove($scalePriceKey);
  345.                 }
  346.             }
  347.         }
  348.         // now we add our discounts
  349.         // afterwards we add maximum
  350.         foreach ($discountGroupCollection->getElements() as $discountGroup) {
  351.             $maxValue $discountGroup->getMaxQuantity();
  352.             /** @var ScalePriceStruct $scalePrice */
  353.             foreach ($scalePriceCollection->getElements() as $scalePrice) {
  354.                 if($maxValue === $scalePrice->getTo()) {
  355.                     continue 2;
  356.                 }
  357.             }
  358.             /** @var ScalePriceStruct $scalePrice */
  359.             foreach ($scalePriceCollection->getElements() as $scalePriceKey => $scalePrice) {
  360.                 if($maxValue >= $scalePrice->getFrom()
  361.                     && ($maxValue $scalePrice->getTo() || $scalePrice->getTo() === null)) {
  362.                     $scalePriceCollection->add(new ScalePriceStruct($scalePrice->getFrom(), $maxValue$scalePrice->getCalculatedPrice()));
  363.                     $scalePriceCollection->add(new ScalePriceStruct($maxValue 1$scalePrice->getTo(), $scalePrice->getCalculatedPrice()));
  364.                     $scalePriceCollection->remove($scalePriceKey);
  365.                 }
  366.             }
  367.         }
  368.         // sort scale prices quantity asc
  369.         $scalePriceCollection->sort(function (ScalePriceStruct $aScalePriceStruct $b) {
  370.             return $a->getFrom() <=> $b->getFrom();
  371.         });
  372.         // at the end we build back the calculated prices
  373.         $priceCollection = new PriceCollection();
  374.         $quantityLast $scalePriceCollection->last()->getFrom();
  375.         /** @var ScalePriceStruct $scalePrice */
  376.         foreach ($scalePriceCollection as $scalePrice) {
  377.             $calculatedPrice $scalePrice->getCalculatedPrice();
  378.             if($quantityLast === $scalePrice->getFrom()) {
  379.                 $quantity $scalePrice->getFrom();
  380.             } else {
  381.                 $quantity $scalePrice->getTo();
  382.             }
  383.             if($quantity === $calculatedPrice->getQuantity()) {
  384.                 $priceCollection->add($calculatedPrice);
  385.             } else {
  386.                 $units $this->getUnits($salesChannelContext);
  387.                 $reference ReferencePriceDto::createFromProduct($product);
  388.                 $calculatedPrice->getListPrice() instanceof ListPrice $listPrice $calculatedPrice->getListPrice()->getPrice() : $listPrice null;
  389.                 $calculatedPrice->getRegulationPrice() instanceof RegulationPrice $regulationPrice $calculatedPrice->getRegulationPrice()->getPrice() : $regulationPrice null;
  390.                 $definition $this->buildDefinition($product$calculatedPrice->getUnitPrice(), $salesChannelContext$units$reference$quantity$listPrice$regulationPrice);
  391.                 $priceCollection->add($this->calculator->calculate($definition$salesChannelContext));
  392.             }
  393.         }
  394.         $product->setCalculatedPrices($priceCollection);
  395.     }
  396.     private function isScalePriceRebuildNeeded(DiscountGroupCollection $discountGroupCollection): bool
  397.     {
  398.         /** @var DiscountGroupEntity $discountGroup */
  399.         foreach ($discountGroupCollection->getElements() as $discountGroup) {
  400.             if($discountGroup->getMinQuantity() > || $discountGroup->getMaxQuantity() !== null) {
  401.                 return true;
  402.             }
  403.         }
  404.         return false;
  405.     }
  406.     /*
  407.      * Copied and adapted from ProductPriceCalculator
  408.      * */
  409.     private function buildDefinition(
  410.         Entity $product,
  411.         float $price,
  412.         SalesChannelContext $context,
  413.         UnitCollection $units,
  414.         ReferencePriceDto $reference,
  415.         int $quantity 1,
  416.         ?float $listPrice null,
  417.         ?float $regulationPrice null
  418.     ): QuantityPriceDefinition {
  419.         $taxId $product->get('taxId');
  420.         $definition = new QuantityPriceDefinition($price$context->buildTaxRules($taxId), $quantity);
  421.         $definition->setReferencePriceDefinition(
  422.             $this->buildReferencePriceDefinition($reference$units)
  423.         );
  424.         $definition->setListPrice($listPrice);
  425.         $definition->setRegulationPrice($regulationPrice);
  426.         return $definition;
  427.     }
  428.     private function buildReferencePriceDefinition(ReferencePriceDto $definitionUnitCollection $units): ?ReferencePriceDefinition
  429.     {
  430.         if ($definition->getPurchase() === null || $definition->getPurchase() <= 0) {
  431.             return null;
  432.         }
  433.         if ($definition->getUnitId() === null) {
  434.             return null;
  435.         }
  436.         if ($definition->getReference() === null || $definition->getReference() <= 0) {
  437.             return null;
  438.         }
  439.         if ($definition->getPurchase() === $definition->getReference()) {
  440.             return null;
  441.         }
  442.         $unit $units->get($definition->getUnitId());
  443.         if ($unit === null) {
  444.             return null;
  445.         }
  446.         return new ReferencePriceDefinition(
  447.             $definition->getPurchase(),
  448.             $definition->getReference(),
  449.             $unit->getTranslation('name')
  450.         );
  451.     }
  452.     private function getUnits(SalesChannelContext $context): UnitCollection
  453.     {
  454.         if ($this->units !== null) {
  455.             return $this->units;
  456.         }
  457.         /** @var UnitCollection $units */
  458.         $units $this->unitRepository
  459.             ->search(new Criteria(), $context->getContext())
  460.             ->getEntities();
  461.         return $this->units $units;
  462.     }
  463.     /**
  464.      * Copied and adapted from GrossPriceCalculator and NetPriceCalculator
  465.      */
  466.     private function calculateReferencePriceByReferencePrice(float $price, ?ReferencePrice $referencePriceCashRoundingConfig $config): ?ReferencePrice
  467.     {
  468.         if (!$referencePrice instanceof ReferencePrice) {
  469.             return $referencePrice;
  470.         }
  471.         if ($referencePrice->getPurchaseUnit() <= || $referencePrice->getReferenceUnit() <= 0) {
  472.             return null;
  473.         }
  474.         $price $price $referencePrice->getPurchaseUnit() * $referencePrice->getReferenceUnit();
  475.         $price $this->priceRounding->mathRound($price$config);
  476.         return new ReferencePrice(
  477.             $price,
  478.             $referencePrice->getPurchaseUnit(),
  479.             $referencePrice->getReferenceUnit(),
  480.             $referencePrice->getUnitName()
  481.         );
  482.     }
  483.     private function setCalculationBase(CalculatedPrice $calculatedPriceDiscountGroupEntity $discountGroupEntitySalesChannelContext $salesChannelContext)
  484.     {
  485.         $newUnitPrice null;
  486.         if($discountGroupEntity->getCalculationBase() === DiscountGroupDefinition::CALCULATION_BASE_LIST_PRICE) {
  487.             if($calculatedPrice->getListPrice() instanceof ListPrice && $calculatedPrice->getListPrice()->getPrice() > 0) {
  488.                 $newUnitPrice $calculatedPrice->getListPrice()->getPrice();
  489.             }
  490.         } elseif($discountGroupEntity->getCalculationBase() === DiscountGroupDefinition::CALCULATION_BASE_RRP) {
  491.             $tax $this->taxDetector->getTaxState($salesChannelContext);
  492.             $rrp 0;
  493.             if($discountGroupEntity->getRrpTax() === DiscountGroupDefinition::RRP_TAX_GROSS
  494.                 || ($discountGroupEntity->getRrpTax() === DiscountGroupDefinition::RRP_TAX_AUTO && $tax === CartPrice::TAX_STATE_GROSS)) {
  495.                 $rrp $this->getRrp($calculatedPriceCartPrice::TAX_STATE_GROSS);
  496.             } elseif ($discountGroupEntity->getRrpTax() === DiscountGroupDefinition::RRP_TAX_NET
  497.                 || ($discountGroupEntity->getRrpTax() === DiscountGroupDefinition::RRP_TAX_AUTO && $tax !== CartPrice::TAX_STATE_GROSS)) {
  498.                 $rrp $this->getRrp($calculatedPriceCartPrice::TAX_STATE_NET);
  499.             }
  500.             if($rrp 0) {
  501.                 $newUnitPrice $rrp;
  502.             }
  503.         }
  504.         if($newUnitPrice !== null) {
  505.             $calculatedPrice->assign(['unitPrice' => $newUnitPrice]);
  506.             $calculatedPrice->assign(['totalPrice' => $newUnitPrice $calculatedPrice->getQuantity()]);
  507.         }
  508.     }
  509.     private function getRrp(CalculatedPrice $calculatedPricestring $tax CartPrice::TAX_STATE_GROSS): float
  510.     {
  511.         $rrp 0;
  512.         if($calculatedPrice->hasExtension(self::ACRIS_RRP_PRICE_EXTENSION_KEY) && $calculatedPrice->getExtension(self::ACRIS_RRP_PRICE_EXTENSION_KEY)->getRrpPricePrice()) {
  513.             $rrpPrice $calculatedPrice->getExtension(self::ACRIS_RRP_PRICE_EXTENSION_KEY)->getRrpPricePrice();
  514.             if($tax === CartPrice::TAX_STATE_GROSS && $rrpPrice->getGross() > 0) {
  515.                 $rrp $rrpPrice->getGross();
  516.             } elseif($tax !== CartPrice::TAX_STATE_GROSS && $rrpPrice->getNet() > 0) {
  517.                 $rrp $rrpPrice->getNet();
  518.             }
  519.         }
  520.         return (float) $rrp;
  521.     }
  522. }