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

334 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace External\HannahBundle\Util;
use KupShop\CatalogBundle\ProductList\ProductCollection;
use KupShop\CatalogBundle\ProductList\ProductList;
use KupShop\KupShopBundle\Util\Functional\Mapping;
use KupShop\StoresBundle\Utils\StoresInStore;
use Query\Operator;
use Query\Product;
class ProductUtil
{
public const FLAG_LAST_SIZES = 'LS';
public const LABEL_CODE_LAST_SIZES = 'LS';
public function __construct(
private StoresInStore $storesInStore,
private ProductList $productList,
) {
}
public function getDateIncrement(array $products, bool $min = false): int
{
$increment = $min ? null : 0;
$items = $this->getProducts($products);
foreach ($items as $product) {
$productDeliveryTime = $this->getDeliveryTimeByProduct($product);
if ($increment === null) {
$increment = $productDeliveryTime;
}
if ($min) {
$increment = min(
$increment,
$productDeliveryTime
);
continue;
}
$increment = max(
$increment,
$productDeliveryTime
);
}
return $increment;
}
public function getStoreDeliveryTime(array $products, int $storeId): int
{
$deliveryTime = 0;
$items = $this->getProducts($products);
// nactu deliveryTime pro konkretni prodejnu
foreach ($items as $product) {
$deliveryTime = max(
$this->getDeliveryTimeByProduct($product, $storeId),
$deliveryTime
);
}
// kontroluju oteviraci dobu prodejny
if ($openingHours = $this->getStoreOpeningHours($storeId)) {
[$open, $closed] = $openingHours;
$closedDateTime = new \DateTime(
$this->getCurrentDateTime()->format('Y-m-d '.$closed)
);
// Pokud je prodejna uz zavrena, tak pridam jeste jeden den, protoze to udelaji az dalsi den
if ($this->getCurrentDateTime() > $closedDateTime) {
++$deliveryTime;
}
} else {
++$deliveryTime;
}
return $deliveryTime;
}
public function getLabelByCode(string $code): ?int
{
$labelId = sqlQueryBuilder()
->select('id')
->from('labels')
->where(Operator::equals(['code' => $code]))
->execute()->fetchOne();
return $labelId ?: null;
}
private function getDeliveryTimeByProduct(\Product $product, ?int $storeId = null): int
{
$dbcfg = \Settings::getDefault();
// pouziju nastaveni z administrace
$config = $dbcfg->outdoorconcept['deliveryTime'] ?? [];
// nactu si skladovosti
[$inStore, $inStoreSellers, $inStoreSelectedStore] = $this->getInStores($product->storesInStore ?? [], $storeId);
$deliveryTime = 0;
if ($product->isVirtual()) {
return $deliveryTime;
}
if ($storeId) {
$afternoonIncrement = null;
// pokud ze mam skladem na vybranem skladu (prodejne), tak je vyzednuti v podstate ihned
if ($inStoreSelectedStore > 0) {
$deliveryTime = 0;
// pokud nemam skladem na prodejne, ale mam skladem na hlavnim skladu, tak se musi na prodejnu zavest z centraly
} elseif ($inStore > 0) {
$deliveryTime = $config['seller']['inMainStore']['days'] ?? 2;
$afternoonIncrement = $config['seller']['inMainStore']['afternoonIncrement'] ?? 0;
} else {
// neni skladem na prodejne, ani na centrale, ale je skladem na jine prodejne, takze budeme prevazet odtamtud
$deliveryTime = $config['seller']['inOtherStore']['days'] ?? 4;
$afternoonIncrement = $config['seller']['inOtherStore']['afternoonIncrement'] ?? 0;
}
// pokud je nastaveny afternoon increment a zaroven je po 12 hodine, takze je odpoledne, tak potrebuju jeste
// navysit datum doruceni o tenhle afternoon increment
if ($afternoonIncrement && $this->getCurrentDateTime()->format('H') >= 12) {
$deliveryTime += $afternoonIncrement;
}
} else {
// Pokud nemam na centrale, ale mam skladem na nejake prodejne, tak muzu expedovat pres prodejnu, ale je potreba pridat dalsi dny
// k dnum doruceni na dopravci
if ($inStore <= 0 && $inStoreSellers > 0) {
$deliveryTime = $config['seller']['carrier']['inSellerStoreIncrement'] ?? 2;
}
}
return (int) $deliveryTime;
}
private function getStoreOpeningHours(int $storeId): ?array
{
static $storesCache;
if (!$storesCache) {
$qb = sqlQueryBuilder()
->select('s.id, s.name, dtd.data')
->from('stores', 's')
->join('s', 'delivery_type_delivery', 'dtd', 's.id_delivery = dtd.id');
foreach ($qb->execute() as $item) {
$storesCache[$item['id']] = [
'name' => $item['name'],
'data' => json_decode($item['data'] ?? '', true) ?? [],
];
}
}
$currentDay = $this->getCurrentDateTime()->format('N');
if ($store = ($storesCache[$storeId] ?? false)) {
if ($openingHours = ($store['data']['opening_hours'] ?? false)) {
$currentDayOpeningHours = array_filter($openingHours[$currentDay] ?? []);
if (!empty($currentDayOpeningHours)) {
return $currentDayOpeningHours;
}
}
}
return null;
}
private function getCurrentDateTime(): \DateTime
{
static $dateTime;
if (!$dateTime) {
$dateTime = new \DateTime();
}
return $dateTime;
}
/**
* Funkce, ktera vrati skladovosti.
*
* Vraci:
* [hlavni sklad, sklad prodejen, sklad vybrane prodejny]
*/
private function getInStores(array $storesValues, ?int $selectedStoreId = null): array
{
$stores = $this->storesInStore->getStores();
$sellers = $this->getSellers();
$inStore = 0;
$inStoreStores = 0;
$inStoreSelectedStore = 0;
foreach ($storesValues as $storeId => $data) {
if (!($store = ($stores[$storeId] ?? false))) {
continue;
}
if ($selectedStoreId && $storeId == $selectedStoreId) {
$inStoreSelectedStore += $data['in_store'];
}
$seller = null;
foreach ($sellers as $tmpSeller) {
if ($tmpSeller['id_store'] == $storeId) {
$seller = $tmpSeller;
break;
}
}
// pokud je sklad prodejna, tak ma nastaveny id_delivery
if ($store['id_delivery'] || $seller) {
// pokud neni prodejna aktivni, tak jeji sklad ignoruju
if ($seller['figure'] !== 'N') {
$inStoreStores += $data['in_store'];
}
} else {
$inStore += $data['in_store'];
}
}
return [$inStore, $inStoreStores, $inStoreSelectedStore];
}
public function generateLastSizesFlag(): void
{
$ids = array_map(
fn ($x) => $x['id'],
sqlQueryBuilder()
->select('p.id')
->from('products', 'p')
->leftJoin('p', 'products_variations', 'pv', 'pv.id_product = p.id')
->leftJoin('pv', 'products_variations_combination', 'pvc', 'pvc.id_variation = pv.id')
->leftJoin('pvc', 'products_variations_choices_values', 'pvcv', 'pvc.id_value = pvcv.id')
->where('pv.in_store > 0 AND pv.figure = \'Y\' AND pvcv.value NOT IN (\'UNI\', \'nezadáno\', \'-\')')
->groupBy('p.id')
->having('COUNT(pv.id) = 1')
->execute()->fetchAllAssociative()
);
sqlGetConnection()->transactional(function () {
$labelId = $this->getLabelByCode(self::LABEL_CODE_LAST_SIZES);
if (!$labelId) {
return;
}
sqlQueryBuilder()
->delete('product_labels_relation')
->andWhere(Operator::equals(['id_label' => $labelId]))
->execute();
sqlQuery('INSERT IGNORE INTO product_labels_relation (id_label, id_product)
SELECT '.$labelId.' as id_label, p.id as id_product FROM products p
LEFT JOIN products_variations pv ON pv.id_product = p.id
LEFT JOIN products_variations_combination pvc ON pvc.id_variation = pv.id
LEFT JOIN products_variations_choices_values pvcv on pvc.id_value = pvcv.id
WHERE pv.in_store > 0 AND pv.figure = \'Y\'
AND pvcv.value NOT IN (\'UNI\', \'nezadáno\', \'-\')
GROUP BY p.id
HAVING COUNT(pv.id) = 1');
});
// TODO: deprecated campaign - remove me after some time
sqlGetConnection()->transactional(function () use ($ids) {
sqlQueryBuilder()
->update('products')
->set('campaign', 'REMOVE_FROM_SET(:flag, campaign)')
->setParameter('flag', self::FLAG_LAST_SIZES)
->execute();
sqlQueryBuilder()
->update('products')
->set('campaign', 'ADD_TO_SET(:flag, campaign)')
->where(Operator::inIntArray($ids, 'id'))
->setParameter('flag', self::FLAG_LAST_SIZES)
->execute();
});
}
public function getProductVat(int $productId): float
{
$vatId = sqlQueryBuilder()
->select('vat')
->from('products')
->where(Operator::equals(['id' => $productId]))
->execute()->fetchOne();
return (float) getVat($vatId);
}
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->fetchStoresInStore();
$productsCache[$cacheKey] = $collection;
}
return $productsCache[$cacheKey];
}
private function getSellers(): array
{
static $sellersAll;
if (!$sellersAll) {
$sellersAll = Mapping::mapKeys(sqlQueryBuilder()
->select('id, id_store, figure')
->from('sellers')
->execute()
->fetchAllAssociative(), fn ($k, $v) => [$v['id'], $v]);
}
return $sellersAll;
}
}