'processPrice', 'ProduktCenaTier' => 'processQuantityDiscount', ]; } public function processPrice(array $item): void { if ($item['meta']['delete'] ?? false) { if (!($item = $this->getProductPriceDeleteItem($item))) { return; } } if (!$this->isMessageWithStoreRequiredValid($item)) { return; } // CenovaUroven = ID ceniku v Heliosu, v podstate kazda cena by mela spadat pod nejaky cenik if (empty($item['CenovaUroven'])) { return; } if (!($priceListId = $this->znzUtil->getPriceListByZNZId($item['CenovaUroven']))) { return; } [$productId, $variationId] = $this->znzUtil->getProductMapping($item['IdProdukt']); if (!$productId) { throw new RabbitRetryMessageException('Product not found'); } $variationId = $this->znzUtil->getProductVariationIdWithStoreCheck($productId, $variationId, $item['IdSklad'] ?? null); $priceListType = $this->getPriceListType($item, $priceListId); $isPriceListWithDefaultPrice = $this->isPriceListWithDefaultPrice($item, $priceListType, $priceListId); $maxId = $this->znzUtil->getZNZMaxId($item['IdProdukt'], 'ProduktCena', (string) $item['CenovaUroven']); // je to stara zmena, ktera nas uz nezajima, protoze mame novejsi data if ($maxId && $maxId > $item['meta']['id_change']) { return; } $price = $this->createProductPrice($productId, $item); // aktualizovat vychozi cenu u produktu if ($isPriceListWithDefaultPrice) { $this->updateItemDefaultPrice($productId, $variationId, $price); } $this->updatePriceList($priceListId, $productId, $variationId, $price); $this->updatePriceForDiscount($priceListId, $productId, $variationId, $price, $isPriceListWithDefaultPrice); $this->znzUtil->updateZNZMaxId($item['IdProdukt'], 'ProduktCena', (string) $item['CenovaUroven'], $item['meta']['id_change']); } public function processQuantityDiscount(array $item): void { if (!$this->isMessageWithStoreRequiredValid($item)) { return; } [$productId, $variationId] = $this->znzUtil->getProductMapping($item['IDProdukt']); if (!$productId) { return; } $variationId = $this->znzUtil->getProductVariationIdWithStoreCheck($productId, $variationId, $item['IdSklad'] ?? null); $discount = $this->createProductPrice($productId, $item, 'Sleva4'); $groups = array_flip($this->quantityDiscountUtil->getGroups()); // v pripade B2B shopu je mnozstevni sleva per cenik if ($this->configuration->isB2BShop()) { if (!($priceListId = $this->znzUtil->getPriceListByZNZId($item['CenovaUroven']))) { return; } // id skupiny mnozsteni slevy je stejne jako ID ceniku $groupId = $priceListId; } else { $groupId = null; // ulozim si mnozstevni slevu pod spravnou skupinu (skupiny jsou vytvorene podle websites) if ($groups[$item['IdWebsite']] ?? false) { $groupId = $groups[$item['IdWebsite']]; } } $currencyContext = Contexts::get(CurrencyContext::class); $currency = $item['Mena4'] ?? $currencyContext->getDefaultId(); if (!($currencyContext->getAll()[$currency] ?? false)) { throw new ZNZException('Currency "'.$currency.'" not found!'); } $this->updateQuantityDiscount( $productId, $variationId, (int) $item['MnozstviOd'], $discount, $groupId, $currency ); } protected function getPriceListType(array $item, int $priceListId): string { // jedna se o cenik s priznakem Vychozi=true if ($this->isPriceListDefault($priceListId)) { // pokud je zapnuto vychozi cenik pro kazdou website, tak to vzdycky musi byt cenik if ($this->configuration->isDefaultPriceListPerWebsite()) { return self::TYPE_DEFAULT_PRICE_LIST; } // pokud je to cenik ve vychozi mene + to je vychozi cenik, tak vracim TYPE_DEFAULT // to znamena, ze se cena neulozi do ceniku, ale ulozi se primo k produktu/variante if ($item['Mena'] === Contexts::get(CurrencyContext::class)->getDefaultId()) { return self::TYPE_DEFAULT; } } return self::TYPE_PRICE_LIST; } protected function isPriceListDefault(int $priceListId): bool { static $cache = []; if (($cache[$priceListId] ?? null) === null) { $cache[$priceListId] = (bool) sqlQueryBuilder() ->select('is_default') ->from('znz_pricelists') ->where(Operator::equals(['id_pricelist' => $priceListId])) ->execute()->fetchOne(); } return $cache[$priceListId]; } private function updatePriceForDiscount(int $priceListId, int $productId, ?int $variationId, \Decimal $price, bool $isPriceListWithDefaultPrice): void { if (!findModule(\Modules::PRICE_HISTORY)) { return; } // aktualizovat CPS v ceniku sqlQueryBuilder() ->update('pricelists_products') ->set('price_for_discount', 'LEAST(:price, price_for_discount)') ->setParameter('price', $price) ->where(Operator::equalsNullable(['id_pricelist' => $priceListId, 'id_product' => $productId, 'id_variation' => $variationId])) ->execute(); // aktualizovat CPS u produktu/varianty - pokud se jedna o cenik s vychozi cenou if ($isPriceListWithDefaultPrice) { sqlQueryBuilder() ->update($variationId ? 'products_variations' : 'products') ->set('price_for_discount', 'LEAST(:price, price_for_discount)') ->setParameter('price', $price) ->where(Operator::equals(['id' => $variationId ?: $productId])) ->execute(); } } private function updateItemDefaultPrice(int $productId, ?int $variationId, \Decimal $price): void { // aktualizovat vychozi cenu sqlQueryBuilder() ->update($variationId ? 'products_variations' : 'products') ->directValues(['price' => $price]) ->where(Operator::equals(['id' => $variationId ?: $productId])) ->execute(); } private function getProductPriceDeleteItem(array $item): ?array { $parts = explode('-', $item['meta']['unique_id'] ?? ''); if (empty($parts[0])) { return null; } $znzPriceListId = !empty($parts[2]) ? (int) $parts[2] : null; $priceList = $znzPriceListId ? $this->getPriceListByZNZId($znzPriceListId) : null; return [ 'IdProdukt' => (int) $parts[0], 'CenovaUroven' => $znzPriceListId, 'Mena' => $priceList['currency'] ?? $this->configuration->getWebsiteCurrency($item['meta']['website']), 'IdWebsite' => $item['meta']['website'], 'Cena' => 0, 'meta' => $item['meta'], ]; } /** * Urcuje, zda se jedna o cenik, ktery zaroven obsahuje vychozi cenu, ktera bude ulozena primo k produktu, aby byla * videt v administraci. */ private function isPriceListWithDefaultPrice(array $item, string $priceListType, int $priceListId): bool { if ($priceListType === self::TYPE_DEFAULT) { return true; } if ($priceListType !== self::TYPE_DEFAULT_PRICE_LIST) { return false; } // pokud neni cenik ve vychozi mene, tak nemuze byt bran jako cenik s vychozi cenou if (Contexts::get(CurrencyContext::class)->getDefaultId() !== $this->getPriceListCurrency($priceListId)) { return false; } return $this->configuration->getMainWebsite() === $item['IdWebsite']; } private function getDefaultPriceListId(string $priceListName, string $currency, array $data = []): int { static $defaultPriceListsByName; if ($defaultPriceListsByName === null) { $priceListsAll = sqlQueryBuilder() ->select('id, name') ->from('pricelists') ->execute()->fetchAllAssociative(); $priceListsByName = Mapping::mapKeys( $priceListsAll, fn ($k, $v) => [StringUtil::slugify($v['name']), (int) $v['id']] ); } // pokud cenik se stejnym nazvem uz existuje, tak vratim jeho ID if ($defaultPriceListsByName[StringUtil::slugify($priceListName)] ?? false) { return $defaultPriceListsByName[StringUtil::slugify($priceListName)]; } if (!($defaultPriceListsByName[StringUtil::slugify($priceListName)] ?? false)) { $priceListId = sqlGetConnection()->transactional(function () use ($priceListName, $currency, $data) { $priceListId = $this->priceListWorker->findPriceList($priceListName, $currency); sqlQueryBuilder() ->update('pricelists') ->directValues(['data' => json_encode($data)]) ->where(Operator::equals(['id' => $priceListId])) ->execute(); return $priceListId; }); $defaultPriceListsByName[StringUtil::slugify($priceListName)] = $priceListId; } return $defaultPriceListsByName[StringUtil::slugify($priceListName)]; } private function getPriceListId(int $znzId, string $priceListName, string $currency, array $data = []): int { static $priceLists, $priceListsByName; if ($priceLists === null) { $priceListsAll = sqlQueryBuilder() ->select('id, name') ->from('pricelists') ->execute()->fetchAllAssociative(); $priceLists = Mapping::mapKeys( $priceListsAll, fn ($k, $v) => [$v['id'], (int) $v['id']] ); $priceListsByName = Mapping::mapKeys( $priceListsAll, fn ($k, $v) => [StringUtil::slugify($v['name']), (int) $v['id']] ); } if ($priceListsByName[StringUtil::slugify($priceListName)] ?? false) { return $priceListsByName[StringUtil::slugify($priceListName)]; } if (!($priceLists[$znzId] ?? false)) { $priceListId = sqlGetConnection()->transactional(function () use ($znzId, $priceListName, $currency, $data) { $tmpId = $this->priceListWorker->findPriceList($priceListName, $currency); sqlQueryBuilder() ->update('pricelists') ->directValues(['data' => json_encode($data)]) ->where(Operator::equals(['id' => $tmpId])) ->execute(); if (!empty($znzId) && $tmpId != $znzId) { sqlQueryBuilder() ->update('pricelists') ->directValues(['id' => $znzId]) ->where(Operator::equals(['id' => $tmpId])) ->execute(); } return $znzId; }); $priceLists[$znzId] = $priceListId; } return $priceLists[$znzId]; } private function getPriceListByZNZId(int $znzId): ?array { static $priceListsById; if ($priceListsById === null) { $priceListsAll = sqlQueryBuilder() ->select('pl.id, pl.name, pl.currency, zpl.id_znz') ->from('pricelists', 'pl') ->join('pl', 'znz_pricelists', 'zpl', 'zpl.id_pricelist = pl.id') ->execute()->fetchAllAssociative(); $priceLists = Mapping::mapKeys( $priceListsAll, fn ($k, $v) => [$v['id_znz'], $v] ); } return $priceLists[$znzId] ?? null; } private function updatePriceList(int $priceListId, int $productId, ?int $variationId, \Decimal $price): void { sqlQueryBuilder() ->insert('pricelists_products') ->directValues([ 'id_pricelist' => $priceListId, 'id_product' => $productId, 'id_variation' => $variationId, 'price' => $price, ]) ->onDuplicateKeyUpdate(['price']) ->execute(); } private function updateQuantityDiscount(int $productId, ?int $variationId, int $pieces, \Decimal $discount, ?int $groupId, string $currency): bool { $id = sqlQueryBuilder() ->select('id') ->from('products_quantity_discounts') ->where( Operator::equalsNullable( [ 'id_group' => $groupId, 'id_product' => $productId, 'id_variation' => $variationId, 'pieces' => $pieces, ] ) )->execute()->fetchOne(); if ($id) { sqlQueryBuilder() ->update('products_quantity_discounts') ->directValues( [ 'discount' => $discount, 'discount_type' => $currency, ] ) ->where(Operator::equals(['id' => $id])) ->execute(); } else { sqlQueryBuilder() ->insert('products_quantity_discounts') ->directValues( [ 'id_group' => $groupId, 'id_product' => $productId, 'id_variation' => $variationId, 'pieces' => $pieces, 'discount' => $discount, 'discount_type' => $currency, ] )->execute(); } return true; } private function createProductPrice(int $productId, array $item, string $priceField = 'Cena'): \Decimal { $price = toDecimal($item[$priceField]); // pokud je uvedena cena s DPH, tak DPH odeberu aby se to u nas ulozilo spravne if (($item['BezDPH'] ?? 'N') === 'N') { $price = $price->removeVat( $this->znzUtil->getProductVat($productId) ); } return $price; } private function getPriceListCurrency(int $priceListId): string { static $priceListCurrencyCache = []; if (!($priceListCurrencyCache[$priceListId] ?? false)) { $priceListCurrencyCache[$priceListId] = sqlQueryBuilder() ->select('currency') ->from('pricelists') ->where(Operator::equals(['id' => $priceListId])) ->sendToMaster() ->execute()->fetchOne(); } return $priceListCurrencyCache[$priceListId]; } }