select(static::$variationFields) ->from('products_variations', 'pv') ->join('pv', 'products', 'p', 'p.id = pv.id_product') ->andWhere('pv.id_product = :id_product')->setParameter('id_product', $id_product); } public static function getProductVariations($id_product, $in_store = false, $figure = true) { global $cfg; $qb = static::getProductVariationsQueryBuilder($id_product); // TODO prepsat na ProductList a nebude toto potreba $inStoreField = \Query\Product::getInStoreField(true, $qb); if (findModule('products', 'showMax')) { $qb->addSelect('LEAST('.$inStoreField.', COALESCE(pv.in_store_show_max, p.in_store_show_max, '.$cfg['Modules']['products']['showMax'].')) in_store'); } else { $qb->addSelect($inStoreField.' as in_store'); } if (findModule('products_variations', 'variationCode')) { $qb->addSelect('pv.code'); } if (findModule('products', 'price_buy')) { $qb->addSelect('pv.price_buy'); } if (findModule('products', 'price_common')) { $qb->addSelect('COALESCE(pv.price_common, p.price_common) price_common'); } if (findModule('products', 'weight')) { $qb->addSelect('COALESCE(pv.weight, p.weight) weight'); } if ($figure) { $qb->andWhere(\Query\Variation::isVisible()); } if ($in_store) { $qb->andWhere($inStoreField.' > 0'); } if (findModule(\Modules::BONUS_PROGRAM)) { $qb->addSelect('COALESCE(pv.bonus_points, p.bonus_points) as bonus_points'); } if (findModule(\Modules::PRICE_HISTORY)) { $qb->addSelect('COALESCE(pv.price_for_discount, p.price_for_discount) as price_for_discount'); } $id_product = intval($id_product); $variations = []; $SQL = sqlQueryBuilder()->select('id_label')->from('products_variations_choices_categorization') ->where('id_product=:id_product')->setParameter('id_product', $id_product) ->orderBy('list_order', 'ASC')->execute(); if (sqlNumRows($SQL) <= 0) { return $variations; } $index = 0; while (($row = sqlFetchAssoc($SQL)) !== false) { $id = $row['id_label']; $qb->leftJoin('pv', 'products_variations_combination', 'pvc'.$index, 'pv.id = pvc'.$index.'.id_variation AND pvc'.$index.'.id_label='.$id.'') ->leftJoin('pvc'.$index, 'products_variations_choices_values', 'pvcv'.$index, 'pvcv'.$index.'.id=pvc'.$index.'.id_value') ->addOrderBy('pvcv'.$index.'.sort', 'ASC'); $index++; } // join pricelists if (findModule(Modules::PRICELISTS)) { $pricelistContext = ServiceContainer::getService(\KupShop\PricelistBundle\Context\PricelistContext::class); if ($pricelistContext->getActiveId()) { $qb->andWhere(\KupShop\PricelistBundle\Query\Product::applyPricelist($pricelistContext->getActiveId())); } } $qb->andWhere(Translation::coalesceTranslatedFields(VariationsTranslation::class)); $SQL = $qb->execute(); $found = sqlNumRows($SQL); $qbProduct = sqlQueryBuilder()->select('p.discount') ->from('products', 'p') ->addSelect(\Query\Product::withVat()) ->andWhere('p.id = :id_product')->setParameter('id_product', $id_product) ->execute(); $product = sqlFetchAssoc($qbProduct); if ($found > 0) { $COMBINATIONS = self::getProductCombinations($id_product); } // Variations photos $varPhotos = []; if (findModule('products_variations_photos')) { $varPhotosQb = sqlQueryBuilder() ->select('pp.id_variation, ph.id, ph.date_update, pp.show_in_lead') ->from('photos_products_relation', 'pp') ->join('pp', 'photos', 'ph', 'ph.id = pp.id_photo') ->where(Operator::equalsNullable(['pp.id_product' => $id_product, 'pp.id_variation IS NOT NULL'])); if (findModule(Modules::VIDEOS)) { $varPhotosQb->addSelect('id_cdn id_video') ->leftJoin('ph', 'videos', 'v', 'v.id_photo = ph.id'); } foreach ($varPhotosQb->execute() as $photo) { $idVar = $photo['id_variation']; if (empty($varPhotos[$idVar])) { $varPhotos[$idVar] = []; } $varPhotos[$idVar][] = array_merge(getImage($photo['id'], null, null, 'product_detail', '', strtotime($photo['date_update'])), [ 'id_photo' => $photo['id'], 'id_video' => $photo['id_video'] ?? null, 'show_in_lead' => $photo['show_in_lead'], ]); } } foreach ($SQL as $row) { $id_variation = $row['id']; $row['discount'] = $product['discount']; $row['original_vat'] = $product['original_vat'] ?? $product['vat']; if (!empty($row['ean'])) { $row['ean'] = formatEAN($row['ean']); } $currencyContext = ServiceContainer::getService(\KupShop\KupShopBundle\Context\CurrencyContext::class); $priceForDiscountCurrency = $currencyContext->getDefault(); // create productPrice $row['productPrice'] = new ProductPrice( toDecimal($row['price']), $currencyContext->getDefault(), getVat($product['vat']), $product['discount'] ); // set pricelist price if (findModule(Modules::PRICELISTS) && isset($row['pricelist_price'])) { $priceConverter = ServiceContainer::getService(\KupShop\I18nBundle\Util\PriceConverter::class); $row['pricelist_currency'] = $currencyContext->getOrDefault($row['pricelist_currency']); /** @var \KupShop\PricelistBundle\Entity\Pricelist $activePricelist */ $activePricelist = Contexts::get(PricelistContext::class)->getActive(); $row['priceOriginal'] = $row['price']; $row['price'] = $row['pricelist_price']; $extend = $activePricelist?->getUseProductDiscount(); if (($extend && $activePricelist && isset($row['pricelist_discount'])) || (!$extend && $activePricelist)) { $row['discount'] = $row['pricelist_discount']; } else { $row['discount'] = $product['discount']; } // price convert to CZK $originalProductPrice = $row['productPrice']; // replace productPrice with PriceListPrice $row['productPrice'] = new \KupShop\PricelistBundle\Util\Price\PriceListPrice( toDecimal($row['price']), $row['pricelist_currency'], getVat($product['vat']), $row['discount'] ); $row['productPrice']->setOriginalPrice($originalProductPrice); $coefficient = $activePricelist?->getCoefficient(); if ($coefficient && in_array($row['price_source'], ['p', 'pv'])) { /* v pripade koeficientu na ceniku je aplikovan i na originalPrice */ $row['productPrice']->applyCoefficient(toDecimal($coefficient)); if (!empty($row['price_for_discount'])) { $row['price_for_discount'] *= $coefficient; } } $row['price'] = $priceConverter->convert($row['pricelist_currency'], $currencyContext->getDefaultId(), $row['price']); // prerazim CPS od produktu CPS z ceniku, pokud ji mam dostupnou if (!empty($row['pricelist_price_history']) && array_key_exists('pricelist_price_for_discount', $row)) { $row['price_for_discount'] = $row['pricelist_price_for_discount']; $priceForDiscountCurrency = $row['pricelist_currency']; } } $row['price_array'] = PriceWrapper::wrap($row['productPrice']); self::getData($row); $variation = $row; if (findModule(Modules::PRICELISTS) && isset($row['pricelist_price']) && isset($row['priceOriginal'])) { $priceOriginal = new \KupShop\KupShopBundle\Util\Price\Price(toDecimal($row['priceOriginal']), $currencyContext->getDefault(), getVat($product['vat'])); $variation['priceOriginal'] = PriceWrapper::wrap($priceOriginal); } $variation['price'] = formatCustomerPrice($variation['price'], $row['discount'], getVat($product['vat']), $id_product); if (array_key_exists('price_buy', $variation)) { $variation['price_buy'] = formatPrice(applyCurrency($variation['price_buy']), getVat($product['vat'])); } if (array_key_exists('price_for_discount', $variation)) { $price_for_discount = toDecimal($variation['price_for_discount']); $price_for_discount = new ProductPrice($price_for_discount, $priceForDiscountCurrency, $variation['productPrice']->getVat()); $variation['price_for_discount'] = PriceWrapper::wrap($price_for_discount); } if (array_key_exists('price_common', $variation) && ($variation['price_common'] > 0)) { $original_vat = $product['original_vat'] ?? $product['vat']; $price_common = toDecimal($variation['price_common'])->removeVat(getVat($original_vat)); $price_common = new ProductPrice($price_common, $currencyContext->getDefault(), $variation['productPrice']->getVat()); $variation['priceCommon'] = PriceWrapper::wrap($price_common); } // replace productPrice with PriceLevelPrice if ($priceLevel = Contexts::get(PriceLevelContext::class)->getActive()) { $variation['productPrice'] = new PriceLevelPrice($row['productPrice']); $priceLevelDiscount = $priceLevel->getDiscount($id_product, null, -1, $product); $variation['productPrice']->setPricelevelDiscount($priceLevelDiscount); } if (findModule(Modules::BONUS_PROGRAM)) { $bonus_points = (is_null($row['bonus_points']) ? null : toDecimal($row['bonus_points'])); $variation['bonus_points'] = $bonus_points; } $variation['combinations'] = $COMBINATIONS[$id_variation]; // wrap productPrice - cuz of templates $variation['productPrice'] = PriceWrapper::wrap($variation['productPrice']); Variations::prepareDeliveryText($id_product, $variation); $variation['in_store'] = max(0, floatval($variation['in_store'])); if (findModule('products_variations_photos')) { if (!empty($varPhotos[$id_variation])) { $variation['photos'] = $varPhotos[$id_variation]; } else { $variation['photos'] = false; } } static::recalculatePricesVatFromPriceWithVat($variation); $variations[$id_variation] = $variation; } sqlFreeResult($SQL); return $variations; } public static function recalculatePricesVatFromPriceWithVat(array &$variation): void { if (!PriceUtil::isProductPricesVatFromTop()) { return; } $fields = ['priceOriginal', 'productPrice', 'price_for_discount']; $originalVatId = $variation['original_vat'] ?? null; $originalVat = (float) getVat($originalVatId); $vatContext = Contexts::get(VatContext::class); if ($vatContext->getActive() == $vatContext::NO_VAT) { $originalVat = $vatContext->getVat($originalVatId)['vat']; } foreach ($fields as $field) { if (!isset($variation[$field])) { continue; } // PriceLevelPrice se uvnitr pocita specialne :/ if ($variation[$field] instanceof PriceLevelPrice || ($variation[$field] instanceof PriceWrapper && $variation[$field]->getObject() instanceof PriceLevelPrice)) { $variation[$field]->setOriginalPrice( PriceUtil::recalculatePriceVatFromPriceWithVat( $variation[$field]->getOriginalPrice(), $originalVat, $variation['vat'] ?? null ) ); } // prepocitam cenu tak, aby DPH bylo pocitano ze shora $variation[$field] = PriceUtil::recalculatePriceVatFromPriceWithVat($variation[$field], $originalVat, $variation['vat'] ?? null); } $variation['price_array'] = $variation['productPrice']; } /** * @return array|mixed */ public static function getData(&$variation) { if (empty($variation['data'])) { $variation['data'] = []; } else { $variation['data'] = json_decode($variation['data'], true); } } public static function getProductLabels($id_product) { $qb = sqlQueryBuilder() ->select('pvcl.id, pvcl.label') ->from('products_variations_choices_categorization', 'pvcc') ->join('pvcc', 'products_variations_choices_labels', 'pvcl', 'pvcc.id_label=pvcl.id') ->where(Operator::equals(['pvcc.id_product' => $id_product])) ->orderBy('pvcc.list_order'); $qb->andWhere(Translation::coalesceTranslatedFields(VariationsLabelsTranslation::class)); $productLabels = $qb->execute() ->fetchAll(); $labels = []; foreach ($productLabels as $label) { $iter = $label['id']; $labels[$iter] = [ 'id' => $iter, 'label' => $label['label'], 'values' => [], 'codes' => [], ]; $qb = sqlQueryBuilder() ->select('pvcv.id, pvcv.value, pvcv.code, pvcv.data') ->from('products_variations', 'pv') ->join('pv', 'products_variations_combination', 'pvc', 'pvc.id_variation = pv.id') ->join('pvc', 'products_variations_choices_values', 'pvcv', 'pvcv.id = pvc.id_value') ->where(Operator::equals(['pv.id_product' => $id_product, 'pvcv.id_label' => $iter])) ->andWhere(Variation::isVisible()) ->orderBy('pvcv.sort') ->addOrderBy('pvcv.value + 0 '); $qb->andWhere(Translation::coalesceTranslatedFields(VariationsValuesTranslation::class)); foreach ($qb->execute() as $labelValue) { $labels[$iter]['values'][$labelValue['id']] = $labelValue['value']; $labels[$iter]['codes'][$labelValue['id']] = $labelValue['code']; $labels[$iter]['data'][$labelValue['id']] = json_decode($labelValue['data'] ?: '', true) ?? []; } } return $labels; } public static function fillInProductTitle($id_variation, $title_start) { $title = $title_start; $qb = sqlQueryBuilder() ->select('pv.title') ->from('products_variations', 'pv') ->where(Operator::equals(['pv.id' => $id_variation])); $qb->andWhere(Translation::coalesceTranslatedFields(VariationsTranslation::class)); $variationTitle = $qb->execute()->fetchAssociative(); $variationTitle = $variationTitle['title'] ?? ''; $title .= ' ('.$variationTitle.')'; return $title; } /** * @param $price Decimal * * @return Decimal */ public static function getCustomPrice($id_variation, $price) { if (empty($id_variation)) { return $price; } $qb = sqlQueryBuilder() ->select('pv.price') ->addSelect(\Query\Product::withVat()) ->from('products_variations', 'pv') ->join('pv', 'products', 'p', 'p.id = pv.id_product') ->andWhere(Operator::equals(['pv.id' => $id_variation])); if (findModule(Modules::PRICELISTS)) { $pricelistContext = ServiceContainer::getService(\KupShop\PricelistBundle\Context\PricelistContext::class); $qb->andWhere(\KupShop\PricelistBundle\Query\Product::applyPricelistOnVariation($pricelistContext->getActiveId())); } $data = $qb->execute()->fetch(); $result = toDecimal($data['price']); if (findModule(Modules::PRICELISTS)) { $priceConverter = ServiceContainer::getService(\KupShop\I18nBundle\Util\PriceConverter::class); $currencyContext = ServiceContainer::getService(\KupShop\KupShopBundle\Context\CurrencyContext::class); if (!$data['pricelist_currency']) { $data['pricelist_currency'] = $currencyContext->getDefaultId(); } /** @var \KupShop\PricelistBundle\Entity\Pricelist $activePricelist */ $activePricelist = Contexts::get(PricelistContext::class)->getActive(); $coefficient = $activePricelist?->getCoefficient(); if ($coefficient && in_array($data['price_source'], ['p', 'pv'])) { $data['pricelist_price'] *= $coefficient; } $result = $priceConverter->convert($data['pricelist_currency'], $currencyContext->getDefaultId(), $data['pricelist_price']); } $originalVatId = $data['original_vat'] ?? $data['vat']; if ($result->isPositive()) { if (PriceUtil::isProductPricesVatFromTop()) { $result = $result->addVat(getVat($originalVatId))->removeVat(getVat($data['vat'])); } return $result; } return $price; } // //////////////////////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS // //////////////////////////////////////////////////////////////////////////////////////////////////////////// private static function getProductCombinations($id_product) { // nejdrive nacist vsechy kombinace k variantam $COMBINATIONS = []; $SQL = sqlQuery('SELECT pvc.id_variation, pvc.id_label, pvc.id_value, pvcc.list_order FROM '.getTableName('products_variations_combination').' AS pvc JOIN '.getTableName('products_variations').' AS pv ON pv.id=pvc.id_variation LEFT JOIN '.getTableName('products_variations_choices_categorization').' AS pvcc ON pvc.id_label=pvcc.id_label AND pv.id_product=pvcc.id_product WHERE pv.id_product='.intval($id_product).' ORDER BY pvcc.list_order ASC'); foreach ($SQL as $row) { $IDvariation = $row['id_variation']; if (!isset($COMBINATIONS[$IDvariation])) { $COMBINATIONS[$IDvariation] = []; } $COMBINATIONS[$IDvariation][] = [ 'id_label' => $row['id_label'], 'id_value' => $row['id_value'], 'order' => $row['list_order'], ]; } return $COMBINATIONS; } public static function updateTitle($id_variation = null, $id_value = null, $id_label = null) { $where = '1'; if ($id_variation) { $where = "pv.id = {$id_variation}"; } elseif ($id_value) { $where = "pvc2.id_value = {$id_value}"; } elseif ($id_label) { $where = "pvc2.id_variation = {$id_label}"; } $query = 'UPDATE '.getTableName('products_variations')." pv LEFT JOIN products_variations_combination pvc2 ON pv.id=pvc2.id_variation SET title=( SELECT GROUP_CONCAT(CONCAT_WS(': ', label, value) ORDER BY list_order ASC SEPARATOR ', ') as title FROM ".getTableName('products_variations_combination').' pvc JOIN'.getTableName('products_variations_choices_values').' pvcv ON pvcv.id=pvc.id_value JOIN '.getTableName('products_variations_choices_labels').'pvcl ON pvcl.id=pvc.id_label JOIN '.getTableName('products_variations_choices_categorization').'pvcc ON pvcc.id_label=pvc.id_label WHERE pv.id=pvc.id_variation AND pvcc.id_product=pv.id_product GROUP BY pv.id) WHERE '.$where; if (findModule(Modules::TRANSLATIONS) && findModule(Modules::PRODUCTS_VARIATIONS)) { $languageContext = ServiceContainer::getService(\KupShop\KupShopBundle\Context\LanguageContext::class); foreach ($languageContext->getSupported() as $language) { if ($languageContext->getDefaultId() == $language->getId()) { continue; } // create records in products_variations_translations sqlQuery('INSERT INTO products_variations_translations (id_products_variation, id_language) SELECT pv.id as id_products_variation, :lang as id_language FROM products_variations pv LEFT JOIN products_variations_combination pvc2 ON pv.id=pvc2.id_variation LEFT JOIN products_variations_translations pvt ON pv.id = pvt.id_products_variation AND pvt.id_language = :lang WHERE pvt.id IS NULL AND '.$where.' GROUP BY pv.id;', ['lang' => $language->getId()]); // update titles in products_variations_translations $qb = sqlQueryBuilder() ->select('pv.id, pvt.id_language, pvt.id as tid, GROUP_CONCAT(CONCAT_WS(": ", COALESCE(pvclt.label, pvcl.label), COALESCE(pvcvt.value, pvcv.value)) ORDER BY list_order ASC SEPARATOR ", ") as title') ->from('products_variations_translations', 'pvt') ->join('pvt', 'products_variations', 'pv', 'pv.id = pvt.id_products_variation') ->join('pv', 'products_variations_combination', 'pvc', 'pvc.id_variation = pv.id') ->join('pvc', 'products_variations_choices_values', 'pvcv', 'pvcv.id = pvc.id_value') ->join('pvc', 'products_variations_choices_labels', 'pvcl', 'pvcl.id=pvc.id_label') ->join('pvc', 'products_variations_choices_categorization', 'pvcc', 'pvcc.id_label=pvc.id_label') ->leftJoin('pvcv', 'products_variations_choices_values_translations', 'pvcvt', 'pvcvt.id_products_variations_choices_value = pvcv.id AND pvcvt.id_language = pvt.id_language') ->leftJoin('pvcl', 'products_variations_choices_labels_translations', 'pvclt', 'pvclt.id_products_variations_choices_label = pvcl.id AND pvclt.id_language = pvt.id_language') ->andWhere('pv.id=pvc.id_variation AND pvcc.id_product=pv.id_product') ->groupBy('pv.id, pvt.id_language'); if ($id_variation) { $qb->andWhere(Operator::equals(['pv.id' => $id_variation])); } elseif ($id_value) { $qb->andWhere(Operator::equals(['pvc.id_value' => $id_value])); } elseif ($id_label) { $qb->andWhere(Operator::equals(['pvc.id_variation' => $id_label])); } sqlQuery('UPDATE products_variations_translations pvt2 JOIN products_variations pv ON pv.id = pvt2.id_products_variation LEFT JOIN products_variations_combination pvc2 ON pv.id=pvc2.id_variation JOIN ('.$qb->getSQL().') t1 ON pvt2.id_products_variation = t1.id AND t1.id_language = :lang SET pvt2.title = t1.title WHERE pvt2.id = t1.tid AND pvt2.title != t1.title OR pvt2.title IS NULL AND '.$where.';', array_merge(['lang' => $language->getId()], $qb->getParameters()), $qb->getParameterTypes()); } } sqlQuery($query); } public static function duplicateVariations($fromID, $toID) { sqlGetConnection()->transactional(function () use ($fromID, $toID) { $data = ['id_product_from' => $fromID, 'id_product_to' => $toID]; sqlQuery('INSERT IGNORE INTO products_variations_choices_categorization (id_product, id_label, list_order) SELECT :id_product_to, id_label, list_order FROM products_variations_choices_categorization WHERE id_product=:id_product_from', $data); $qb = sqlQueryBuilder() ->select('pv.id, GROUP_CONCAT(pvc.id_value ORDER BY pvc.id_value) as valueIds') ->from('products_variations_combination', 'pvc') ->join('pvc', 'products_variations', 'pv', 'pvc.id_variation = pv.id') ->join('pvc', 'products_variations_choices_values', 'pvcv', 'pvc.id_value=pvcv.id') ->where(Operator::equals(['pv.id_product' => $fromID])) ->groupBy('pv.id'); $last_id = null; foreach ($qb->execute() as $variant) { $data['id_variation_from'] = $variant['id']; // check that the variation does not exist yet $variantExists = sqlQueryBuilder() ->select('pv.id') ->from('products_variations', 'pv') ->join('pv', 'products_variations_combination', 'pvc', 'pvc.id_variation = pv.id') ->where(Operator::equals(['pv.id_product' => $toID])) ->groupBy('pv.id') ->having('GROUP_CONCAT(pvc.id_value ORDER BY pvc.id_value) = :variationValues') ->setParameter('variationValues', $variant['valueIds']) ->execute()->fetchOne(); if ($variantExists) { continue; } if ($last_id != $variant['id']) { $data['id_variation_to'] = self::duplicateVariationData($data); $last_id = $variant['id']; } if (findModule('photos') && findModule('products_variations_photos') && getVal('duplicateImages') && getVal('duplicateID')) { sqlQuery("INSERT INTO photos_products_relation (id_photo, id_product, id_variation, show_in_lead, active, date_added, position) SELECT id_photo, {$toID}, {$data['id_variation_to']}, show_in_lead, active, date_added, position FROM photos_products_relation WHERE id_product={$fromID} AND id_variation={$data['id_variation_from']}"); } } }); $prod = new Product($toID); $prod->updateInStore(); $prod->updateDeliveryTime(); } protected static function duplicateVariationData($data) { $fields = 'delivery_time, price, figure, '; if (findModule(Modules::PRODUCTS, Modules::SUB_NOTE)) { $fields .= 'note, '; } if (findModule(Modules::PRODUCTS, Modules::SUB_WEIGHT)) { $fields .= 'weight, '; } $fields .= 'width, height, depth, data'; if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_COMMON)) { $fields .= ', price_common'; } if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)) { $fields .= ', price_buy'; } sqlQuery('INSERT INTO products_variations (id, id_product, title, in_store, '.$fields.') SELECT NULL, :id_product_to, title, 0, '.$fields.' FROM products_variations WHERE id=:id_variation_from', $data); $insertedId = sqlInsertId(); $data['id_variation_to'] = $insertedId; sqlQuery('INSERT INTO products_variations_combination (id_variation, id_label, id_value) SELECT :id_variation_to, id_label, id_value FROM products_variations_combination WHERE id_variation=:id_variation_from', $data); return $insertedId; } public static function duplicateVariation($fromIDVariation, $toIDProduct): ?int { $fromIDProduct = sqlQueryBuilder()->select('id_product')->from('products_variations') ->where(Operator::equals(['id' => $fromIDVariation])) ->execute()->fetchOne(); $newVariationId = sqlGetConnection()->transactional(function () use ($fromIDVariation, $fromIDProduct, $toIDProduct) { $data = ['id_product_from' => $fromIDProduct, 'id_product_to' => $toIDProduct]; sqlQuery('INSERT IGNORE INTO products_variations_choices_categorization (id_product, id_label, list_order) SELECT :id_product_to, id_label, list_order FROM products_variations_choices_categorization WHERE id_product=:id_product_from', $data); $data['id_variation_from'] = $fromIDVariation; $data['id_variation_to'] = self::duplicateVariationData($data); if (findModule('photos') && findModule('products_variations_photos') && getVal('duplicateImages') && getVal('duplicateID')) { sqlQuery("INSERT INTO photos_products_relation (id_photo, id_product, id_variation, show_in_lead, active, date_added, position) SELECT id_photo, {$toIDProduct}, {$data['id_variation_to']}, show_in_lead, active, date_added, position FROM photos_products_relation WHERE id_product={$fromIDProduct} AND id_variation={$data['id_variation_from']}"); } return $data['id_variation_to']; }); $prod = new Product($toIDProduct); $prod->updateInStore(); $prod->updateDeliveryTime(); return $newVariationId; } public static function createProductVariation($productId, $parts, $search = true) { if ($search) { // Discover wheter variation already exists $query = 'SELECT p.id AS id FROM '.getTableName('products_variations').' AS p '; $queryWhere = 'WHERE p.id_product='.$productId.' '; foreach ($parts as $variant => $value) { $query .= 'LEFT JOIN '.getTableName('products_variations_combination').' AS v'.$variant.' ON p.id=v'.$variant.'.id_variation and v'.$variant.'.id_label='.$variant.' LEFT JOIN '.getTableName('products_variations_choices_values').' AS vv'.$variant.' ON v'.$variant.'.id_value=vv'.$variant.'.id '; $queryWhere .= ' AND COALESCE(vv'.$variant.'.code, vv'.$variant.".value)='".sqlFormatInput($value)."' "; } $SQL = sqlQuery($query.$queryWhere); // logError(__FILE__, __LINE__, "Testuju varianty: ".sqlNumRows($SQL)." - ".$query.$queryWhere, true); if (sqlNumRows($SQL) > 0) { // Variation exists $row = sqlFetchAssoc($SQL); $variation_id = $row['id']; } } if (!isset($variation_id)) {// Variatin NOT exists, create new one // Find values IDs $valuesQb = sqlQueryBuilder() ->select('id, COALESCE(value, code) as name, id_label') ->from('products_variations_choices_values'); $specs = ['0=1']; foreach ($parts as $variant => $value) { $specs[] = Operator::orX( Operator::equals(['id_label' => $variant, 'COALESCE(code, value)' => $value]), Operator::equals(['id_label' => $variant, 'value' => $value]) ); } $valuesQb->andWhere(Operator::orX($specs)); $values = []; $names = []; foreach ($valuesQb->execute() as $row) { $values[$row['id_label']] = $row['id']; $names[$row['id_label']] = $row['name']; } // Find values labels $query = 'SELECT id, label FROM '.getTableName('products_variations_choices_labels').' WHERE 0=1 '; foreach ($parts as $variant => $value) { $query .= " or id='".$variant."' "; } $SQL = sqlQuery($query); $labels = []; while ($row = sqlFetchAssoc($SQL)) { $labels[$row['id']] = $row['label']; } $title = ''; foreach ($parts as $variant => $value) { $title .= $labels[$variant]."\t".($names[$variant] ?? $value)."\n"; } $query = 'INSERT INTO '.getTableName('products_variations').' (id_product, title) VALUES ('.$productId.',"'.sqlFormatInput($title).'")'; sqlQuery($query); $variation_id = sqlInsertId(); // Insert values foreach ($parts as $variant => $value) { if (!isset($values[$variant])) { // Insert new value if does not exists $query = 'INSERT INTO '.getTableName('products_variations_choices_values').' (id_label, value, code) VALUES ('.$variant.",'".sqlFormatInput($value)."','".sqlFormatInput($value)."')"; sqlQuery($query); $values[$variant] = sqlInsertId(); } $query = 'INSERT INTO '.getTableName('products_variations_combination').' (id_variation, id_label, id_value) VALUES ('.$variation_id.','.$variant.",'".$values[$variant]."')"; sqlQuery($query); } // Make sure categorization works $query = 'SELECT 1 FROM '.getTableName('products_variations_choices_categorization').' WHERE id_product='.$productId; $SQL = sqlQuery($query); if (sqlNumRows($SQL) < count($parts)) { $index = 1; foreach ($parts as $variant => $value) { $query = 'INSERT IGNORE INTO '.getTableName('products_variations_choices_categorization').' (id_product, id_label, list_order) VALUES ('.$productId.','.$variant.",'".$index++."')"; sqlQuery($query); } } } return $variation_id; } public static function makeCodeUnique($variationId, $code, $productId = null) { $data = [ 'code' => $code, 'id_variation' => $variationId, 'id_product' => $productId, ]; $productsQb = sqlQueryBuilder() ->select('COUNT(*)') ->from('products', 'p') ->where('p.code = :code') ->setParameters($data); if ($productId) { $productsQb->andWhere('p.id != :id_product'); } $variationsQb = sqlQueryBuilder() ->select('COUNT(*)') ->from('products_variations', 'pv') ->where('pv.code = :code') ->setParameters($data); if ($variationId) { $variationsQb->andWhere('pv.id != :id_variation OR pv.id IS NULL'); } $productsResult = $productsQb->execute()->fetchOne(); $variationsResult = $variationsQb->execute()->fetchOne(); if ($productsResult == 0 && $variationsResult == 0) { return $code; } // Strip "(1)" $code = preg_replace('/ \([0-9]+\)$/', '', $code); $index = 0; do { $index++; $data['code'] = "{$code} ({$index})"; $productsQb->setParameters($data); $variationsQb->setParameters($data); } while ($productsQb->execute()->fetchOne() > 0 || $variationsQb->execute()->fetchOne() > 0); return $data['code']; } public static function eanExists($variationId, $code, $productId = null) { if (findModule('products', 'allow_duplicate_ean')) { return false; } $sql = sqlQueryBuilder()->select('p.id AS id_product, pv.id AS id_variation') ->from('products', 'p') ->leftJoin('p', 'products_variations', 'pv', 'p.id=pv.id_product') ->where('(pv.ean LIKE :ean OR p.ean LIKE :ean) AND (pv.id != :id_variation OR pv.id IS NULL)'); if ($productId) { $sql->andWhere('p.id !=:id_product'); } if (findModule(Modules::PRODUCTS_SUPPLIERS)) { $sql->leftJoin('p', 'products_of_suppliers', 'pos', 'pos.id_product = p.id AND (pv.id = pos.id_variation OR pv.id IS NULL) AND pos.ean LIKE :ean'); } $data = [ 'ean' => ltrim(trim($code, " \t\n\r\0\x0B"), '0'), 'id_variation' => $variationId, 'id_product' => $productId, ]; $exists = $sql->setParameters($data)->execute()->fetch(); if ($exists) { $exists['ean'] = $data['ean']; } return $exists; } public static function eanExistsMessage($exists) { $ean = $exists['ean']; $productId = $exists['id_product']; $variations = (!empty($exists['id_variation']) ? ", '&flap=3'" : ''); return "EAN {$ean} není unikátní. Zobrazit duplicitní produkt."; } public static function recalcDeliveryTimes() { $result = sqlQuery('SELECT id FROM products'); $changed = 0; while (($row = sqlFetchAssoc($result)) !== false) { $prod = new Product($row['id']); $changed += $prod->updateDeliveryTime(); } return $changed; } public static function recalcInStore() { $inStoreCountsQuery = sqlQueryBuilder() ->select('p.id, COALESCE(SUM(GREATEST(pv.in_store, 0)), p.in_store) in_store') ->from('products', 'p') ->leftJoin('p', 'products_variations', 'pv', 'p.id=pv.id_product AND pv.figure = "Y"') ->groupBy('p.id'); // Select which products should be updated $productsToUpdate = sqlQueryBuilder() ->select('p.id') ->from('products', 'p') ->joinSubQuery('p', $inStoreCountsQuery, 'sums', 'p.id=sums.id') ->where('p.in_store != sums.in_store') ->setMaxResults(1000)->execute()->fetchFirstColumn(); $update = sqlQueryBuilder()->update('products', 'p') ->joinSubQuery('p', $inStoreCountsQuery, 'sums', 'p.id=sums.id') ->set('p.in_store', 'sums.in_store') ->where('p.in_store != sums.in_store'); // DB Locks optimization, reduces locked rows -> reduces deadlocks if (count($productsToUpdate) < 1000) { $update->andWhere(Operator::inIntArray($productsToUpdate, 'p.id')); } return $update->execute(); } protected static function prepareDeliveryText($id_product, &$variation) { $inStore = $variation['in_store']; $delivery_time_text = getProductDeliveryText($variation['in_store'], $variation['delivery_time']); $variation['delivery_time_index'] = $variation['delivery_time']; $variation['delivery_time'] = $delivery_time_text; if ($inStore <= 0 && findModule('products_suppliers')) { $inSuppliers = returnSQLResult('SELECT SUM(in_store) FROM '.getTableName('products_of_suppliers')." pos WHERE pos.id_product={$id_product} AND pos.id_variation={$variation['id']}"); if (findModule(\Modules::PRODUCTS_SUPPLIERS, \Modules::SUB_ALLOW_NEGATIVE_IN_STORE)) { $inSuppliers += $inStore; } if ($inSuppliers > 0) { $variation['in_store_suppliers'] = $inSuppliers; if (findModule(\Modules::PRODUCTS_SUPPLIERS) && findModule(\Modules::PRODUCTS_SUPPLIERS, \Modules::SUB_DELIVERY_TIME)) { $cfg = \KupShop\KupShopBundle\Config::get(); $languageContext = Contexts::get(\KupShop\KupShopBundle\Context\LanguageContext::class); $variation['delivery_time_index'] = $cfg['Modules']['products_suppliers']['delivery_time']; if (!$languageContext->translationActive()) { $variation['delivery_time'] = sprintf($cfg['Products']['DeliveryTime'][$cfg['Modules']['products_suppliers']['delivery_time']], strval($inSuppliers)); } else { $variation['delivery_time'] = sprintf(translate($cfg['Modules']['products_suppliers']['delivery_time'], 'deliveryTime'), strval($inSuppliers)); } } } } } } if (empty($subclass)) { class Variations extends VariationsBase { } }