Files
kupshop/bundles/External/PompoBundle/Util/ProductUtil.php
2025-08-02 16:30:27 +02:00

757 lines
28 KiB
PHP

<?php
declare(strict_types=1);
namespace External\PompoBundle\Util;
use KupShop\CatalogBundle\ProductList\MultiFetch;
use KupShop\CatalogBundle\ProductList\ProductCollection;
use KupShop\CatalogBundle\ProductList\ProductList;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\UserContext;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\DateUtil;
use KupShop\KupShopBundle\Util\Functional\Mapping;
use KupShop\KupShopBundle\Util\Price\Price;
use KupShop\KupShopBundle\Util\Price\PriceCalculator;
use KupShop\KupShopBundle\Util\Price\ProductPrice;
use KupShop\LabelsBundle\Util\LabelUtil;
use KupShop\SellerBundle\Context\SellerContext;
use Query\Operator;
use Query\Product;
class ProductUtil
{
private const int MAIN_STORE_ID = 2;
private const int SUPPLIER_ID = 3;
public function __construct(
private Configuration $configuration,
private ProductList $productList,
private LabelUtil $labelUtil,
) {
}
/**
* Multifetch pro nafetchovani dalsich cen, ktere potrebujeme zobrazovat na katalogu / detailu produktu.
*
* Nafetchuje DMOC cenu, VIP cenu a "Pompo klub setri" cenu.
* Zaroven to prida dmocDiscount, coz je sleva vypocitana z DMOC ceny.
*/
public function fetchAdditionalPrices(ProductCollection $products): void
{
if ($products->count() === 0) {
return;
}
if (isset($products->_pompoVipPricesFetched)) {
return;
}
$products->fetchPricelists();
$this->fetchDefaultPriceForDiscount($products);
$currencyContext = Contexts::get(CurrencyContext::class);
// pokud existuje cenik s DMOC, tak ho nactu
if ($dmocPriceListId = ($this->configuration->getDMOCPriceLists()[$currencyContext->getActiveId()] ?? false)) {
foreach ($products as $product) {
$product->dmocPrice = $product->pricelists[$dmocPriceListId]['productPrice'] ?? null;
$product->dmocDiscount = \DecimalConstants::zero();
if ($product->dmocPrice) {
$product->dmocPrice = PriceCalculator::convert($product->dmocPrice, $product->getProductPrice()->getCurrency());
$productPriceWithVat = $product->getProductPrice()->getPriceWithVat();
$dmocPriceWithVat = $product->dmocPrice->getPriceWithVat();
if ($dmocPriceWithVat->isPositive()) {
$product->dmocDiscount = \DecimalConstants::one()->sub($productPriceWithVat->div($dmocPriceWithVat))->mul(\DecimalConstants::hundred());
} else {
$product->dmocDiscount = \DecimalConstants::zero();
}
}
}
}
// pokud existuje cenik pro prihlaseneho uzivatele, tak nactu VIP cenu
if ($vipPriceListId = ($this->configuration->getUsersPriceLists()[$currencyContext->getActiveId()] ?? false)) {
$userIsActive = Contexts::get(UserContext::class)->isActive();
foreach ($products as $product) {
$product->userVipPrice = $product->pricelists[$vipPriceListId]['productPrice'] ?? null;
$product->vipOriginalPrice = null;
// pokud jsem prihlasenej a existuje VIP cena, tak chci nacist "Pompo setri" cenu
if ($userIsActive && $product->userVipPrice) {
$userVipPrice = $product->userVipPrice;
// puvodni cena produktu (neprihlaseny uzivatel)
// pokud mam puvodni cenu brat z ceniku
if ($originalPriceListId = ($this->configuration->getPriceLists()[$currencyContext->getActiveId()] ?? false)) {
$originalPrice = $product->pricelists[$originalPriceListId]['productPrice'] ?? $product->priceOriginal->getObject();
$defaultPriceForDiscount = $originalPrice->getPriceWithoutDiscount();
if (!empty($product->pricelists[$originalPriceListId]['price_for_discount'])) {
$defaultPriceForDiscount = new ProductPrice(
toDecimal($product->pricelists[$originalPriceListId]['price_for_discount']),
Contexts::get(CurrencyContext::class)->getOrDefault($product->pricelists[$originalPriceListId]['currency']),
$product->vat
);
}
} else {
$originalPrice = $product->priceOriginal->getObject();
$defaultPriceForDiscount = $product->defaultPriceForDiscount ?? $originalPrice->getPriceWithoutDiscount();
}
// prevod cen na stejnou menu
PriceCalculator::makeCompatible($userVipPrice, $originalPrice, false);
// pokud je VIP cena i original cena stejna
$vipSavingsEnabled = true;
if ($userVipPrice->getPriceWithVat()->equals($originalPrice->getPriceWithVat())) {
$originalPrice = $defaultPriceForDiscount;
$vipSavingsEnabled = false;
}
// prevod cen na stejnou menu
PriceCalculator::makeCompatible($userVipPrice, $originalPrice, false);
$product->priceForDiscount = $originalPrice;
$product->originalPrice = $originalPrice;
$product->vipOriginalPrice = $originalPrice;
// spocitam rozdil - tim zjistim kolik mi setri Pompo klub
$diff = $originalPrice->getPriceWithVat()->sub($userVipPrice->getPriceWithVat());
$product->vipSavingsPrice = null;
// pokud je rozdil vetsi jak 0, tak ho ulozim at ho muzu zobrazit
if ($vipSavingsEnabled && $diff->isPositive()) {
$product->vipSavingsPrice = new Price($diff, $userVipPrice->getCurrency(), 0);
}
}
}
}
$products->_pompoVipPricesFetched = true;
}
public function fetchDefaultPriceForDiscount(ProductCollection $products): void
{
if ($products->count() === 0) {
return;
}
$qb = sqlQueryBuilder()
->select('id, COALESCE(price_for_discount, price) as price_for_discount, vat')
->from('products')
->where(Operator::inIntArray($products->getProductIds(), 'id'));
$defaultCurrency = Contexts::get(CurrencyContext::class)->getDefault();
foreach ($qb->execute() as $item) {
if (isset($products[$item['id']]) && !empty($item['price_for_discount'])) {
$price_for_discount = toDecimal($item['price_for_discount']);
$products[$item['id']]->defaultPriceForDiscount = new ProductPrice($price_for_discount, $defaultCurrency, getVat($item['vat']));
}
}
}
/**
* Multifetch pro nafetchovani informaci okolo skladu / prodejen, ktere se pouzivaji v katalogu, v kosiku atd...
*
* Nafetchuje skladovosti rozdelene podle skladu - jde o centralni sklad, sklad aktualne vybrane prodejny a soucet kusu na ostatnich prodejnach.
*/
public function fetchStoreInfo(ProductCollection $products): void
{
if (!findModule(\Modules::SELLERS) || $products->count() === 0) {
return;
}
static $storeIdBySeller = [];
$sellerContext = Contexts::get(SellerContext::class);
// nacist ID aktivniho skladu podle aktivni prodejny
if (!($storeIdBySeller[$sellerContext->getActiveId()] ?? false)) {
$storeId = sqlQueryBuilder()
->select('id_store')
->from('sellers')
->where(Operator::equals(['id' => $sellerContext->getActiveId()]))
->execute()->fetchOne();
if ($storeId) {
$storeIdBySeller[$sellerContext->getActiveId()] = (int) $storeId;
}
}
$activeStoreId = $storeIdBySeller[$sellerContext->getActiveId()] ?? null;
// nemusim to fetchovat znova, protoze uz to je fetchnuty
if ($products->_pompoStoresFetched[$activeStoreId] ?? false) {
return;
}
// multifetchu informace o skaldech
$products->fetchStoresInStore();
$products->fetchProductOfSuppliersInStore();
$sellersByStoreId = $this->getSellersByStoreId();
// k produktum doplnim potrebne informace
foreach ($products as $product) {
// skladovost na centralnim skladu
$product->inStoreMain = (int) ($product->storesInStore[$this->configuration->getMainStoreId()]['in_store'] ?? 0);
// skladovost u dodavatele
$product->inStoreSupplier = (int) ($product->in_store_suppliers ?? 0);
// skladovost na moji prodejne
$product->inStoreSeller = (int) ($product->storesInStore[$activeStoreId]['in_store'] ?? 0);
// skladovost na ostatnich prodejnach
$inStoreSellerOther = 0;
foreach ($product->storesInStore ?? [] as $storeId => $item) {
// v $sellersByStoreId mam prodejce indexovany podle ID skladu
// zaroven tam jsou odfiltrovany prodejny podle aktivniho jazyku - pro cs jen CZ prodejny a pro sk jen SK prodejny
// takze na CZ shopu nebudeme zobrazovat skladovost SK prodejen, protoze SK prodejna stejne na CZ shopu nejde vybrat
if (!($sellersByStoreId[$storeId] ?? false)) {
continue;
}
$inStoreSellerOther += max(0, $item['in_store']);
}
$product->inStoreSellerOther = (int) $inStoreSellerOther;
}
if (!isset($products->_pompoStoresFetched)) {
$products->_pompoStoresFetched = [];
}
$products->_pompoStoresFetched[$activeStoreId] = true;
}
/**
* Vrací, zda je daný produkt skladem u marketplace dodavatele.
*/
public function isInStoreMarketplace(\Product $product, float $quantity): bool
{
$marketplaceSuppliers = $this->configuration->getMarketplaceSuppliers();
$suppliers = $product->getSuppliers();
foreach ($marketplaceSuppliers as $marketplaceSupplierId => $_) {
if (($suppliers[0][$marketplaceSupplierId] ?? 0) > $quantity) {
return true;
}
}
return false;
}
/**
* Vrátí datum dopravy navýšený a potřebný počet dnů.
*
* @deprecated `External\PompoBundle\Overrides\Order\DeliveryInfo::calculateCollectionDate` is used instead
*/
public function getDeliveryDateIncremented(\DateTime $deliveryDate, array $items, ?\Delivery $delivery = null): \DateTime
{
return $deliveryDate;
}
public function getDeliveryExpeditionDate(\Delivery $delivery): \DateTime
{
// Kontroluju kvuli tomu, zda se stihne odeslat jeste dnes nebo ne
$expeditionDate = new \DateTime();
$timeHours = '13:00:00';
if (!empty($delivery->time_hours)) {
$timeHours = $delivery->time_hours;
}
try {
$hourParts = explode(':', $timeHours);
$maxHoursToday = (new \DateTime())
->setTime((int) ($hourParts[0] ?? 0), (int) ($hourParts[1] ?? 0), (int) ($hourParts[2] ?? 0));
// Pokud se nestihne odeslat jeste dnes, tak navysim datum jeste o jeden den
if ((new \DateTime()) > $maxHoursToday) {
$expeditionDate = DateUtil::calcWorkingDays(1, $expeditionDate);
}
} catch (\Throwable $e) {
}
return $expeditionDate;
}
/**
* Vrací date increment - počet dní o kolik se má navýšit datum doručení.
*
* Date increment se urcuje podle dodavatele, zavrene expedice z centraly nebo podle specialne nastaveneho incrementu pro dany den.
*/
public function getDateIncrement(array $items, ?array $orderedQuantities = [], ?\DateTime $expeditionDate = null): int
{
$increment = 0;
if (empty($items)) {
return $increment;
}
$filterItems = [];
foreach ($items as $key => $_) {
$parts = explode('/', (string) $key);
$filterItems[$parts[0]] = $filterItems[$parts[0]] ?? null;
if ($parts[1] ?? null) {
$filterItems[$parts[0]][] = $parts[1];
}
}
// nactu si produkty
$products = $this->getProducts($filterItems);
// fetchnu si k produktum dodatecne info
$this->fetchProductsSupplierDeliveryDateIncrement($products);
$this->fetchStoreInfo($products);
// projdu produkty
foreach ($products as $key => $product) {
// pokud je to nejhracka, tak nemame prodejny a zajima nas inStore field na produktu, ale pokud
// jsme na pompu, tak nas zajima inStoreMain, kde je skladovost na hlavnim skladu bez prodejen
$inStoreMain = $this->configuration->isNejhracka() ? $product->inStore : ($product->inStoreMain ?? 0);
$tmpIncrement = 0;
// pocet kusu skladem, ktere objednavam
$requiredQuantity = $items[$key] ?? 0;
// pocet kusu, ktere jsou objednane v uz vytvorene objednavce
$orderedQuantity = $orderedQuantities[$key] ?? 0;
// pocet kusu skladem na hlavnim skladu
$inStore = $inStoreMain + min($orderedQuantity, $inStoreMain);
// pocet kusu skladem u dodavatele
$inStoreSuppliers = $product->in_store_suppliers;
// navysit increment podle skladovosti - pokud neni skladem na hlavnim skladu, ale je skladem u dodavatele, tak dorucuji pozdeji
if ($inStore < $requiredQuantity && $inStoreSuppliers > 0) {
$tmpIncrement = (int) ($product->supplierDeliveryDateIncrement ?? 0);
if ($supplierIncrement = $this->getSupplierOrderIncrement($product->_pompoSupplierId ?? self::SUPPLIER_ID)) {
$tmpIncrement += $supplierIncrement;
}
}
$increment = max($increment, $tmpIncrement);
}
$expeditionDate ??= new \DateTime();
// pokud je napr. expedice z centraly v dany den uzavrena, tak musim doruceni navysit
if ($mainStoreIncrement = $this->getMainStoreDeliveryDateIncrement($expeditionDate)) {
$increment += $mainStoreIncrement;
}
// pokud je v dany den nastaven nejaky dodatecny increment (Nastaveni e-shopu, Pompo a "Navýšení data doručení"
if ($dayIncrement = $this->getDeliveryIncrementByDay($expeditionDate)) {
$increment += $dayIncrement;
}
return $increment;
}
/**
* Vrací datumy, kdy je hlavní sklad uzavřen a tímpádem je uzavčena i expedice z něj.
*/
public function getMainStoreClosedDates(): array
{
$dbcfg = \Settings::getDefault();
$from = $dbcfg->datago['mainStoreClosed']['from'] ?? null;
$to = $dbcfg->datago['mainStoreClosed']['to'] ?? null;
if (!empty($from) && !empty($to)) {
try {
$dateFrom = (new \DateTime($from))->setTime(0, 0);
$dateTo = (new \DateTime($to))->setTime(23, 59, 59);
return [$dateFrom, $dateTo];
} catch (\Throwable $e) {
}
}
return [null, null];
}
/**
* Vrátí počet dní, o které je potřeba navýšit datum doručení pokud je expedice z centrály uzavřena.
* Např. kvůli inventuře, svátku, nebo Vánocům.
*/
public function getMainStoreDeliveryDateIncrement(\DateTime $expeditionDate): int
{
$increment = 0;
[$from, $to] = $this->getMainStoreClosedDates();
if (!empty($from) && !empty($to)) {
try {
// Pokud je centralni sklad z nejakyho duvodu uzavren
if ($expeditionDate >= $from && $expeditionDate <= $to) {
// rozdil mezi datumama
$diff = (new \DateTime())->diff($to);
// Ensure the increment is at least 1 day
$increment = max(1, abs($diff->days));
}
} catch (\Throwable $e) {
}
}
return $increment;
}
public function getDeliveryIncrementByDay(\DateTime $expeditionDate): int
{
$dbcfg = \Settings::getDefault();
if ($increment = $dbcfg->pompoSettings['dayDeliveryIncrement'][$expeditionDate->format('N')] ?? 0) {
return (int) $increment;
}
return 0;
}
/**
* Odecte od incrementu nepracovni dny, aby kdyz se zavola DateUtil::calcWorkingDays nebylo datum o ty nepracovni dny posunuty.
*
* TODO: Zpusobilo to problemy s datumem doruceni pred svatkama (4.7.2023). Upravovalo se to pred Vanocema, takze tam to taky delalo nejakej problem. Nasimulovat, projit a vyresit!
*/
private function getDateIncrementWithoutWorkdays(int $increment): int
{
if ($increment <= 0) {
return $increment;
}
foreach (range(1, $increment) as $i) {
$date = (new \DateTime())->add(new \DateInterval('P'.$i.'D'));
if (!DateUtil::isWorkday($date)) {
$increment--;
}
}
return $increment;
}
/**
* Vraci pocet dnu do dalsiho objednavaciho dne od dodavatele.
*/
private function getSupplierOrderIncrement(int $supplierId = self::SUPPLIER_ID): int
{
static $supplierIncrement = [];
if (!($supplierIncrement[$supplierId] ?? false)) {
$supplier = sqlQueryBuilder()
->select('*')
->from('suppliers')
->where(Operator::equals(['id' => $supplierId]))
->execute()->fetchAssociative();
$supplier['data'] = json_decode($supplier['data'] ?? '', true) ?? [];
$today = new \DateTime();
$orderHours = array_filter($supplier['data']['order_hours'] ?? []);
$increment = null;
// zkontrolovat, zda se bude dneska jeste objednavat od dodavatele
if ($todayOrderHour = $orderHours[$today->format('N')] ?? null) {
[$hour, $minute] = explode(':', $todayOrderHour);
$todayOrderDate = (new \DateTime())->setTime((int) $hour, (int) $minute);
// stiham objednat jeste dneska
if ($today < $todayOrderDate) {
$increment = 0;
}
}
if ($increment === null) {
// najit nejblizsi den, kdy se delaji objednavky dodavatelovi
$increment = $this->recursivelyGetClosestDeliveryDateIncrement($supplier['data']['order_hours'] ?? []);
}
$supplierIncrement[$supplierId] = $increment;
}
return $supplierIncrement[$supplierId];
}
/**
* Rekurzivne prochazi objednaci casy nastavene u dodavatele, aby se nasel nejblizsi den, kdy se od dodavatele bude objednavat.
*/
private function recursivelyGetClosestDeliveryDateIncrement(array $orderHours, int $increment = 0, bool $deep = false): int
{
$today = new \DateTime();
$found = false;
foreach ($orderHours as $day => $hour) {
if (!$deep && $day <= $today->format('N')) {
continue;
}
// if is weekday and hour is empty, skip
// weekday should not be added to increment, because we are adding only working days
if (in_array($day, [6, 7]) && empty($hour)) {
continue;
}
$increment++;
if (!empty($hour)) {
$found = true;
break;
}
}
if (!$found) {
if ($deep) {
return 0;
}
$increment = $this->recursivelyGetClosestDeliveryDateIncrement($orderHours, $increment, true);
}
return $increment;
}
/**
* Nafetchuje k produktum increment podle zaznamu v products of suppliers - kazdy produkt muze byt totiz delivery increment jeste povyseny.
*/
public function fetchProductsSupplierDeliveryDateIncrement(ProductCollection $products): ProductCollection
{
if ($products->count() === 0) {
return $products;
}
if (isset($products->_pompoSupplierDeliveryDateIncrementFetched)) {
return $products;
}
$qb = sqlQueryBuilder()
->select('pos.id_supplier, pos.id_product, pos.id_variation, MIN(COALESCE(pos.note, COALESCE(IF(JSON_VALUE(data, \'$.delivery_time\')=\'\', 4, JSON_VALUE(data, \'$.delivery_time\')), 4))) as note')
->from('products_of_suppliers', 'pos')
->join('pos', 'suppliers', 's', 's.id = pos.id_supplier')
->andWhere('pos.in_store > 0')
->groupBy('pos.id_product, pos.id_variation');
$specs = [];
foreach ($products as $key => $product) {
$id = explode('/', (string) $key);
$productId = (int) $id[0];
$variationId = null;
if (!empty($id[1])) {
$variationId = (int) $id[1];
}
$specs[] = Operator::equalsNullable(['id_product' => $productId, 'id_variation' => $variationId]);
}
$qb->andWhere(Operator::orX($specs));
foreach ($qb->execute() as $item) {
$key = $item['id_product'];
if ($item['id_variation']) {
$key .= '/'.$item['id_variation'];
}
if ($products[$key] ?? false) {
$products[$key]->_pompoSupplierId = $item['id_supplier'] ?? null;
$products[$key]->supplierDeliveryDateIncrement = !empty($item['note']) ? (int) $item['note'] : null;
}
}
$products->_pompoSupplierDeliveryDateIncrementFetched = true;
return $products;
}
/**
* Nastaví produktům stitek podle podmínek.
*
* Bezva cena - DMOC sleva >= 16
* Cenový hit - DMOC sleva >= 36
*/
public function generateProductDiscountLabels(): void
{
// Bezva cena
$labelIdBC = $this->labelUtil->getLabelIdByCode('BC');
// Cenový hit
$labelIdCH = $this->labelUtil->getLabelIdByCode('CH');
if (!$labelIdBC || !$labelIdCH) {
return;
}
$productList = clone $this->productList;
$productList->fetchProductOfSuppliersInStore();
$productList->applyDefaultFilterParams();
$productList->addResultModifiers(fn (ProductCollection $products) => $this->fetchAdditionalPrices($products));
$batchSize = 500;
$updateData = [
$labelIdBC => [],
$labelIdCH => [],
];
// projdu produkty abych nasel ty, kterym potrebuju nastavit kampan
$iterationLimit = $productList->getProductsCount() / $batchSize;
for ($i = 0; $i < $iterationLimit; $i++) {
$productList->limit($batchSize, $i * $batchSize);
foreach ($productList->getProducts() as $product) {
if (!isset($product->dmocDiscount)) {
continue;
}
if (!$product->dmocDiscount->isPositive()) {
continue;
}
$discount = $product->dmocDiscount->asFloat();
// Cenovy hit
if ($discount >= 36) {
$updateData[$labelIdCH][] = $product->id;
// Bezva cena
} elseif ($discount >= 16) {
$updateData[$labelIdBC][] = $product->id;
}
}
}
foreach ($updateData as $labelId => $data) {
// smazu vygenerovane stitky od produktu
sqlQueryBuilder()
->delete('product_labels_relation')
->where(Operator::equals(['id_label' => $labelId]))
->execute();
$baseInsertQb = sqlQueryBuilder()
->insert('product_labels_relation')
->onDuplicateKeyUpdate(['id_label', 'id_product']);
// nastavim stitek k produktum
foreach (array_chunk($data, 500) as $products) {
$qb = clone $baseInsertQb;
foreach ($products as $productId) {
$qb->multiDirectValues(['id_label' => $labelId, 'id_product' => $productId, 'generated' => 1]);
}
$qb->execute();
}
}
}
public function updateProductsVIPPriceList(): void
{
$selectQb = sqlQueryBuilder()
->select($this->configuration->getVIPPriceListId(), 'p.id', 'ROUND(p.price_buy * 1.05, 4) as vip_price')
->from('products', 'p')
->where('price_buy IS NOT NULL AND price_buy > 0');
sqlQuery("INSERT INTO pricelists_products (id_pricelist, id_product, price)
{$selectQb->getSQL()}
ON DUPLICATE KEY UPDATE price = VALUES(price);");
}
/**
* Nastaví produktům flagy podle podmínek.
*
* Bezva cena - DMOC sleva >= 16
* Cenový hit - DMOC sleva >= 36
*
* @depracated Delete me when labels are fully used instead of campaigns
*/
public function generateProductDiscountFlags(): void
{
$campaigns = getCampaigns();
if (!isset($campaigns['BC']) || !isset($campaigns['CH'])) {
return;
}
$productList = clone $this->productList;
$productList->fetchProductOfSuppliersInStore();
$productList->applyDefaultFilterParams();
$productList->addResultModifiers(fn (ProductCollection $products) => $this->fetchAdditionalPrices($products));
$batchSize = 500;
$updateData = [
'BC' => [],
'CH' => [],
];
// projdu produkty abych nasel ty, kterym potrebuju nastavit kampan
$iterationLimit = $productList->getProductsCount() / $batchSize;
for ($i = 0; $i < $iterationLimit; $i++) {
$productList->limit($batchSize, $i * $batchSize);
foreach ($productList->getProducts() as $product) {
if (!isset($product->dmocDiscount)) {
continue;
}
if (!$product->dmocDiscount->isPositive()) {
continue;
}
$discount = $product->dmocDiscount->asFloat();
// Cenovy hit
if ($discount >= 36) {
$updateData['CH'][] = $product->id;
// Bezva cena
} elseif ($discount >= 16) {
$updateData['BC'][] = $product->id;
}
}
}
foreach ($updateData as $campaign => $data) {
// smazu kampan od produktu
sqlQueryBuilder()
->update('products')
->set('campaign', 'REMOVE_FROM_SET(:campaign, campaign)')
->where(Operator::findInSet([$campaign], 'campaign'))
->setParameter('campaign', $campaign)
->execute();
// nastavim kampan k produktum
foreach (array_chunk($data, 500) as $products) {
sqlQueryBuilder()
->update('products')
->set('campaign', 'ADD_TO_SET(:campaign, campaign)')
->where(Operator::inIntArray(array_values($products), 'id'))
->setParameter('campaign', $campaign)
->execute();
}
}
}
/**
* Vrací prodejce indexované podle ID skladu.
*/
private function getSellersByStoreId(): array
{
return Mapping::mapKeys(
Contexts::get(SellerContext::class)->getSupported(),
function ($k, $v) {
return [$v['id_store'], $v];
}
);
}
/**
* Vrací `ProductCollection` podle zadaných ID produktů.
*/
private function getProducts(array $products): ProductCollection
{
static $productsCache = [];
$cacheKey = md5(serialize($products));
if (!($productsCache[$cacheKey] ?? false)) {
$productList = clone $this->productList;
$productList->setVariationsAsResult(true);
$collection = $productList->andSpec(Product::productsAndVariationsIds($products))
->getProducts();
$collection->fetchProductOfSuppliersInStore();
$productsCache[$cacheKey] = $collection;
}
return $productsCache[$cacheKey];
}
}