Mapping of pricelist IDs to currency (cache in case of multiple calls to activatePreorder) */ private array $pricelistCurrenciesCache = []; public function __construct( private readonly ContextManager $contextManager, private readonly PurchaseUtil $purchaseUtil, private readonly PreorderUtil $listUtil, private readonly PricelistContext $pricelistContext, private readonly PriceConverter $priceConverter, ) { } /** * @template T of mixed * * @param callable(): T $callback * * @return T * * @throws InvalidArgumentException */ public function withDynamicPrices(Preorder $preorder, array $items, callable $callback) { $contexts = array_merge( $this->getBaseContexts($preorder), $this->getDynamicContexts($preorder, $items), ); return $this->contextManager->activateContexts($contexts, $callback); } /** * @template T of mixed * * @param callable(): T $callback * * @return T * * @throws InvalidArgumentException */ public function withBasePrices(Preorder $preorder, callable $callback) { $contexts = $this->getBaseContexts($preorder); return $this->contextManager->activateContexts($contexts, $callback); } protected function getBaseContexts(Preorder $preorder, ?\User $user = null): array { $contexts = []; if ($user) { $contexts[UserContext::class] = $user; } if (PreorderUtil::fieldIsComposedOfDigits($preorder, 'id_pricelist')) { $contexts[PricelistContext::class] = (int) $preorder['id_pricelist']; } else { $contexts[PricelistContext::class] = ContextManager::FORCE_EMPTY; } if (PreorderUtil::fieldIsComposedOfDigits($preorder, 'id_price_level')) { $contexts[PriceLevelContext::class] = (int) $preorder['id_price_level']; } else { $contexts[PriceLevelContext::class] = ContextManager::FORCE_EMPTY; } return $contexts; } /** * @throws InvalidArgumentException */ private function getDynamicContexts(Preorder $preorder, array $items = []): array { $contexts = []; // Pokud nemáme nastaveny dynamické ceny, tak není potřeba již volat výpočet total price if (!$preorder->hasDynamicPrice()) { return $contexts; } /* @var TotalPrice $total */ $this->withBasePrices($preorder, function () use ($items, &$total) { $productList = $this->listUtil->getPreorderProductList($items); $purchaseState = PreorderUtil::toPurchaseState($productList->getProducts()); $total = $this->purchaseUtil->recalculateTotalPrices($purchaseState) ->getTotalPrice(); }); // convert currency of total price to default preorder's price-list's currency if ($pricelistId = $preorder['id_pricelist'] ?? false) { /** @var Currency $currency */ $currency = $this->contextManager->activateContexts( [PricelistContext::class => $pricelistId], fn () => $this->pricelistContext->getActive()->getCurrency(), ); if ($total->getCurrency()->getId() !== $currency->getId()) { $total = $this->priceConverter->convertPrice( $total->getCurrency(), $currency, $total->getPriceWithoutVat(), ); } } if (method_exists($total, 'getPriceWithoutVat')) { $total = $total->getPriceWithoutVat(); } if ($idPriceLevel = $preorder->getPriceLevelID($total)) { $contexts[PriceLevelContext::class] = $idPriceLevel; } if ($idPricelist = $preorder->getPricelistID($total)) { $contexts[PricelistContext::class] = $idPricelist; } return $contexts; } /** * @template T of mixed * * @param (callable(): T)|null $callback * * @return T * * @throws InvalidArgumentException */ public function activateUserPreorder(UserPreorder $userPreorder, ?callable $callback = null) { $contexts = array_merge( $this->getBaseContexts($userPreorder->getPreorder()), $this->getDynamicContexts($userPreorder->getPreorder(), $userPreorder->getItems()), ); $customData = $userPreorder->getCustomData(); if ($lang = $customData->get('language') ?? false) { $contexts[LanguageContext::class] = $lang; } if ($currency = $customData->get('currency') ?? false) { $contexts[CurrencyContext::class] = $currency; } if ($callback) { return $this->contextManager->activateContexts($contexts, $callback); } foreach ($contexts as $class => $id) { Contexts::get($class)?->activate($id === null ? null : (string) $id); } return null; } /** * @template T of mixed * * @param (callable(): T)|null $callback * * @return T * * @throws InvalidArgumentException */ public function activatePreorder(Preorder $preorder, ?\User $user, ?callable $callback = null) { $contexts = $this->getBaseContexts($preorder, $user); if ($user) { $contexts = array_merge( $contexts, $this->getDynamicContexts($preorder, $preorder->getItemsByUser($user)), ); } // Activate the appropriate currency in case of the preorder having a different currency than the site. // E.g. user is on the czech site with CZK, but the preorder pricelist is EUR -> prices displayed in CZK, // but total price is calculated based on the pricelist = EUR values with Kč. // "Appropriate currency" = currency of the default pricelist in preorder settings. if (null !== ($priceListId = $preorder->getPricelistID())) { $currency = $this->getCurrencyFromPricelist($priceListId); if ($currency) { $contexts[CurrencyContext::class] = $currency; } } if ($callback) { return $this->contextManager->activateContexts($contexts, $callback); } foreach ($contexts as $class => $id) { $context = Contexts::get($class); switch ($id) { case ContextManager::FORCE_EMPTY: if (!($context instanceof EmptiableContext)) { throw new \LogicException('cannot force non-emptiable context to be empty'); } $context->forceEmpty(); break; case null: $context->clearCache(); break; default: $context->activate(is_int($id) ? (string) $id : $id); } } return null; } private function getCurrencyFromPricelist(int $pricelistId): ?string { if (!array_key_exists($pricelistId, $this->pricelistCurrenciesCache)) { $currency = sqlQueryBuilder()->select('currency') ->from('pricelists') ->andWhere(Operator::equals(['id' => $pricelistId])) ->execute() ->fetchOne(); $this->pricelistCurrenciesCache[$pricelistId] = $currency ?: null; } return $this->pricelistCurrenciesCache[$pricelistId]; } }