vendor/shopware/core/Content/Product/SalesChannel/Price/ProductPriceCalculator.php line 48

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Product\SalesChannel\Price;
  3. use Shopware\Core\Checkout\Cart\Price\QuantityPriceCalculator;
  4. use Shopware\Core\Checkout\Cart\Price\Struct\CartPrice;
  5. use Shopware\Core\Checkout\Cart\Price\Struct\PriceCollection as CalculatedPriceCollection;
  6. use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition;
  7. use Shopware\Core\Checkout\Cart\Price\Struct\ReferencePriceDefinition;
  8. use Shopware\Core\Content\Product\Aggregate\ProductPrice\ProductPriceCollection;
  9. use Shopware\Core\Content\Product\DataAbstractionLayer\CheapestPrice\CalculatedCheapestPrice;
  10. use Shopware\Core\Content\Product\DataAbstractionLayer\CheapestPrice\CheapestPrice;
  11. use Shopware\Core\Framework\DataAbstractionLayer\Entity;
  12. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Pricing\Price;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Pricing\PriceCollection;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  16. use Shopware\Core\Framework\Log\Package;
  17. use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
  18. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  19. use Shopware\Core\System\Unit\UnitCollection;
  20. use Symfony\Contracts\Service\ResetInterface;
  21. #[Package('inventory')]
  22. class ProductPriceCalculator extends AbstractProductPriceCalculator implements ResetInterface
  23. {
  24.     private ?UnitCollection $units null;
  25.     /**
  26.      * @internal
  27.      */
  28.     public function __construct(
  29.         private readonly EntityRepository $unitRepository,
  30.         private readonly QuantityPriceCalculator $calculator
  31.     ) {
  32.     }
  33.     public function getDecorated(): AbstractProductPriceCalculator
  34.     {
  35.         throw new DecorationPatternException(self::class);
  36.     }
  37.     /**
  38.      * @param Entity[] $products
  39.      */
  40.     public function calculate(iterable $productsSalesChannelContext $context): void
  41.     {
  42.         $units $this->getUnits($context);
  43.         /** @var Entity $product */
  44.         foreach ($products as $product) {
  45.             $this->calculatePrice($product$context$units);
  46.             $this->calculateAdvancePrices($product$context$units);
  47.             $this->calculateCheapestPrice($product$context$units);
  48.         }
  49.     }
  50.     public function reset(): void
  51.     {
  52.         $this->units null;
  53.     }
  54.     private function calculatePrice(Entity $productSalesChannelContext $contextUnitCollection $units): void
  55.     {
  56.         $price $product->get('price');
  57.         $taxId $product->get('taxId');
  58.         if ($price === null || $taxId === null) {
  59.             return;
  60.         }
  61.         $reference ReferencePriceDto::createFromEntity($product);
  62.         $definition $this->buildDefinition($product$price$context$units$reference);
  63.         $price $this->calculator->calculate($definition$context);
  64.         $product->assign([
  65.             'calculatedPrice' => $price,
  66.         ]);
  67.     }
  68.     private function calculateAdvancePrices(Entity $productSalesChannelContext $contextUnitCollection $units): void
  69.     {
  70.         $prices $product->get('prices');
  71.         $product->assign(['calculatedPrices' => new CalculatedPriceCollection()]);
  72.         if ($prices === null) {
  73.             return;
  74.         }
  75.         if (!$prices instanceof ProductPriceCollection) {
  76.             return;
  77.         }
  78.         $prices $this->filterRulePrices($prices$context);
  79.         if ($prices === null) {
  80.             return;
  81.         }
  82.         $prices->sortByQuantity();
  83.         $reference ReferencePriceDto::createFromEntity($product);
  84.         $calculated = new CalculatedPriceCollection();
  85.         foreach ($prices as $price) {
  86.             $quantity $price->getQuantityEnd() ?? $price->getQuantityStart();
  87.             $definition $this->buildDefinition($product$price->getPrice(), $context$units$reference$quantity);
  88.             $calculated->add($this->calculator->calculate($definition$context));
  89.         }
  90.         $product->assign(['calculatedPrices' => $calculated]);
  91.     }
  92.     private function calculateCheapestPrice(Entity $productSalesChannelContext $contextUnitCollection $units): void
  93.     {
  94.         $cheapest $product->get('cheapestPrice');
  95.         if ($product->get('taxId') === null) {
  96.             return;
  97.         }
  98.         if (!$cheapest instanceof CheapestPrice) {
  99.             $price $product->get('price');
  100.             if ($price === null) {
  101.                 return;
  102.             }
  103.             $reference ReferencePriceDto::createFromEntity($product);
  104.             $definition $this->buildDefinition($product$price$context$units$reference);
  105.             $calculated CalculatedCheapestPrice::createFrom(
  106.                 $this->calculator->calculate($definition$context)
  107.             );
  108.             $prices $product->get('calculatedPrices');
  109.             $hasRange $prices instanceof CalculatedPriceCollection && $prices->count() > 1;
  110.             $calculated->setHasRange($hasRange);
  111.             $product->assign(['calculatedCheapestPrice' => $calculated]);
  112.             return;
  113.         }
  114.         $reference ReferencePriceDto::createFromCheapestPrice($cheapest);
  115.         $definition $this->buildDefinition($product$cheapest->getPrice(), $context$units$reference);
  116.         $calculated CalculatedCheapestPrice::createFrom(
  117.             $this->calculator->calculate($definition$context)
  118.         );
  119.         $calculated->setHasRange($cheapest->hasRange());
  120.         $product->assign(['calculatedCheapestPrice' => $calculated]);
  121.     }
  122.     private function buildDefinition(
  123.         Entity $product,
  124.         PriceCollection $prices,
  125.         SalesChannelContext $context,
  126.         UnitCollection $units,
  127.         ReferencePriceDto $reference,
  128.         int $quantity 1
  129.     ): QuantityPriceDefinition {
  130.         $price $this->getPriceValue($prices$context);
  131.         $taxId $product->get('taxId');
  132.         $definition = new QuantityPriceDefinition($price$context->buildTaxRules($taxId), $quantity);
  133.         $definition->setReferencePriceDefinition(
  134.             $this->buildReferencePriceDefinition($reference$units)
  135.         );
  136.         $definition->setListPrice(
  137.             $this->getListPrice($prices$context)
  138.         );
  139.         $definition->setRegulationPrice(
  140.             $this->getRegulationPrice($prices$context)
  141.         );
  142.         return $definition;
  143.     }
  144.     private function getPriceValue(PriceCollection $priceSalesChannelContext $context): float
  145.     {
  146.         /** @var Price $currency */
  147.         $currency $price->getCurrencyPrice($context->getCurrencyId());
  148.         $value $this->getPriceForTaxState($currency$context);
  149.         if ($currency->getCurrencyId() !== $context->getCurrency()->getId()) {
  150.             $value *= $context->getContext()->getCurrencyFactor();
  151.         }
  152.         return $value;
  153.     }
  154.     private function getPriceForTaxState(Price $priceSalesChannelContext $context): float
  155.     {
  156.         if ($context->getTaxState() === CartPrice::TAX_STATE_GROSS) {
  157.             return $price->getGross();
  158.         }
  159.         return $price->getNet();
  160.     }
  161.     private function getListPrice(PriceCollection $pricesSalesChannelContext $context): ?float
  162.     {
  163.         $price $prices->getCurrencyPrice($context->getCurrency()->getId());
  164.         if ($price === null || $price->getListPrice() === null) {
  165.             return null;
  166.         }
  167.         $value $this->getPriceForTaxState($price->getListPrice(), $context);
  168.         if ($price->getCurrencyId() !== $context->getCurrency()->getId()) {
  169.             $value *= $context->getContext()->getCurrencyFactor();
  170.         }
  171.         return $value;
  172.     }
  173.     private function getRegulationPrice(PriceCollection $pricesSalesChannelContext $context): ?float
  174.     {
  175.         $price $prices->getCurrencyPrice($context->getCurrency()->getId());
  176.         if ($price === null || $price->getRegulationPrice() === null) {
  177.             return null;
  178.         }
  179.         $taxPrice $this->getPriceForTaxState($price$context);
  180.         $value $this->getPriceForTaxState($price->getRegulationPrice(), $context);
  181.         if ($taxPrice === 0.0 || $taxPrice === $value) {
  182.             return null;
  183.         }
  184.         if ($price->getCurrencyId() !== $context->getCurrency()->getId()) {
  185.             $value *= $context->getContext()->getCurrencyFactor();
  186.         }
  187.         return $value;
  188.     }
  189.     private function buildReferencePriceDefinition(ReferencePriceDto $definitionUnitCollection $units): ?ReferencePriceDefinition
  190.     {
  191.         if (
  192.             $definition->getPurchase() === null
  193.             || $definition->getPurchase() <= 0
  194.             || $definition->getUnitId() === null
  195.             || $definition->getReference() === null
  196.             || $definition->getReference() <= 0
  197.             || $definition->getPurchase() === $definition->getReference()
  198.         ) {
  199.             return null;
  200.         }
  201.         $unit $units->get($definition->getUnitId());
  202.         if ($unit === null) {
  203.             return null;
  204.         }
  205.         return new ReferencePriceDefinition(
  206.             $definition->getPurchase(),
  207.             $definition->getReference(),
  208.             $unit->getTranslation('name')
  209.         );
  210.     }
  211.     private function filterRulePrices(ProductPriceCollection $rulesSalesChannelContext $context): ?ProductPriceCollection
  212.     {
  213.         foreach ($context->getRuleIds() as $ruleId) {
  214.             $filtered $rules->filterByRuleId($ruleId);
  215.             if (\count($filtered) > 0) {
  216.                 return $filtered;
  217.             }
  218.         }
  219.         return null;
  220.     }
  221.     private function getUnits(SalesChannelContext $context): UnitCollection
  222.     {
  223.         if ($this->units !== null) {
  224.             return $this->units;
  225.         }
  226.         $criteria = new Criteria();
  227.         $criteria->setTitle('product-price-calculator::units');
  228.         /** @var UnitCollection $units */
  229.         $units $this->unitRepository
  230.             ->search($criteria$context->getContext())
  231.             ->getEntities();
  232.         return $this->units $units;
  233.     }
  234. }