variationsAsResult = $variationsAsResult; $this->multiFetch = ServiceContainer::getService(MultiFetch::class); $this->addResultModifiers(fn (ProductCollection $products) => $this->multiFetch->fetchDeliveryText($products)); } public function fetchSets($showOutOfStock = null) { $this->resultModifiers[] = function (ProductCollection $products) use ($showOutOfStock) { $this->multiFetch->fetchSets($products, $showOutOfStock); }; return $this; } public function fetchStoresInStore(bool $frontedQuantity = true): ProductListBase { if (findModule(Modules::STORES)) { $this->resultModifiers[] = function (ProductCollection $products) use ($frontedQuantity) { $this->multiFetch->fetchStoresInStore($products, $this->variationsAsResult, frontedQuantity: $frontedQuantity); }; } return $this; } public function fetchPriceLevelPrice($priceLevelID): ProductListBase { if (findModule(Modules::PRICE_LEVELS)) { $this->resultModifiers[] = function (ProductCollection $products) use ($priceLevelID) { $this->multiFetch->fetchPriceLevelPrice($products, $priceLevelID); }; } return $this; } /** Create default FilterParams and attach it to ProductList. * @return FilterParams */ public function applyDefaultFilterParams() { $filterParams = \FilterParams::createDefault($this->variationsAsResult ? \FilterParams::ENTITY_VARIATION : \FilterParams::ENTITY_PRODUCT); $this->andSpec(function () use ($filterParams) { return $filterParams->getSpec(); }); return $filterParams; } public function fetchImages($mainSize, $imageKind = null, bool $fallbackToProductPhoto = false) { $this->andSpec(function (Query\QueryBuilder $qb) use ($imageKind, $fallbackToProductPhoto) { $qb->addSelect(\Query\Product::withProductPhotoId($this->variationsAsResult, $imageKind, $fallbackToProductPhoto)); }); $this->resultModifiers[] = function (ProductCollection $products) use ($mainSize) { /** @var Product $product */ foreach ($products as $product) { $product->fetchImages($mainSize); } }; return $this; } public function fetchOtherImages($size, $type = null) { $this->resultModifiers[] = function (ProductCollection $products) use ($size, $type) { $this->multiFetch->fetchOtherImages($products, $size, $type); }; return $this; } public function fetchVariations($labels, bool $filterOnlyVisible = true) { $this->resultModifiers[] = function (ProductCollection $products) use ($labels, $filterOnlyVisible) { $this->multiFetch->fetchVariations($products, $labels, $filterOnlyVisible); }; return $this; } public function fetchVariationsEntities(bool $filterOnlyVisible = true) { $this->resultModifiers[] = function (ProductCollection $products) use ($filterOnlyVisible) { $this->multiFetch->fetchVariationsEntities($products, $filterOnlyVisible); }; return $this; } public function fetchParameters($parameters = null) { $this->resultModifiers[] = function (ProductCollection $products) use ($parameters) { $this->multiFetch->fetchParameters($products, $parameters); }; return $this; } public function fetchProductOfSuppliersInStore() { $this->resultModifiers[] = function (ProductCollection $products) { $this->multiFetch->fetchProductOfSuppliersInStore($products); }; return $this; } public function fetchSections() { $this->resultModifiers[] = function (ProductCollection $products) { $this->multiFetch->fetchSections($products); }; return $this; } public function fetchActiveSellerInStore() { $this->resultModifiers[] = function (ProductCollection $products) { $this->multiFetch->fetchActiveSellerInStore($products); }; return $this; } public function fetchProductLabels(?string $visibility = null) { $this->resultModifiers[] = function (ProductCollection $products) use ($visibility) { $this->multiFetch->fetchProductLabels($products, $visibility); }; return $this; } public function fetchCollections() { $this->resultModifiers[] = function (ProductCollection $products) { /* @var Product $product */ $this->multiFetch->fetchCollections($products); }; return $this; } public function fetchProductsRelated() { $this->resultModifiers[] = function (ProductCollection $products) { $this->multiFetch->fetchProductsRelated($products); }; } public function fetchProducers() { $this->resultModifiers[] = function (ProductCollection $products) { $this->multiFetch->fetchProducers($products); }; return $this; } public function fetchReviews() { $this->resultModifiers[] = function (ProductCollection $products) { $this->multiFetch->fetchRating($products); }; } public function fetchPhotos($size, $type = null) { $this->resultModifiers[] = function (ProductCollection $products) use ($type, $size) { $this->multiFetch->fetchOtherImages($products, $size, $type); }; } public function fetchLinks() { $this->resultModifiers[] = function (ProductCollection $products) { $this->multiFetch->fetchLinks($products); }; return $this; } public function fetchCharges() { $this->resultModifiers[] = function (ProductCollection $products) { $this->multiFetch->fetchProductsCharges($products); }; return $this; } public function fetchDescriptionPlus(): ProductListBase { $this->resultModifiers[] = function (ProductCollection $products) { $this->multiFetch->fetchDescriptionPlus($products); }; return $this; } public function fetchAttachments(): ProductListBase { $this->resultModifiers[] = function (ProductCollection $products) { $this->multiFetch->fetchAttachments($products); }; return $this; } public function fetchVariationImages(int $size, ?array $type = null): self { $this->resultModifiers[] = function (ProductCollection $products) use ($size, $type) { $this->multiFetch->fetchVariationImages($products, $size, $type); }; return $this; } public function fetchConvertorsValues($ids = [], bool $showHiddenVariations = false) { if (findModule(Modules::CONVERTORS)) { $this->resultModifiers[] = function (ProductCollection $products) use ($ids, $showHiddenVariations) { $this->multiFetch->fetchConvertorsValues($products, $ids, showHiddenVariations: $showHiddenVariations); }; } } public function fetchMeasureUnits() { if (findModule(Modules::PRODUCTS, Modules::SUB_UNITS)) { $this->resultModifiers[] = function (ProductCollection $products) { $this->multiFetch->fetchMeasureUnits($products); }; } } public function orderBy($sort, $order = null) { $this->andSpec(function (Query\QueryBuilder $qb) use ($sort, $order) { $qb->orderBy($sort, $order); }); return $this; } public function limit($count, $offset = null) { $this->andSpec(function (Query\QueryBuilder $qb) use ($count, $offset) { if (isset($offset)) { $qb->setFirstResult($offset); } $qb->setMaxResults($count); }); return $this; } /** * @param int $totalCount if provided, $totalCount will be set to total count of found rows limit applied * * @return ProductCollection */ public function getProducts(&$totalCount = null) { $useTotalCount = count(func_get_args()); $products = []; $dataAll = []; $qb = $this->createQueryBuilder($useTotalCount); $result = $qb->execute(); if ($useTotalCount) { $totalCount = (int) sqlFetchAssoc(sqlQuery('SELECT FOUND_ROWS() as total_count'))['total_count']; } if (!$this->variationsAsResult) { $result = sqlFetchAll($result, 'id'); $variations = array_filter($result, function ($p) { return !empty($p['variationsIds']); }); if ($variations) { $minVariations = sqlQueryBuilder()->select('pv.id_product') ->fromProducts() ->joinVariationsOnProducts() ->andWhere(Operator::inIntArray(array_keys($variations), 'p.id')); foreach ($this->getVariationSelectFields() as $alias => $expr) { $minVariations->addSelect("{$expr} as `{$alias}`"); } $usePriceListPrice = false; if (findModule(Modules::PRICELISTS)) { $pricelistContext = Contexts::get(PricelistContext::class); if ($pricelist = $pricelistContext->getActiveId()) { $usePriceListPrice = true; // add pricelist price $minVariations->andWhere(\KupShop\PricelistBundle\Query\Product::applyPricelistOnVariation($pricelist)); } } $varSpecs = []; foreach ($variations as $id_product => $product) { // Pokud je aktivní ceník, tak je potřeba brát všechny varianty i s různou cenou. Minimální cenu zajistí applyMinVariationsOrderBy a následný GROUP BY p.id if ($usePriceListPrice) { $varSpecs[] = Operator::andX( "pv.id IN ({$product['variationsIds']})", 'pv.price IS NOT NULL'); } else { $varSpecs[] = Operator::andX( "pv.id IN ({$product['variationsIds']})", "pv.price = {$product['price']}"); } } $minVariations->andWhere(Operator::orX($varSpecs)); $this->applyMinVariationsOrderBy($minVariations); $variations = $minVariations->execute()->fetchAllAssociativeIndexed(); foreach ($variations as $id_product => $variation) { $result[$id_product]['minVariation'] = $variation; // $variation = array_filter($variation); unset($variation['bonus_points']); $result[$id_product] = array_replace($result[$id_product], $variation); } } } foreach ($result as $row) { if ($this->variationsAsResult && $row['id_variation'] !== null) { $product = new Variation(); } else { $product = new Product(); } $product->createFromArray($row); $id = $product->id; if ($product instanceof Variation) { $id .= '/'.$product->variationId; } $products[$id] = $product; $dataAll[$id] = $row; } $productCollection = (new ProductCollection($products)) ->setEntityType($this->variationsAsResult ? FilterParams::ENTITY_VARIATION : FilterParams::ENTITY_PRODUCT); foreach ($this->resultModifiers as $callback) { $callback($productCollection, $dataAll); } return $productCollection; } public function getProductsCount() { $count = $this->variationsAsResult ? '*' : 'distinct p.id'; $query = $this->createQueryBuilder(false) ->select("count({$count}) c") ->resetQueryPart('groupBy'); return (int) $query->execute()->fetch()['c']; } public function andSpec(?callable $spec = null) { $this->specs[] = $spec; return $this; } public function getVariationSelectFields($qb = null) { $fields = [ 'id_variation' => 'pv.id', 'variation_title' => 'pv.title', 'price' => 'pv.price', 'matched_id_variation' => 'pv.id', 'variationVisible' => 'pv.figure', 'variationData' => 'pv.data', ]; if (findModule(\Modules::PRICE_HISTORY)) { $fields['price_for_discount'] = 'COALESCE(pv.price_for_discount, p.price_for_discount)'; } if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_COMMON)) { $fields['price_common'] = 'COALESCE(pv.price_common, p.price_common)'; } if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)) { $fields['price_buy'] = 'COALESCE(pv.price_buy, p.price_buy)'; } if (findModule(\Modules::PRODUCTS, \Modules::SUB_WEIGHT)) { $fields['weight'] = 'COALESCE(pv.weight, p.weight)'; } if (findModule(\Modules::BONUS_PROGRAM)) { $fields['bonus_points'] = 'COALESCE(pv.bonus_points, p.bonus_points)'; } return $fields; } public function getSelectFields($qb = null) { $fields = [ 'id' => 'p.id', 'show_in_search' => 'p.show_in_search', 'id_variation' => 'pv.id', 'variation_title' => 'pv.title', 'code' => 'p.code', 'productCode' => 'p.code', 'in_store' => \Query\Product::getInStoreField($this->variationsAsResult, $qb), 'price' => 'MIN(COALESCE(pv.price, p.price))', 'priceMax' => 'MAX(COALESCE(pv.price, p.price))', 'priceRaw' => 'p.price', 'price_common' => $this->variationsAsResult ? 'COALESCE(pv.price_common, p.price_common)' : 'p.price_common', 'discount' => 'p.discount', 'delivery_time' => $this->variationsAsResult ? 'COALESCE(pv.delivery_time, p.delivery_time)' : 'p.delivery_time', 'guarantee' => 'p.guarantee', 'campaign' => 'p.campaign', 'ean' => $this->variationsAsResult ? 'COALESCE(pv.ean, p.ean)' : 'p.ean', 'producer' => 'p.producer', 'matched_id_variation' => 'pv.id', 'figure' => $this->variationsAsResult ? 'pv.figure' : 'p.figure', 'data' => 'p.data', 'variationVisible' => 'pv.figure', 'variationData' => 'pv.data', 'width' => $this->variationsAsResult ? 'COALESCE(pv.width, p.width)' : 'p.width', 'height' => $this->variationsAsResult ? 'COALESCE(pv.height, p.height)' : 'p.height', 'depth' => $this->variationsAsResult ? 'COALESCE(pv.depth, p.depth)' : 'p.depth', 'parameters' => 'p.parameters', 'variationsIds' => 'GROUP_CONCAT(DISTINCT pv.id)', 'has_variations' => '(pv.id IS NOT NULL)', 'date_added' => $this->variationsAsResult ? 'COALESCE(pv.date_added, p.date_added)' : 'p.date_added', 'meta_title' => 'p.meta_title', 'meta_description' => 'p.meta_description', 'meta_keywords' => 'p.meta_keywords', ]; if (findModule(Modules::PRODUCTS_VARIATIONS, Modules::SUB_CODE)) { $fields['variationCode'] = 'pv.code'; } if (findModule(Modules::PRODUCTS, Modules::SUB_SHOW_MAX)) { $field = \Query\Product::getInStoreField($this->variationsAsResult); $fields['in_store'] = 'LEAST('.$field.', COALESCE(p.in_store_show_max, '.findModule('products', 'showMax').'))'; } if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)) { $fields['price_buy'] = $this->variationsAsResult ? 'COALESCE(pv.price_buy, p.price_buy)' : 'p.price_buy'; } if (findModule(\Modules::PRODUCTS, \Modules::SUB_WEIGHT)) { $fields['weight'] = $this->variationsAsResult ? 'COALESCE(pv.weight, p.weight)' : 'p.weight'; } if (findModule(\Modules::BONUS_PROGRAM)) { $fields['bonus_points'] = $this->variationsAsResult ? 'COALESCE(pv.bonus_points, p.bonus_points)' : 'p.bonus_points'; } if (findModule(Modules::PRODUCTS, Modules::SUB_UNITS)) { $fields['unit'] = 'p.unit'; $fields['short_name'] = 'pu.short_name'; $fields['short_name_admin'] = 'pu.short_name_admin'; $fields['unit_long_name'] = 'pu.long_name'; $fields['recalculate_to'] = 'pu2.recalculate_to'; $fields['measure_quantity'] = 'p.measure_quantity'; } if (findModule(Modules::PRODUCTS, Modules::SUB_DESCR_PLUS)) { $fields['id_block'] = 'p.id_block'; } if (findModule(\Modules::OSS_VATS)) { $fields['id_cn'] = 'p.id_cn'; } if (findModule(\Modules::PRICE_HISTORY)) { $fields['price_for_discount'] = 'MIN(COALESCE(pv.price_for_discount, p.price_for_discount))'; } if (findModule(Modules::STOCK_IN)) { $fields['date_stock_in'] = 'p.date_stock_in'; } if (findModule(Modules::PRODUCTS_SERIAL_NUMBERS)) { $fields['serial_number_require'] = 'p.serial_number_require'; } return $fields; } public function getQueryBuilder() { return $this->createQueryBuilder(null); } /** * @return Query\QueryBuilder */ protected function createQueryBuilder($useTotalCount) { /** @var \Query\QueryBuilder $query */ $query = sqlQueryBuilder() ->fromProducts() ->joinVariationsOnProducts() ->where(Operator::not(Operator::equals(['p.id' => 0]))) // Kvuli produktu pro pokladnu s ID 0 ->groupBy('p.id'); $selectedFields = $this->getSelectFields($query); // Jinak to do qb nacpat nejde. $selectedFields['id'] = 'SQL_CALC_FOUND_ROWS '.$selectedFields['id']; foreach ($selectedFields as $alias => $expr) { $query->addSelect("{$expr} as `{$alias}`"); } if ($this->variationsAsResult) { $query->addGroupBy('pv.id'); } // add pricelist price if (findModule(Modules::PRICELISTS)) { $pricelistContext = ServiceContainer::getService(PricelistContext::class); if ($pricelistContext->getActiveId()) { $query->andWhere( \KupShop\PricelistBundle\Query\Product::applyPricelist($pricelistContext->getActiveId(), null, true) ); } } $query->andWhere( Query\Translation::coalesceTranslatedFields( \KupShop\I18nBundle\Translations\ProductsTranslation::class, ['title', 'short_descr', 'long_descr', 'parameters', 'figure', 'meta_title', 'meta_description'] ) ); if (findModule(Modules::PRODUCTS_VARIATIONS) && $this->variationsAsResult) { $query->andWhere( Query\Translation::coalesceTranslatedFields( \KupShop\I18nBundle\Translations\VariationsTranslation::class, ['title' => 'variation_title'] ) ); } if (findModule(Modules::PRODUCTS, Modules::SUB_UNITS)) { $query->leftJoin('p', 'products_units', 'pu', 'p.unit=pu.id'); $query->andWhere( Query\Translation::coalesceTranslatedFields( \KupShop\I18nBundle\Translations\ProductsUnitsTranslation::class ) ); $query->leftJoin('p', 'products_units', 'pu2', 'p.measure_unit=pu2.id') ->addSelect('pu2.short_name as measure_unit_name, pu2.recalculate_to'); } if (findModule(Modules::PRODUCTS, Modules::SUB_UNITS_FLOAT)) { $query->addSelect('pu.pieces_precision'); } $query->addSelect(\Query\Product::withVat()); $query->andWhere(Operator::andX($this->specs)); return $query; } /** * @return $this */ public function setVariationsAsResult($variationsAsResult) { $this->variationsAsResult = $variationsAsResult; return $this; } /** * @return bool */ public function getVariationsAsResult() { return $this->variationsAsResult; } public function addResultModifiers(callable $resultModifier, $prepend = false): ProductListBase { if ($prepend) { array_unshift($this->resultModifiers, $resultModifier); } else { $this->resultModifiers[] = $resultModifier; } return $this; } protected function applyMinVariationsOrderBy(Query\QueryBuilder $qb): void { if (findModule(\Modules::PRICE_HISTORY)) { $qb->orderBy('COALESCE(pv.price_for_discount, p.price_for_discount)', 'ASC'); } if (findModule(Modules::PRICELISTS)) { $pricelistContext = Contexts::get(PricelistContext::class); if ($pricelistContext->getActiveId()) { $qb->orderBy('COALESCE(prlv.price, prlp.price, pv.price, p.price) * ((100 - COALESCE(prlv.discount, prlp.discount, p.discount))/100)', 'DESC'); } } } } if (empty($subclass)) { class ProductList extends ProductListBase { } }