1153 lines
49 KiB
PHP
1153 lines
49 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace External\PompoBundle\Util\Ordering;
|
|
|
|
use External\PompoBundle\DataGo\Util\DataGoApi;
|
|
use External\PompoBundle\DRS\Util\DRSApi;
|
|
use External\PompoBundle\Exception\PompoCartException;
|
|
use External\PompoBundle\Util\Configuration;
|
|
use External\PompoBundle\Util\PompoUtil;
|
|
use External\PompoBundle\Util\ProductUtil;
|
|
use KupShop\CatalogBundle\ProductList\ProductCollection;
|
|
use KupShop\KupShopBundle\Context\CurrencyContext;
|
|
use KupShop\KupShopBundle\Util\ArrayUtil;
|
|
use KupShop\KupShopBundle\Util\Contexts;
|
|
use KupShop\KupShopBundle\Util\DateUtil;
|
|
use KupShop\KupShopBundle\Util\Functional\Mapping;
|
|
use KupShop\OrderingBundle\Entity\Purchase\ProductPurchaseItem;
|
|
use KupShop\OrderingBundle\Entity\Purchase\PurchaseState;
|
|
use KupShop\OrderingBundle\Exception\DeliveryException;
|
|
use KupShop\OrderingBundle\Util\Order\OrderItemInfo;
|
|
use KupShop\OrderingBundle\Util\Order\OrderUtil;
|
|
use KupShop\SellerBundle\Context\SellerContext;
|
|
use KupShop\SellerBundle\Utils\SellerUtil;
|
|
use Query\Operator;
|
|
|
|
class OrderingUtil
|
|
{
|
|
public const CHARGES_HANDLING_FEE = [1, 2];
|
|
|
|
private ProductUtil $productUtil;
|
|
private Configuration $configuration;
|
|
private OrderUtil $orderUtil;
|
|
private OrderItemInfo $orderItemInfo;
|
|
private PompoUtil $pompoUtil;
|
|
private DRSApi $drsApi;
|
|
private DataGoApi $dataGoApi;
|
|
private ?SellerUtil $sellerUtil;
|
|
|
|
public function __construct(
|
|
ProductUtil $productUtil,
|
|
Configuration $configuration,
|
|
OrderUtil $orderUtil,
|
|
OrderItemInfo $orderItemInfo,
|
|
PompoUtil $pompoUtil,
|
|
DRSApi $drsApi,
|
|
DataGoApi $dataGoApi,
|
|
?SellerUtil $sellerUtil,
|
|
) {
|
|
$this->productUtil = $productUtil;
|
|
$this->configuration = $configuration;
|
|
$this->orderUtil = $orderUtil;
|
|
$this->orderItemInfo = $orderItemInfo;
|
|
$this->pompoUtil = $pompoUtil;
|
|
$this->drsApi = $drsApi;
|
|
$this->dataGoApi = $dataGoApi;
|
|
$this->sellerUtil = $sellerUtil;
|
|
}
|
|
|
|
/**
|
|
* Rozdělí objednávku typu ORDER_TRANSPORT_RESERVATION na dvě objednávky. Jedna objednávka bude čistě s dostupnýma produktama
|
|
* na prodejně a druhá objednávka bude obsahovat produkty, které jsou dostupné pouzen a centrálním skladu a je potřeba je
|
|
* tedy převézt na prodejnu.
|
|
*/
|
|
public function orderSplitTransportReservation(\Order $order): void
|
|
{
|
|
// rozdelujeme jen typ ORDER_TRANSPORT_RESERVATION
|
|
if ($this->getOrderType($order) !== OrderType::ORDER_TRANSPORT_RESERVATION) {
|
|
return;
|
|
}
|
|
|
|
// cely to delam v transakci
|
|
sqlGetConnection()->transactional(function () use ($order) {
|
|
// force-loadnu si data, protoze eventy tam mohli neco pridat a bez force-loadu bych to tam nemel
|
|
$orderData = $order->getDataAll(true);
|
|
$sellerId = empty($orderData['sellerId']) ? null : (int) $orderData['sellerId'];
|
|
|
|
$items = $this->getPurchaseStateProducts($order->getPurchaseState(), $sellerId, false);
|
|
$itemsGrouped = $this->getPurchaseStateProductsGrouped($order->getPurchaseState());
|
|
// prvne se podivam, zda je co rozdelovat, abych nevytvoril prazdnou rezervacni objednavku
|
|
$reservationItems = false;
|
|
foreach ($items as $item) {
|
|
if (($item->getProduct()->inStoreSeller ?? 0) > 0) {
|
|
$reservationItems = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$reservationItems) {
|
|
return;
|
|
}
|
|
|
|
// zduplikuju objednavku - tohle bude objednavka s cistou rezervaci
|
|
$reservationOrder = $this->orderUtil->copyOrder($order->id);
|
|
$reservationOrder->status_previous = -1;
|
|
$reservationOrder->status = -1;
|
|
$reservationOrder->setData('orderType', OrderType::ORDER_RESERVATION);
|
|
$reservationOrder->setData('orderPartId', 2);
|
|
|
|
$deliveryData = array_merge($orderData['delivery_data'] ?? [], ['seller_id' => $sellerId]);
|
|
// TODO: mozna bude potreba pridat vetsi logiku
|
|
$deliveryData['deliveryDate'] = (new \DateTime())->format('Y-m-d');
|
|
|
|
// insertnu vazbu objednavky na prodejnu k rezervacni objednavce
|
|
if ($sellerId) {
|
|
sqlQueryBuilder()->insert('order_sellers')
|
|
->directValues([
|
|
'id_order' => $reservationOrder->id,
|
|
'id_seller' => $sellerId,
|
|
])
|
|
->onDuplicateKeyUpdate(['id_order', 'id_seller'])
|
|
->execute();
|
|
}
|
|
|
|
$reservationOrder->setData('delivery_data', $deliveryData);
|
|
// Odeberu flag "Rezervace s prepravou" po zkopirovani z puvodni objednavky
|
|
$this->orderUtil->removeFlag($reservationOrder, 'RT');
|
|
// pridam flag "Rezervace"
|
|
$this->orderUtil->addFlag($reservationOrder, 'RZ');
|
|
// pridam flag "Vznikla rozdelenim"
|
|
$this->orderUtil->addFlag($reservationOrder, 'VR');
|
|
|
|
foreach ($items as $item) {
|
|
$key = $this->getPurchaseItemKey($item);
|
|
$itemGrouped = $itemsGrouped[$key] ?? [];
|
|
$itemGrouped->inStoreSeller = ($itemGrouped->inStoreSeller ?? ($item->getProduct()->inStoreSeller ?? 0));
|
|
// pokud to neni skladem na prodejne, tak to zustava v objednavce
|
|
if ($itemGrouped->inStoreSeller <= 0) {
|
|
continue;
|
|
}
|
|
|
|
// pocet kusu, ktere muzu zarezervovat
|
|
$reservationPieces = min($itemGrouped->inStoreSeller, $item->getPieces());
|
|
$newPieces = $item->getPieces() - $reservationPieces;
|
|
|
|
// zduplikuju item do rezervace s novym poctem kusu
|
|
$this->copyOrderItem($reservationOrder, (int) $item->getId(), (int) $reservationPieces);
|
|
$itemGrouped->inStoreSeller -= $reservationPieces;
|
|
|
|
// u aktualni objednavky odeberu pocet kusu, ktere sli do rezervace
|
|
$order->updateItem($item->getId(), $newPieces);
|
|
// smazu item z objednavky, pokud slo vsechno do rezervace
|
|
if ($newPieces <= 0) {
|
|
sqlQueryBuilder()
|
|
->delete('order_items')
|
|
->where(Operator::equals(['id' => $item->getId()]))
|
|
->execute();
|
|
}
|
|
}
|
|
|
|
// nastavim na objednavku ID objednavky s rezervaci - je to hlavne kvuli FE, abychom pak mohli na dekovacce zobrazit odkazy na obe objednavky
|
|
$order->setData('reservationOrderId', $reservationOrder->id);
|
|
$order->setData('orderPartId', 1);
|
|
|
|
// resetuju purchase state objednavky
|
|
$order->setPurchaseState(null);
|
|
|
|
// recalculate na objednavky
|
|
$reservationOrder->recalculate();
|
|
$order->recalculate();
|
|
|
|
// resetnu itemy na objednavce, aby se znovu loadnuly, kdyz by si o ne nekdo rekl a bylo to tam spravne
|
|
$order->items = [];
|
|
|
|
// Odeslat e-mail k rezervacni objednavce
|
|
$reservationOrder->changeStatus(0);
|
|
|
|
// Zaloguju k objednavce, ze z ni byla vytvorena rezervacni objednavka
|
|
$order->logHistory(
|
|
sprintf('Z této objednávka byla vytvořena rezervační objednávka číslo <a href="javascript:nw(\'orders\', \'%s\')">%s</a>', $reservationOrder->id, $reservationOrder->order_no)
|
|
);
|
|
|
|
// Zaloguju k rezervacni objednavce info o tom, ze vznikla rozdelenim
|
|
$reservationOrder->logHistory(
|
|
sprintf('Rezervační objednávka, která vznikla rozdělením z objednávky číslo <a href="javascript:nw(\'orders\', \'%s\')">%s</a>', $order->id, $order->order_no)
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Vrátí typ objednávky - jeden z typů definovaných v classe OrderType.
|
|
*/
|
|
public function getOrderType(\Order $order): string
|
|
{
|
|
// Objednavka uz na sobe orderType ma ulozeny
|
|
if ($orderType = $order->getData('orderType')) {
|
|
return $orderType;
|
|
}
|
|
|
|
// objednávka je přes přepravce
|
|
if (!$this->isOrderInPerson($order)) {
|
|
return OrderType::ORDER_TRANSPORT;
|
|
}
|
|
|
|
// kouknu, jestli ma objednavka manipulacni poplatek
|
|
// nebo je to osobni odber s centralnim skladem
|
|
if ($this->isTransportChargeInOrder($order) || $this->isOrderInPersonWithMainStore($order)) {
|
|
return OrderType::ORDER_TRANSPORT_RESERVATION;
|
|
}
|
|
|
|
return OrderType::ORDER_RESERVATION;
|
|
}
|
|
|
|
/**
|
|
* Lze pouzit pouze pokud vim, ze je doprava osobni odber. Protoze s PurchaseStatu nepoznam o jakou dopravu se jedna.
|
|
*
|
|
* Vrati to teda ORDER_TRANSPORT_RESERVATION nebo ORDER_RESERVATION
|
|
*/
|
|
public function getPurchaseStateOrderType(PurchaseState $purchaseState, int $sellerId): string
|
|
{
|
|
$seller = $this->sellerUtil->getSeller($sellerId);
|
|
|
|
$notInSellerStore = false;
|
|
foreach ($this->getPurchaseStateProducts($purchaseState, $sellerId) as $item) {
|
|
// pokud neni v dostatecnem skladu na prodejne, tak uz to nemuze byt cista rezervace
|
|
// nebo pokud se jedna o prodejnu, ktera ma nastaveny sklad na hlavni sklad - v takovem pripade to taky nesmi byt cista rezervace
|
|
if (($item->getProduct()->inStoreSeller ?? 0) < $item->getPieces() || $seller['id_store'] === $this->configuration->getMainStoreId()) {
|
|
$notInSellerStore = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($notInSellerStore) {
|
|
return OrderType::ORDER_TRANSPORT_RESERVATION;
|
|
}
|
|
|
|
return OrderType::ORDER_RESERVATION;
|
|
}
|
|
|
|
/**
|
|
* Vrací informaci o tom, zda pro PurchaseState existuje prodejna, na kterou je možné provést závoz.
|
|
*/
|
|
public function hasProductWithInPersonTransferDelivery(PurchaseState $purchaseState): bool
|
|
{
|
|
if (!$this->configuration->isPompo()) {
|
|
return false;
|
|
}
|
|
|
|
$transferDeliverySupported = false;
|
|
|
|
$sellerContext = Contexts::get(SellerContext::class);
|
|
$sellers = $sellerContext->getSupported();
|
|
|
|
$this->fetchSellersCartInfo(
|
|
$purchaseState,
|
|
$sellers
|
|
);
|
|
|
|
foreach ($sellers as $seller) {
|
|
if ($seller['availability'] === ProductAvailability::SELLER_IN_STORE_TRANSFER) {
|
|
$transferDeliverySupported = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $transferDeliverySupported;
|
|
}
|
|
|
|
/**
|
|
* Fetchne skladovou dostupnost k jednotlivym produktum v PurchaseStatu. Například na druhém kroku dopravy, kde
|
|
* zobrazujeme produkty s dostupností.
|
|
*/
|
|
public function fetchPurchaseStateProductsAvailability(PurchaseState $purchaseState, ?int $sellerId = null, ?string $pickType = null): void
|
|
{
|
|
$productsInfo = $this->getPurchaseStateProducts($purchaseState, $sellerId);
|
|
$giftsInfo = $this->getPurchaseStateGifts($purchaseState, $sellerId);
|
|
|
|
$getInfoItem = function (ProductPurchaseItem $item) use ($productsInfo, $giftsInfo) {
|
|
$key = $this->getPurchaseItemKey($item);
|
|
|
|
if (!($infoItem = $productsInfo[$key] ?? null)) {
|
|
if (!($infoItem = $giftsInfo[$key] ?? null)) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return $infoItem;
|
|
};
|
|
|
|
// zmerguju si produkty se slevama, abych prosel vsechno naraz
|
|
$items = array_merge($purchaseState->getProducts(), $purchaseState->getDiscounts());
|
|
// doplnim jeste darky od produktu z additional items
|
|
foreach ($purchaseState->getProducts() as $item) {
|
|
foreach ($item->getAdditionalItems() as $additionalItem) {
|
|
$items[] = $additionalItem;
|
|
}
|
|
}
|
|
|
|
foreach ($items as $item) {
|
|
// pokud to neni ProductPurchaseItem, tak me to nezajima
|
|
if (!($item instanceof ProductPurchaseItem)) {
|
|
continue;
|
|
}
|
|
|
|
if (!($infoItem = $getInfoItem($item))) {
|
|
continue;
|
|
}
|
|
|
|
$product = $infoItem->getProduct();
|
|
$pieces = (float) $infoItem->getPieces();
|
|
|
|
$totalMainStore = ($product->inStoreMain ?? 0) + ($product->inStoreSupplier ?? 0);
|
|
$totalSellerStore = ($product->inStoreMain ?? 0) + ($product->inStoreSupplier ?? 0) + ($product->inStoreSeller ?? 0);
|
|
|
|
$productAvailability = ProductAvailability::NOT_IN_STORE;
|
|
// Pokud je na hlavnim skladu dostatek mnozstvi
|
|
if (($product->inStoreMain ?? 0) >= $pieces) {
|
|
$productAvailability = ProductAvailability::IN_STORE;
|
|
} elseif (($product->inStoreSupplier ?? 0) >= ($pieces - ($product->inStoreMain ?? 0))) {
|
|
$productAvailability = ProductAvailability::IN_STORE_SUPPLIER;
|
|
}
|
|
|
|
if ($sellerId) {
|
|
if ($pickType === 'complete') {
|
|
$productAvailability = $productAvailability > ProductAvailability::NOT_IN_STORE ? ProductAvailability::SELLER_IN_STORE_PARTIALLY : ProductAvailability::NOT_IN_STORE;
|
|
}
|
|
|
|
if ($pickType !== 'complete') {
|
|
$productAvailability = $this->getSellerAvailability(
|
|
$product->inStoreSeller ?? 0,
|
|
($product->inStoreMain ?? 0) + ($product->inStoreSupplier ?? 0),
|
|
$pieces
|
|
);
|
|
}
|
|
}
|
|
|
|
// pridam notu, abych v kosiku byl schopny zobrazit primo u produktu stav skladu (napr. na kroku s dopravou)
|
|
$item->addNote('availability', $productAvailability);
|
|
|
|
if ($sellerId && $totalSellerStore < $pieces) {
|
|
// pridam notu, abych v kosiku byl schopny zobrazit primo u produktu stav skladu (napr. na kroku s dopravou)
|
|
$item->addNote('availability', ProductAvailability::NOT_IN_STORE);
|
|
}
|
|
|
|
if (!$sellerId && $totalMainStore < $pieces) {
|
|
// pridam notu, abych v kosiku byl schopny zobrazit primo u produktu stav skladu (napr. na kroku s dopravou)
|
|
$item->addNote('availability', ProductAvailability::NOT_IN_STORE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Kontrola dostupnosti pro každou dopravu. Kontrola se provádí pro každou dopravu, abych dokázal správně zadisablovat
|
|
* dopravy, přes které nejde objednat.
|
|
*
|
|
* Kontroluje dostupnost produktu na prodejnach vs centralni sklad pro osobni odbery a pro dopravce.
|
|
*
|
|
* 1) Na prodejnu mohu objednat bud z centralniho skladu (zavoz na prodejnu) nebo muzu rezervovat zbozi na prodejne, ale nemohu
|
|
* objednat zbozi z jine prodejny.
|
|
* 2) Pokud objednavam pres dopravce, tak musi byt produkt skladem na centralnim sklade nebo u dodavatele, ale nemohu
|
|
* objednat produkt z prodejny.
|
|
*/
|
|
public function validatePurchaseStateByDelivery(\Delivery $delivery, PurchaseState $purchaseState): void
|
|
{
|
|
$selectedDeliveryType = $purchaseState->getDeliveryType();
|
|
|
|
// pokud se jedna o osobni odber
|
|
if ($delivery instanceof \OdberNaProdejne) {
|
|
if ($delivery->getPointId()) {
|
|
$sellerId = (int) $delivery->getPointId();
|
|
|
|
$seller = $this->sellerUtil->getSeller($sellerId);
|
|
// zkontroluju, ze se na daneho sellera da objednavat - muzou prodejnu uzvarit napr. kvuli inventure
|
|
if (!$seller || ($seller['data']['orders_disabled'] ?? 'N') === 'Y') {
|
|
throw new DeliveryException(
|
|
translate('seller_sellerOrdersDisabled', 'pompo'),
|
|
translate('seller_sellerOrdersDisabled', 'pompo'),
|
|
DeliveryException::ERROR_SOFT
|
|
);
|
|
}
|
|
|
|
// zvaliduju produkty v PurchaseState
|
|
foreach ($this->getPurchaseStateProducts($purchaseState, $sellerId) as $item) {
|
|
$product = $item->getProduct();
|
|
$pieces = (float) $item->getPieces();
|
|
|
|
// objednavam na prodejnu a nemam to skladem na centrala nebo na vybrane prodejne - je to nejspis skladem pouze na jine prodejne
|
|
if ($seller['id_store'] != $this->configuration->getMainStoreId()) {
|
|
$totalSellerStore = ($product->inStoreMain ?? 0) + ($product->inStoreSupplier ?? 0) + max($product->inStoreSeller ?? 0, 0);
|
|
} else {
|
|
$totalSellerStore = ($product->inStoreMain ?? 0) + ($product->inStoreSupplier ?? 0);
|
|
}
|
|
// musim mit vybranou dopravu "Osobni odber" a prodejnu, abych mohl zacit spoustet validaci
|
|
if ($selectedDeliveryType && $sellerId) {
|
|
// kontrola restrikci, pokud mam dostatecny sklad, ale neni dostatek kusu na prodejne a bude se muset zavazet z centralniho skladu
|
|
// a teprve v takovou chvili muzu spustit kontrolu restrikci
|
|
if ($totalSellerStore >= $pieces && ($product->inStoreSeller ?? 0) < $pieces) {
|
|
try {
|
|
// povolit a zkontrolovat restrikce
|
|
$delivery->restrictionsEnabled = true;
|
|
$delivery->checkRestrictions($purchaseState);
|
|
$delivery->restrictionsEnabled = false;
|
|
} catch (DeliveryException $e) {
|
|
$delivery->restrictionsEnabled = false;
|
|
// re-throw as soft error
|
|
throw new DeliveryException(
|
|
$e->getMessage(),
|
|
$e->getShortMessage(),
|
|
DeliveryException::ERROR_SOFT
|
|
);
|
|
}
|
|
}
|
|
|
|
// kontrola dostupnosti
|
|
if ($totalSellerStore < $pieces) {
|
|
// vyhodim chybu, ale doprava "Osobni odber" neni zadisablovana, protoze si muzu chtit zmenit na jinou prodejnu
|
|
throw new DeliveryException(
|
|
// Produkt "%s" není na vybrané prodejně dostupný.
|
|
sprintf(
|
|
translate('seller_productNotInStore', 'pompo'),
|
|
trim($item->getName()),
|
|
$totalSellerStore
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// validace PurchaseStatu pro ostatni dopravce
|
|
static $products;
|
|
|
|
if (!$products) {
|
|
// pro dopravce mi staci nacist data k PurchaseStatu jen jednou, protoze tam by nemel byt zadny rozdil
|
|
$products = $this->getPurchaseStateProducts($purchaseState);
|
|
}
|
|
|
|
// zvaliduju produkty v PurchaseState
|
|
foreach ($products as $item) {
|
|
$product = $item->getProduct();
|
|
$pieces = (float) $item->getPieces();
|
|
|
|
// kontroluju jednotlive dopravce dopravce, takze skladovost je hlavni sklad + sklad dodavatele
|
|
$totalMainStore = ($product->inStoreMain ?? 0) + ($product->inStoreSupplier ?? 0);
|
|
if ($totalMainStore < $pieces) {
|
|
// pokud nemam dostatecne mnozstvi skladem, tak vyhazuju chybu, ktera disabluje i dopravy, aby nesla vybrat
|
|
$deliveryException = new DeliveryException(
|
|
// Produkt "%s" není možné objednat přes dopravce, protože je skladem pouze na vybraných prodejnách.
|
|
sprintf(
|
|
translate('productNotInMainStore', 'pompo'),
|
|
trim($item->getName()),
|
|
$totalMainStore
|
|
),
|
|
// %s - na centralnim skladu je pouze %s ks
|
|
sprintf(
|
|
translate('productNotInMainStoreShort', 'pompo'),
|
|
trim($item->getName()),
|
|
$totalMainStore
|
|
),
|
|
DeliveryException::ERROR_DELIVERY_DISABLED
|
|
);
|
|
|
|
// pokud je dana doprava vybrana, tak vyhodim chybu aby se zobrazila i cervena hlaska nad kosikem
|
|
if ($selectedDeliveryType && $selectedDeliveryType->id_delivery == $delivery->id) {
|
|
throw $deliveryException;
|
|
}
|
|
|
|
// pokud doprava vybrana neni, tak jen nastavim chybu k doprave, aby se doprava disablovala a zobrazila u ni hlaska
|
|
$delivery->exception = $deliveryException;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Nafetchuje skladovou dostupnost k prodejcum - pouziva se to v kosiku, pri vybirani prodejny pro vyzvednuti.
|
|
*
|
|
* Zaroven to ke kazdemu prodejci prida deliveryDateIncrement, kterej se v kosiku pricte k predpokladanemu datu dorucenu
|
|
*
|
|
* $availability - viz. \External\PompoBundle\Util\Ordering\ProductAvailability
|
|
*/
|
|
public function fetchSellersCartInfo(PurchaseState $purchaseState, array &$sellers): void
|
|
{
|
|
// potrebuju si nafetchovat pripadny increment u dodavatele (kdyby jediny misto, kde je skladem byl dodavatel)
|
|
$fetchProductSupplierIncrements = function (ProductCollection $products) {
|
|
$this->productUtil->fetchProductsSupplierDeliveryDateIncrement($products);
|
|
};
|
|
|
|
$giftProducts = $this->getPurchaseStateGifts($purchaseState, null);
|
|
|
|
$productQuantityMainStore = [];
|
|
$productQuantityByStore = [];
|
|
// ulozim si skladova data do pole, aby se mi s tim lepe pracovalo
|
|
foreach ($this->getPurchaseStateProducts($purchaseState, null, true, $fetchProductSupplierIncrements) as $item) {
|
|
$product = $item->getProduct();
|
|
|
|
// Skladovost hlavni sklad + dodavatel
|
|
$productQuantityMainStore[$item->getId()] = ($product->inStoreMain ?? 0) + ($product->inStoreSupplier ?? 0);
|
|
// Skladovost dodavatele
|
|
$productQuantitySupplier[$item->getId()] = $product->inStoreSupplier ?? 0;
|
|
|
|
// Skladovost na jednotlivych prodejnach
|
|
foreach ($product->storesInStore ?? [] as $storeId => $store) {
|
|
$productQuantityByStore[$storeId][$item->getId()] = $store['in_store'];
|
|
}
|
|
}
|
|
|
|
// projdu vsechny prodejny, abych k nim nacetl dostupnost
|
|
foreach ($sellers as &$seller) {
|
|
$availability = null;
|
|
$deliveryDateIncrement = 0;
|
|
$completePickupAvailable = true;
|
|
|
|
// nactu si dostupnost darku v kosiku - zajima me jestli je na prodejne dostupny, nebo musi jit z centralniho skladu
|
|
$giftsSellerAvailability = null;
|
|
foreach ($giftProducts as $giftProduct) {
|
|
if (($giftProduct->getProduct()->storesInStore[$seller['id_store']]['in_store'] ?? 0) > 0) {
|
|
$giftsSellerAvailability = $giftsSellerAvailability === null ? 1 : min(1, $giftsSellerAvailability);
|
|
} else {
|
|
$giftsSellerAvailability = $giftsSellerAvailability === null ? 0 : min(0, $giftsSellerAvailability);
|
|
}
|
|
}
|
|
|
|
// projdu produkty v kosiku
|
|
foreach ($this->getPurchaseStateProductsGrouped($purchaseState) as $item) {
|
|
$mainStoreQuantity = (float) ($productQuantityMainStore[$item->getId()] ?? 0);
|
|
$sellerStoreQuantity = (float) ($productQuantityByStore[$seller['id_store']][$item->getId()] ?? 0);
|
|
|
|
$productAvailability = $this->getSellerAvailability(
|
|
$sellerStoreQuantity,
|
|
$mainStoreQuantity,
|
|
(float) $item->getPieces()
|
|
);
|
|
|
|
// pokud to ma byt zavoz na prodejnu, tak musim udelat kontrolu restrikci
|
|
if ($productAvailability === ProductAvailability::SELLER_IN_STORE_TRANSFER) {
|
|
// kontrola restrikci, pokud mam dostatecny sklad, ale neni dostatek kusu na prodejne a bude se muset zavazet z centralniho skladu
|
|
if (($mainStoreQuantity + $sellerStoreQuantity) > $item->getPieces() && $sellerStoreQuantity < $item->getPieces()) {
|
|
// najdu si dopravu "OdberNaProdejne"
|
|
$inPersonDeliveries = array_filter(\Delivery::getAll(), fn ($x) => $x instanceof \OdberNaProdejne);
|
|
if (!empty($inPersonDeliveries) && ($inPersonDelivery = reset($inPersonDeliveries))) {
|
|
try {
|
|
$inPersonDelivery->restrictionsEnabled = true;
|
|
$inPersonDelivery->checkRestrictions($purchaseState);
|
|
$inPersonDelivery->restrictionsEnabled = false;
|
|
} catch (DeliveryException $e) {
|
|
// pokud restrikce nedovoluji zavoz na prodejnu, tak prodejnu oznacim jako, ze to tam neni dostupne
|
|
$productAvailability = ProductAvailability::NOT_IN_STORE;
|
|
$inPersonDelivery->restrictionsEnabled = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ukladam si, zda je mozne zavest na prodejnu jako kompletni zasilku - v pripade, ze na prodejne neni vse skladem
|
|
// tak muzu vybirat mezi "vyzvednout po castech" nebo "vyzvednout kompletni", ale k tomu, aby to slo vyzvednout komplet
|
|
// je potreba, aby vsechno bylo skladem na centralnim skladu, odkud se ta kompletni zasilka na prodejnu zaveze
|
|
if (($productQuantityMainStore[$item->getId()] ?? 0) < $item->getPieces()) {
|
|
$completePickupAvailable = false;
|
|
}
|
|
|
|
// Pokud je produkt pouze na centralnim skladu, ale neco predtim uz bylo i na prodejne, tak chci zobrazovat "castecne skladem"
|
|
if ($availability >= ProductAvailability::SELLER_IN_STORE_PARTIALLY && $productAvailability === ProductAvailability::SELLER_IN_STORE_TRANSFER) {
|
|
$productAvailability = ProductAvailability::SELLER_IN_STORE_PARTIALLY;
|
|
}
|
|
|
|
// Pokud je produkt na prodejne, ale neco predtim bylo pouze na centralnim skladu, tak chci zobrazovat "castecne skladem"
|
|
if ($availability === ProductAvailability::SELLER_IN_STORE_TRANSFER && $productAvailability >= ProductAvailability::SELLER_IN_STORE_PARTIALLY) {
|
|
$availability = ProductAvailability::SELLER_IN_STORE_PARTIALLY;
|
|
}
|
|
|
|
$availability = $availability === null ? $productAvailability : min($availability, $productAvailability);
|
|
|
|
// jakmile nema vsechny kusy produktu na prodejne, a nemam ani dostatek na centralnim skladu, tak zacinam resit deliveryDateIncrement ze skladu dodavatele
|
|
$inStoreMainWithoutSupplier = ($productQuantityMainStore[$item->getId()] ?? 0) - ($productQuantitySupplier[$item->getId()] ?? 0);
|
|
if ($availability < ProductAvailability::IN_STORE && $inStoreMainWithoutSupplier < $item->getPieces() && ($productQuantitySupplier[$item->getId()] ?? 0) > 0) {
|
|
$deliveryDateIncrement = max($deliveryDateIncrement, $item->getProduct()->supplierDeliveryDateIncrement ?? 0);
|
|
}
|
|
}
|
|
|
|
// pokud mam v kosiku darky a nektery z darku neni dostupny, tak se osobni odber musi tvarit jako zavoz
|
|
if ($giftsSellerAvailability === 0) {
|
|
$availability = ProductAvailability::SELLER_IN_STORE_TRANSFER;
|
|
}
|
|
|
|
// ulozim dostupnost na prodejce
|
|
// viz. viz. \External\PompoBundle\Util\Ordering\ProductAvailability
|
|
$seller['availability'] = $availability;
|
|
|
|
// ulozim si, zda je mozne vyzvednout jako kompletni zasilku v pripade, ze je zbozi na prodejne castecne skladem
|
|
$seller['completePickupAvailable'] = $completePickupAvailable;
|
|
|
|
// jeste si potrebuju spocitat date increment pro prodejnu a pak navysit o pripadny deliveryDateIncrement
|
|
$seller['deliveryDate'] = DateUtil::calcWorkingDays($deliveryDateIncrement, $this->getSellerDeliveryDate($seller, $availability));
|
|
// pokud je zbozi castecne skladem, tak v kosiku davame moznost rozdelit objednavku, aby neco slo vyzvednout ihned a neco az pozdeji
|
|
if ($availability === ProductAvailability::SELLER_IN_STORE_PARTIALLY) {
|
|
$seller['deliveryDatePartially'] = $this->getSellerDeliveryDate($seller, ProductAvailability::IN_STORE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Vrátí dostupnost na konkrétní prodejně.
|
|
*
|
|
* 0 - nedostupne (neni skladem nikde)
|
|
* 1 - na prodejnu zavezeme (je skladem na centralnim sklade nebo u dodavatele, takze na prodejnu muzeme zavezt)
|
|
* 2 - castecne skladem (cast je skladem na prodejne, ale cast jen na centralnim sklade nebu u dodavatele)
|
|
* 3 - skladem (vsechno je skladem na prodejne)
|
|
*/
|
|
public function getSellerAvailability(float $sellerInStore, float $mainInStore, float $pieces): int
|
|
{
|
|
$productAvailability = ProductAvailability::NOT_IN_STORE;
|
|
|
|
// Produkt je v dostatecnem mnozstvi skladem na prodejne
|
|
if ($sellerInStore >= $pieces) {
|
|
$productAvailability = ProductAvailability::IN_STORE;
|
|
// Produkt je na prodejne jen v castecnem mnozstvi
|
|
} elseif ($sellerInStore > 0) {
|
|
$productAvailability = ProductAvailability::SELLER_IN_STORE_PARTIALLY;
|
|
}
|
|
|
|
// Produkt je dostupny na centralnim skladu
|
|
if ($mainInStore > 0) {
|
|
$productAvailability = max($productAvailability, ProductAvailability::SELLER_IN_STORE_TRANSFER);
|
|
}
|
|
|
|
return $productAvailability;
|
|
}
|
|
|
|
/**
|
|
* Nacte ke vsem produktum v PurchaseStatu informace okolo skladu.
|
|
*
|
|
* @return ProductPurchaseItem[]
|
|
*/
|
|
public function getPurchaseStateProducts(PurchaseState $purchaseState, ?int $sellerId = null, bool $grouped = true, ?callable $customResultModifier = null): array
|
|
{
|
|
return $this->activateSeller(
|
|
$sellerId,
|
|
function () use ($purchaseState, $grouped, $customResultModifier) {
|
|
$products = $purchaseState->createProductCollection();
|
|
|
|
// k PurchaseStatu si nactu informace o skladu
|
|
$this->productUtil->fetchStoreInfo($products);
|
|
|
|
if ($customResultModifier) {
|
|
$customResultModifier($products);
|
|
}
|
|
|
|
return $grouped ? $this->getPurchaseStateProductsGrouped($purchaseState) : $purchaseState->getProducts();
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Vrací, zda je objednávka na osobní odběr (vyzvednutí na prodejně) nebo ne.
|
|
*/
|
|
public function isOrderInPerson(\Order $order): bool
|
|
{
|
|
if ($deliveryType = $order->getDeliveryType()) {
|
|
if ($delivery = $deliveryType->getDelivery()) {
|
|
if ($delivery instanceof \OdberNaProdejne || $delivery->isInPerson()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Vrací, zda je objednávka osobní odběr (vyzvednutí na prodejně) a prodejna má zároveň nastavený
|
|
* centrální sklad jako svůj sklad.
|
|
*
|
|
* V tu chvíli se objednávka musí tvářit jako objednávka se závozem na prodejnu, jen tam nejsou poplatky atd..
|
|
* z pohledu FE se to tedy tváří jako klasická rezervace, ale z pohledu backendu se to musí tvářít jako rezervace se závozem.
|
|
*/
|
|
public function isOrderInPersonWithMainStore(\Order $order): bool
|
|
{
|
|
if ($this->isOrderInPerson($order)) {
|
|
if ($sellerId = ($order->getDeliveryType()->getDelivery()->getPointId() ?: $order->getData('sellerId'))) {
|
|
if ($seller = $this->sellerUtil->getSeller((int) $sellerId)) {
|
|
if ($seller['id_store'] == $this->configuration->getMainStoreId()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Vrací true nebo false na základě toho, jestli je v objednávce manipulační poplatek (položka s příplatkem).
|
|
*/
|
|
public function isTransportChargeInOrder(\Order $order): bool
|
|
{
|
|
foreach ($order->fetchItems() as $item) {
|
|
if ($this->orderItemInfo->getItemType($item) === OrderItemInfo::TYPE_CHARGE) {
|
|
if (in_array($item['note']['id_charge'] ?? null, self::CHARGES_HANDLING_FEE)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function getOrderSeller(\Order $order): ?array
|
|
{
|
|
if (!$this->sellerUtil) {
|
|
return null;
|
|
}
|
|
|
|
if ($sellerId = ($order->getData('delivery_data')['seller_id'] ?? false)) {
|
|
if ($seller = $this->sellerUtil->getSeller((int) $sellerId)) {
|
|
return $seller;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Vrátí dárky, které jsou vybrány v košíku.
|
|
*/
|
|
private function getPurchaseStateGifts(PurchaseState $purchaseState, ?int $sellerId): array
|
|
{
|
|
$gifts = array_filter($purchaseState->getDiscounts(), fn ($x) => $x instanceof ProductPurchaseItem);
|
|
foreach ($purchaseState->getProducts() as $item) {
|
|
foreach ($item->getAdditionalItems() as $additionalItem) {
|
|
if ($additionalItem instanceof ProductPurchaseItem) {
|
|
$gifts[] = $additionalItem;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($gifts)) {
|
|
return [];
|
|
}
|
|
|
|
$giftProducts = Mapping::mapKeys($gifts, fn ($k, $v) => [$v->getProduct()->id, $v->getProduct()]);
|
|
|
|
$this->activateSeller($sellerId, fn () => $this->productUtil->fetchStoreInfo(new ProductCollection($giftProducts)));
|
|
|
|
return Mapping::mapKeys($gifts, fn ($k, $v) => [$v->getProduct()->id, $v]);
|
|
}
|
|
|
|
/**
|
|
* Vrátí produkty v PurchaseStatu zgroupované podle ID produkty a ID varianty.
|
|
*
|
|
* Je to hlavně kvůli košíku a kontrolám skladovosti / kontrole typu objednávky, protože můžu mít v košíku dva řádky
|
|
* stejného produktu kvůli poznámce, kde si lidé říkájí o speifickou barvu, variantu...
|
|
*/
|
|
private function getPurchaseStateProductsGrouped(PurchaseState $purchaseState): array
|
|
{
|
|
$productsGrouped = [];
|
|
|
|
foreach ($purchaseState->getProducts() as $item) {
|
|
$key = $this->getPurchaseItemKey($item);
|
|
// pokud uz produkt jednou mam, tak akorat aktualizuju pocet kusu
|
|
if ($productsGrouped[$key] ?? false) {
|
|
$itemCopy = new ProductPurchaseItem(
|
|
$productsGrouped[$key]->getIdProduct(),
|
|
$productsGrouped[$key]->getIdVariation(),
|
|
$productsGrouped[$key]->getPieces() + $item->getPieces(),
|
|
$productsGrouped[$key]->getPrice(),
|
|
$productsGrouped[$key]->getNote(),
|
|
$productsGrouped[$key]->getIdDiscount(),
|
|
);
|
|
$itemCopy->setId(
|
|
$productsGrouped[$key]->getId()
|
|
);
|
|
if ($productsGrouped[$key]->getProduct()) {
|
|
$itemCopy->setProduct($productsGrouped[$key]->getProduct());
|
|
}
|
|
$productsGrouped[$key] = $itemCopy;
|
|
|
|
continue;
|
|
}
|
|
|
|
$productsGrouped[$key] = $item;
|
|
}
|
|
|
|
return $productsGrouped;
|
|
}
|
|
|
|
/**
|
|
* Zkopíruje položku objednávky do jiné objednávky.
|
|
*/
|
|
private function copyOrderItem(\Order $newOrder, int $oldItemId, ?int $newPieces = null): void
|
|
{
|
|
$item = sqlQueryBuilder()
|
|
->select('*')
|
|
->from('order_items')
|
|
->where(Operator::equals(['id' => $oldItemId]))
|
|
->execute()->fetchAssociative();
|
|
|
|
if (!$item) {
|
|
return;
|
|
}
|
|
|
|
unset($item['id']);
|
|
|
|
$item['id_order'] = $newOrder->id;
|
|
if ($newPieces) {
|
|
$item['pieces'] = $newPieces;
|
|
$item['pieces_reserved'] = $newPieces;
|
|
$item['total_price'] = toDecimal($item['piece_price'])->mul(toDecimal($newPieces));
|
|
}
|
|
|
|
sqlQueryBuilder()
|
|
->insert('order_items')
|
|
->directValues($item)
|
|
->execute();
|
|
}
|
|
|
|
/**
|
|
* Vrací datum doručeni pro konkrétního prodejce - používá se v košíku.
|
|
*/
|
|
public function getSellerDeliveryDate(array $seller, int $availability): \DateTime
|
|
{
|
|
static $deliveryDate = null;
|
|
static $deliveryDays = 0;
|
|
|
|
$shipmentDate = null;
|
|
// Pokud je vsechno skladem na prodejne, tak si to muzu vyzvednout uz dnes.
|
|
// nebo pokud je to zavoz na prodejnu, ale prodejna ma nastaveny sklad jako Centralni sklad, tak je datum zavozu
|
|
// v podstate ten samy den, protoze se pravdepodobne jedna o "Prodejnu", ktera se nachazi na hlavnim skladu
|
|
if ($availability === ProductAvailability::IN_STORE) {
|
|
// najdu nejblizsi datum, kdy si to tam budu moct vyzvednout
|
|
$shipmentDate = $this->sellerUtil->getClosestOpenDate($seller, 3);
|
|
}
|
|
|
|
// loadnu si delivery date a delivery days dopravy OdberNaProdejne
|
|
if ($deliveryDate === null) {
|
|
$delivery = array_filter(\Delivery::getAll(), function ($d) {
|
|
return $d instanceof \OdberNaProdejne;
|
|
});
|
|
$delivery = reset($delivery);
|
|
|
|
if ($delivery) {
|
|
$deliveryDate = $delivery->getDeliveryDate() ?: new \DateTime();
|
|
$deliveryDays = $delivery->time_days ? (int) $delivery->time_days : 0;
|
|
} else {
|
|
$deliveryDate = new \DateTime();
|
|
}
|
|
}
|
|
|
|
if (!$shipmentDate) {
|
|
// najdu nejblizsi datum zavozu
|
|
$shipmentDate = $this->getSellerClosestShipmentDate($seller, $deliveryDate);
|
|
}
|
|
|
|
if (!empty($seller['data']['inventory']['from']) && !empty($seller['data']['inventory']['to'])) {
|
|
try {
|
|
$inventoryFrom = new \DateTime($seller['data']['inventory']['from']);
|
|
$inventoryTo = (new \DateTime($seller['data']['inventory']['to']))->setTime(23, 59, 59);
|
|
// pokud jsem se datumem trefil zrovna do inventury, tak musim posunout datum az za inventuru
|
|
if ($shipmentDate >= $inventoryFrom && $shipmentDate <= $inventoryTo) {
|
|
$shipmentDate = $this->getSellerClosestShipmentDate($seller, DateUtil::calcWorkingDays($deliveryDays, $inventoryTo->add(new \DateInterval('P1D'))));
|
|
}
|
|
} catch (\Throwable $e) {
|
|
}
|
|
}
|
|
|
|
// Pokud budu na prodejnu zavazet a zaroven bude centralni sklad uzavren, tak musim posunout zavoz az na jiny termin
|
|
if ($availability !== ProductAvailability::IN_STORE) {
|
|
[$closedFrom, $closedTo] = $this->productUtil->getMainStoreClosedDates();
|
|
if (!empty($closedFrom) && !empty($closedTo)) {
|
|
// Pokud je centrala zrovna uzavrena, tak zavoz posouvam
|
|
if ($shipmentDate >= $closedFrom && $shipmentDate <= $closedTo) {
|
|
$shipmentDate = $this->getSellerClosestShipmentDate($seller, DateUtil::calcWorkingDays($deliveryDays, $closedTo->add(new \DateInterval('P1D'))));
|
|
}
|
|
}
|
|
}
|
|
|
|
return $shipmentDate;
|
|
}
|
|
|
|
/**
|
|
* Vrací nejbližší datum závozu z centrálního skladu na prodejnu.
|
|
*
|
|
* @param \DateTime $date - datum od kterého se má začít nejbližší datum pro závoz hledat
|
|
*/
|
|
private function getSellerClosestShipmentDate(array $seller, \DateTime $date): \DateTime
|
|
{
|
|
// ulozim si cislo dne v tydnu
|
|
$dayIndex = $date->format('N');
|
|
|
|
$shipmentDate = clone $date;
|
|
// pokud se zavazi ve stejny ten jako je shipmentDate, tak uz nemusim hledat jiny termin zavozu
|
|
if ($seller['data']['shipment'][$dayIndex] ?? 0) {
|
|
return $shipmentDate;
|
|
}
|
|
|
|
// najit nejblizsi den zavozu
|
|
$closestDayIndex = null;
|
|
foreach (range($dayIndex, 7) as $index) {
|
|
// v dany den se nezavazi, takze pokracuju dal
|
|
if (!($seller['data']['shipment'][$index] ?? 0)) {
|
|
continue;
|
|
}
|
|
|
|
// pokud se zavasi, tak si ulozim cislo dnu v tydnu, kdy to bude
|
|
if ($index >= $dayIndex) {
|
|
$closestDayIndex = $index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// pokud jsem nenasel zadny termin zavozu, tak muzu byt napriklad u konce tydne a zavazi se jen zacatkem tydne
|
|
// takze najdu prvni den zavozu v tydnu
|
|
if ($closestDayIndex === null) {
|
|
$shipmentDays = array_keys(array_filter($seller['data']['shipment'] ?? [], function ($x) { return !empty($x); }));
|
|
$closestDayIndex = reset($shipmentDays);
|
|
}
|
|
|
|
if ($closestDayIndex) {
|
|
// shipmentDate nastavim na datum, kdy bude dalsi zavoz (napr. next Monday, coz mi upravi datum tak, aby tam bylo pristi pondeli)
|
|
$shipmentDate = $shipmentDate->modify('next '.$this->getDaysOfWeek()[$closestDayIndex]);
|
|
}
|
|
|
|
return $shipmentDate;
|
|
}
|
|
|
|
/**
|
|
* Zvaliduje skladovost produktu vuci DataGo.
|
|
*/
|
|
public function checkPurchaseStateStockDataGo(PurchaseState $purchaseState): void
|
|
{
|
|
$items = [];
|
|
// pripravim si pole polozek
|
|
foreach ($purchaseState->getProducts() as $item) {
|
|
if (empty($item->getProduct()->code)) {
|
|
continue;
|
|
}
|
|
|
|
$items[] = [
|
|
'number' => $item->getProduct()->code,
|
|
'quantity' => $item->getPieces(),
|
|
];
|
|
}
|
|
|
|
if (empty($items)) {
|
|
return;
|
|
}
|
|
|
|
$data = [
|
|
'currency' => Contexts::get(CurrencyContext::class)->getActiveId(),
|
|
'items' => [
|
|
'item' => $items,
|
|
],
|
|
];
|
|
|
|
[$user, $pass] = $this->pompoUtil->getDataGoDefaultCredentials();
|
|
|
|
$this->dataGoApi->setCredentials($user, $pass);
|
|
|
|
// zkontroluju, ze je DataGo API dostupne, a pokud neni, tak neprovadim kontrolu
|
|
if (!$this->dataGoApi->isApiAvailable()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// odesilam request na DataGo
|
|
$result = $this->dataGoApi->checkItems($data);
|
|
} catch (\Throwable $e) {
|
|
if (isDevelopment()) {
|
|
throw $e;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (ArrayUtil::isDictionary($result['items']['item'] ?? [])) {
|
|
$result['items']['item'] = [$result['items']['item']];
|
|
}
|
|
|
|
// process result
|
|
$stockInfo = [];
|
|
foreach ($result['items']['item'] ?? [] as $item) {
|
|
$stockInfo[$item['number']] = $item['quantity']['total'] ?? 0;
|
|
}
|
|
|
|
// overuji dostupnost podle vysledku z DataGo
|
|
foreach ($purchaseState->getProducts() as $item) {
|
|
if (($stockInfo[$item->getProduct()->code] ?? false) === false) {
|
|
continue;
|
|
}
|
|
|
|
$availableQuantity = $stockInfo[$item->getProduct()->code];
|
|
|
|
// neni na centranil skladu a ani u dodavatele
|
|
if ($availableQuantity < $item->getPieces()) {
|
|
// muze to ale byt produkt z marketplacu, takze potrebuju jeste zkontrolovat, zda neni skladem
|
|
// u dodavatele kterej je pres marketplace
|
|
if ($this->productUtil->isInStoreMarketplace($item->getProduct(), (float) $item->getPieces())) {
|
|
continue;
|
|
}
|
|
|
|
throw new PompoCartException(
|
|
// Produkt "%s" není v požadovaném množství skladem.
|
|
sprintf(translate('productNotInStore', 'pompo'), $item->getName())
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Zvaliduje skladovost vuci DRSu a DataGo.
|
|
*
|
|
* Vyplnene $sellerId znamena, ze objednavam na prodejnu. V opacnem pripade objednavam pres prepravce.
|
|
*
|
|
* 1. Pokud prijde $sellerId, tak nejdriv koukam na sklad dane prodejny a zbytky, ktere nejsou skladem potom overim vuci centrale (DataGo)
|
|
* 2. Pokud neprijde $sellerId, tak validuju pouze vuci centrale (DataGo), protoze z prodejny se neda posilat dopravcem.
|
|
*/
|
|
public function checkPurchaseStateStock(PurchaseState $purchaseState, ?int $sellerId = null): void
|
|
{
|
|
if ($sellerId) {
|
|
$branchId = sqlQueryBuilder()
|
|
->select('JSON_VALUE(s.data, "$.branchId")')
|
|
->from('stores', 's')
|
|
->join('s', 'sellers', 'sell', 's.id = sell.id_store')
|
|
->where(Operator::equals(['sell.id' => $sellerId]))
|
|
->execute()->fetchOne();
|
|
|
|
if (!$branchId) {
|
|
return;
|
|
}
|
|
|
|
// kodu produktu v kosiku
|
|
$productCodes = array_filter(array_map(function ($x) { return $x->getProduct() ? ($x->getProduct()->code ?? null) : null; }, $purchaseState->getProducts()));
|
|
|
|
$drsStockStatus = [];
|
|
// nactu si skladovost na prodejne
|
|
foreach ($this->drsApi->getStock($productCodes, (string) $branchId) as $item) {
|
|
$drsStockStatus[mb_strtolower($item['VART'])] = (int) ($item['Quantity'] ?? 0);
|
|
}
|
|
|
|
// kontroluju skladovost na prodejne
|
|
$dataGoProducts = [];
|
|
foreach ($purchaseState->getProducts() as $item) {
|
|
if (!$item->getProduct()) {
|
|
continue;
|
|
}
|
|
|
|
if (!($code = $item->getProduct()->code ?? null)) {
|
|
continue;
|
|
}
|
|
|
|
$codeLower = mb_strtolower($code);
|
|
|
|
// produkt neni skladem na prodejne
|
|
if (($drsStockStatus[$codeLower] ?? false) === false) {
|
|
// produkt budu jeste validovat vuci centrale
|
|
$dataGoProducts[] = [
|
|
'code' => $code,
|
|
'purchaseItem' => $item,
|
|
'pieces' => $item->getPieces(),
|
|
];
|
|
continue;
|
|
}
|
|
|
|
// produkt je skladem na prodejne, ale neni v dostatecnem mnozstvi
|
|
if ($drsStockStatus[$codeLower] < $item->getPieces()) {
|
|
// zbytek produktu zvaliduju jeste vuci centrale
|
|
$dataGoProducts[] = [
|
|
'code' => $code,
|
|
'purchaseItem' => $item,
|
|
'pieces' => ($item->getPieces() - $drsStockStatus[$codeLower]),
|
|
];
|
|
}
|
|
}
|
|
|
|
// zbytek zvaliduju vuci DataGo, protoze kdyz neco nemam na prodejne, tak to muzu mit na centrale a na
|
|
// prodejnu se to muze prevezt
|
|
if (!empty($dataGoProducts)) {
|
|
$purchaseItems = [];
|
|
foreach ($dataGoProducts as $item) {
|
|
$originalPurchaseItem = $item['purchaseItem'];
|
|
$purchaseItem = new ProductPurchaseItem(
|
|
$originalPurchaseItem->getIdProduct(),
|
|
$originalPurchaseItem->getIdVariation(),
|
|
$item['pieces'],
|
|
$originalPurchaseItem->getPrice(),
|
|
$originalPurchaseItem->getNote(),
|
|
$originalPurchaseItem->getIdDiscount()
|
|
);
|
|
|
|
if ($originalPurchaseItem->getProduct()) {
|
|
$purchaseItem->setProduct($originalPurchaseItem->getProduct());
|
|
}
|
|
|
|
$purchaseItems[] = $purchaseItem;
|
|
}
|
|
|
|
// spustim validaci zbylych produktu vuci centrale
|
|
$this->checkPurchaseStateStockDataGo(
|
|
new PurchaseState($purchaseItems)
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// zvaliduju cely PurchaseState vuci centrale
|
|
$this->checkPurchaseStateStockDataGo($purchaseState);
|
|
}
|
|
|
|
/**
|
|
* Vrací ID PurchaseItemu složené z ID produktu a ID varianty.
|
|
*/
|
|
private function getPurchaseItemKey(ProductPurchaseItem $item): string
|
|
{
|
|
$key = $item->getIdProduct();
|
|
if ($item->getIdVariation()) {
|
|
$key .= '/'.$item->getIdVariation();
|
|
}
|
|
|
|
return (string) $key;
|
|
}
|
|
|
|
/**
|
|
* Pomocná funkce, která vrací dny v týdnu.
|
|
*/
|
|
private function getDaysOfWeek(): array
|
|
{
|
|
return [
|
|
1 => 'Monday',
|
|
2 => 'Tuesday',
|
|
3 => 'Wednesday',
|
|
4 => 'Thursday',
|
|
5 => 'Friday',
|
|
6 => 'Saturday',
|
|
7 => 'Sunday',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Pomocná funkce, která slouží k aktivaci konkrétní prodejny.
|
|
*/
|
|
private function activateSeller(?int $sellerId, callable $callback)
|
|
{
|
|
if (!$sellerId) {
|
|
return $callback();
|
|
}
|
|
|
|
$sellerContext = Contexts::get(SellerContext::class);
|
|
|
|
$originalSellerId = $sellerContext->getActiveId();
|
|
$sellerContext->activate((string) $sellerId);
|
|
|
|
$result = $callback();
|
|
|
|
$sellerContext->activate($originalSellerId);
|
|
|
|
return $result;
|
|
}
|
|
}
|