404 lines
17 KiB
PHP
404 lines
17 KiB
PHP
<?php
|
|
|
|
namespace KupShop\POSBundle\Util;
|
|
|
|
use KupShop\ContentBundle\View\Exception\ValidationException;
|
|
use KupShop\GraphQLBundle\ApiAdmin\Types\Order\OrderItem;
|
|
use KupShop\GraphQLBundle\ApiPos\Types\Order\PosOrder;
|
|
use KupShop\GraphQLBundle\ApiPos\Types\Order\PosOrderFinish;
|
|
use KupShop\GraphQLBundle\ApiPos\Types\Order\PosOrderResponse;
|
|
use KupShop\GraphQLBundle\ApiPos\Types\Purchase\PosPurchase;
|
|
use KupShop\GraphQLBundle\ApiPos\Types\User\PosUser;
|
|
use KupShop\GraphQLBundle\ApiPos\Types\Warehouse\PosOrderWarehouse;
|
|
use KupShop\GraphQLBundle\Exception\GraphQLNotFoundException;
|
|
use KupShop\KupShopBundle\Context\CurrencyContext;
|
|
use KupShop\KupShopBundle\Context\LanguageContext;
|
|
use KupShop\KupShopBundle\Context\PosContext;
|
|
use KupShop\KupShopBundle\Context\UserContext;
|
|
use KupShop\KupShopBundle\Exception\InvalidArgumentException;
|
|
use KupShop\KupShopBundle\Util\Database\QueryHint;
|
|
use KupShop\OrderDiscountBundle\Util\DiscountManager;
|
|
use KupShop\OrderingBundle\OrderList\OrderList;
|
|
use KupShop\OrderingBundle\Util\Order\OrderInfo;
|
|
use KupShop\OrderingBundle\Util\Order\OrderUtil;
|
|
use KupShop\OrderingBundle\Util\Purchase\PurchaseUtil;
|
|
use KupShop\POSBundle\Event\PosOrderEvent;
|
|
use KupShop\WarehouseBundle\Util\StoreItemWorker;
|
|
use Query\Operator;
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
|
|
|
class PosOrderUtil
|
|
{
|
|
public function __construct(
|
|
// Contexts
|
|
protected PosContext $posContext,
|
|
protected CurrencyContext $currencyContext,
|
|
protected UserContext $userContext,
|
|
protected LanguageContext $languageContext,
|
|
|
|
// Utils
|
|
protected PosUtil $posUtil,
|
|
protected OrderList $orderList,
|
|
protected PurchaseUtil $purchaseUtil,
|
|
protected DiscountManager $discountManager,
|
|
protected OrderUtil $orderUtil,
|
|
protected EventDispatcherInterface $eventDispatcher,
|
|
protected PosPurchaseStateUtil $posPurchaseStateUtil,
|
|
protected PosPaymentUtil $posPaymentUtil,
|
|
protected ?PosWarehouseUtil $posWarehouseUtil = null,
|
|
protected ?StoreItemWorker $storeItemWorker = null,
|
|
) {
|
|
}
|
|
|
|
public function getOrder(?int $id, ?string $code): PosOrder
|
|
{
|
|
$posEntity = $this->posContext->getActive();
|
|
try {
|
|
if ($id) {
|
|
$spec = Operator::equals(['o.id' => $id]);
|
|
} elseif ($code) {
|
|
$spec = Operator::equals(['o.order_no' => $code]);
|
|
} else {
|
|
throw new GraphQLNotFoundException('Objednávka nebyla nalezena.');
|
|
}
|
|
|
|
$orders = $this->orderList->andSpec($spec)
|
|
->fetchItems()
|
|
->getOrders();
|
|
|
|
if (!($order = $orders->current())) {
|
|
throw new InvalidArgumentException(sprintf('Order with CODE "%s" was not found!', $code));
|
|
}
|
|
|
|
$order->productList->fetchPhotos('product_gallery', ['Y', 'N']);
|
|
$order->productList->fetchStoresInStore(findModule(\Modules::PRODUCTS_VARIATIONS));
|
|
} catch (\InvalidArgumentException $e) {
|
|
throw new GraphQLNotFoundException('Objednávka nebyla nalezena.');
|
|
}
|
|
|
|
return new PosOrder(
|
|
order: $order,
|
|
posEntity: $posEntity,
|
|
warehouse: $this->getWarehouseOrderData($order),
|
|
payments: $this->posPaymentUtil->getOrderPayments($order->id, $order),
|
|
collection: $orders
|
|
);
|
|
}
|
|
|
|
private function getWarehouseOrderData(\Order $order): ?PosOrderWarehouse
|
|
{
|
|
if (!findModule(\Modules::WAREHOUSE)) {
|
|
return null;
|
|
}
|
|
|
|
return QueryHint::withRouteToMaster(function () use ($order) {
|
|
$warehouseOrder = $this->storeItemWorker->getWarehouseOrder($order->id);
|
|
|
|
return new PosOrderWarehouse(
|
|
warehouseOrder: $warehouseOrder ?: null,
|
|
errors: ($warehouseOrder['id'] ?? false) ? $this->storeItemWorker->checkOrderMatchesBoxContent($warehouseOrder['id']) : []
|
|
);
|
|
});
|
|
}
|
|
|
|
public function recalculateAndUpdateOrder(
|
|
array $orderItems,
|
|
array $couponItems,
|
|
array $newProducts,
|
|
array $newCoupons,
|
|
?int $appliedBonusPoints,
|
|
?PosUser $user,
|
|
?int $idOrder,
|
|
bool $savePurchase,
|
|
string $methodPayment,
|
|
string $paidPrice,
|
|
bool $purchase,
|
|
): PosPurchase {
|
|
$posEntity = $this->posContext->getActive();
|
|
$loadedCoupons = [];
|
|
$loadedProducts = [];
|
|
$posOrderResponse = null;
|
|
$saved = false;
|
|
|
|
// Loads products by scanned bar codes
|
|
$this->mergeScannedProductsToOrderItemsApi($newProducts, $orderItems, $loadedProducts);
|
|
|
|
// Create purchase state from API
|
|
$purchaseState = $this->posPurchaseStateUtil->createStateFromApi(
|
|
$orderItems,
|
|
$couponItems,
|
|
$user,
|
|
$methodPayment,
|
|
$paidPrice,
|
|
$appliedBonusPoints,
|
|
$idOrder === null
|
|
);
|
|
|
|
// Save state to order
|
|
if ($savePurchase) {
|
|
// Add POS create data
|
|
$data = $purchaseState->getCustomData();
|
|
|
|
$data['type'] = 'POS';
|
|
$data['IDPos'] = $posEntity->getId();
|
|
|
|
// Paid by invoice (skip default delivery select)
|
|
$delivery_type = null;
|
|
if (($data['methodPayment'] ?? false) && $data['methodPayment'] != 'UNDEFINED') {
|
|
$delivery_type = $this->posUtil->getPosDeliveryType($data['IDPos'], $data['methodPayment']);
|
|
}
|
|
|
|
// Sets first not null delivery
|
|
if (empty($delivery_type)) {
|
|
if ($posEntity->getCashDeliveryType()) {
|
|
$delivery_type = $this->posUtil->getPosDeliveryType($data['IDPos'], 'CASH');
|
|
} elseif ($posEntity->getCardDeliveryType()) {
|
|
$delivery_type = $this->posUtil->getPosDeliveryType($data['IDPos'], 'CARD');
|
|
} elseif ($posEntity->getInvoiceDeliveryType()) {
|
|
$delivery_type = $this->posUtil->getPosDeliveryType($data['IDPos'], 'INVOICE');
|
|
} elseif ($posEntity->getCustomDeliveryType()) {
|
|
$delivery_type = $this->posUtil->getPosDeliveryType($data['IDPos'], 'CUSTOM');
|
|
}
|
|
}
|
|
|
|
if (is_null($delivery_type)) {
|
|
throw new ValidationException('Není nastaven žádný způsob doručení');
|
|
}
|
|
|
|
$purchaseState->setDeliveryTypeId($delivery_type['id']);
|
|
$data['order']['id_delivery'] = $delivery_type['id'];
|
|
$data['order']['delivery_type'] = $delivery_type['name'];
|
|
|
|
$order = null;
|
|
// Pokud má objednávka již nastavený způsob doručení, tak editace to už nezmění (aktulizuje to dopravu a může to změnit cenu)
|
|
if ($idOrder) {
|
|
$order = new \Order();
|
|
$order->createFromDB($idOrder);
|
|
if ($order->getDeliveryId()) {
|
|
$purchaseState->setDeliveryTypeId($order->getDeliveryId());
|
|
$data['order']['id_delivery'] = $order->getDeliveryId();
|
|
$data['order']['delivery_type'] = $order->delivery_type;
|
|
}
|
|
} else {
|
|
// Zdroj nastavuju pouze na nové objednávce
|
|
$data['order']['source'] = OrderInfo::ORDER_SOURCE_POS;
|
|
}
|
|
|
|
$data['order']['pos'] = $posEntity->getId();
|
|
$data['order']['flags'] = 'POS';
|
|
$purchaseState->setCustomData($data);
|
|
|
|
// Final stock check before creating an order
|
|
$event = new PosOrderEvent($purchaseState, $posEntity->getId(), $order, $idOrder == null);
|
|
$this->eventDispatcher->dispatch($event, PosOrderEvent::PURCHASE_STATE_CHECK);
|
|
|
|
// Creates order from PurchaseState
|
|
$order = $this->purchaseUtil->createOrderFromPurchaseState($purchaseState, $idOrder);
|
|
|
|
// Final stock check before creating an order
|
|
$event->setOrder($order);
|
|
$this->eventDispatcher->dispatch($event, PosOrderEvent::PURCHASE_STATE_ORDER_CREATED);
|
|
|
|
// Creates payment with status CREATED (save purchase comes only on new order)
|
|
$uuidPayment = $this->posPaymentUtil->createOrderPayment($order, $order->getPurchaseState(), $purchase);
|
|
|
|
// Loading data results from order
|
|
$items = $this->posPurchaseStateUtil->getPurchaseItemsFromOrder($posEntity, $order);
|
|
$priceWithoutVat = $order->getTotalPrice()->getPriceWithoutVat();
|
|
$priceWithVat = $order->getTotalPrice()->getPriceWithVat();
|
|
$posOrderResponse = new PosOrderResponse(
|
|
idOrder: $order->id,
|
|
noOrder: $order->order_no,
|
|
isPaid: $order->isPaid(),
|
|
remainingPayment: $order->getRemainingPayment(),
|
|
status: $order->status,
|
|
uuidPayment: $uuidPayment,
|
|
posOrderWarehouse: $this->getWarehouseOrderData($order),
|
|
posOrderPayments: $this->posPaymentUtil->getOrderPayments($order->id, $order)
|
|
);
|
|
$saved = true;
|
|
} else {
|
|
// Loading data results from purchase state
|
|
$items = $this->posPurchaseStateUtil->getPurchaseItemsFromPurchaseState($posEntity, $purchaseState);
|
|
$priceWithoutVat = $purchaseState->getTotalPrice()->getPriceWithoutVat();
|
|
$priceWithVat = $purchaseState->getTotalPrice()->getPriceWithVat();
|
|
}
|
|
|
|
return new PosPurchase(
|
|
items: $items,
|
|
discounts: $this->posPurchaseStateUtil->getDiscountItemsFromPurchaseState($purchaseState),
|
|
usedDiscounts: $purchaseState->getUsedDiscounts(),
|
|
charges: $this->posPurchaseStateUtil->getChargesFromPurchaseState($purchaseState),
|
|
priceWithoutVat: $priceWithoutVat,
|
|
priceWithVat: $priceWithVat,
|
|
loadedProducts: $loadedProducts,
|
|
loadedCoupons: $loadedCoupons,
|
|
posOrderResponse: $posOrderResponse,
|
|
saved: $saved,
|
|
);
|
|
}
|
|
|
|
private function mergeScannedProductsToOrderItemsApi(array $newProducts, array &$orderItems, array &$loadedProducts): void
|
|
{
|
|
if ($newProducts) {
|
|
foreach ($newProducts as $code) {
|
|
if ((int) $code) {
|
|
$ean = (int) $code;
|
|
}
|
|
|
|
$sqlProduct = sqlQueryBuilder()
|
|
->select('p.id as product_id, pv.id as variation_id, p.title as product_title, pv.title as variant_title, v.vat, COALESCE(pv.price, p.price) as price')
|
|
->from('products', 'p')
|
|
->leftJoin('p', 'products_variations', 'pv', 'pv.id_product=p.id')
|
|
->leftJoin('p', 'vats', 'v', 'p.vat=v.id');
|
|
|
|
if (findModule(\Modules::SUPPLIERS)) {
|
|
$sqlProduct->addSelect('COALESCE(pos.ean, pv.ean, p.ean) as ean')
|
|
->leftJoin('p',
|
|
'products_of_suppliers',
|
|
'pos',
|
|
'pos.id_product=p.id AND (pos.id_variation = pv.id OR pv.id IS NULL)')
|
|
->andWhere('p.code LIKE :code');
|
|
|
|
if ($ean ?? false) {
|
|
$sqlProduct->orWhere('p.ean = :ean OR pv.ean = :ean OR pos.ean = :ean');
|
|
}
|
|
} else {
|
|
$sqlProduct->addSelect('COALESCE(pv.ean, p.ean) as ean')
|
|
->andWhere('p.code LIKE :code');
|
|
|
|
if ($ean ?? false) {
|
|
$sqlProduct->orWhere('p.ean = :ean OR pv.ean = :ean');
|
|
}
|
|
}
|
|
$sqlProduct->setParameter('code', "%{$code}%");
|
|
|
|
if ($ean ?? false) {
|
|
$sqlProduct->setParameter('ean', $ean);
|
|
}
|
|
|
|
if (findModule(\Modules::PRODUCTS_VARIATIONS, \Modules::SUB_CODE)) {
|
|
$sqlProduct->orWhere('pv.code LIKE :code')
|
|
->addSelect('COALESCE(pv.code, p.code) as code');
|
|
} else {
|
|
$sqlProduct->addSelect('p.code');
|
|
}
|
|
$sqlProduct = $sqlProduct->groupBy('p.id')->execute()->fetch();
|
|
if (!empty($sqlProduct)) {
|
|
$found = null;
|
|
$found_key = null;
|
|
|
|
foreach ($orderItems as $key => $oItem) {
|
|
if ($oItem->item->getItem()['idProduct'] == $sqlProduct['product_id'] && $oItem->item->getItem()['idVariation'] == $sqlProduct['variation_id']) {
|
|
$found_key = $key;
|
|
$found = true;
|
|
}
|
|
}
|
|
|
|
$orderItems[] = new OrderItem(
|
|
new \KupShop\OrderingBundle\Entity\Order\OrderItem(
|
|
[
|
|
'idProduct' => $sqlProduct['product_id'],
|
|
'idVariation' => $sqlProduct['variation_id'],
|
|
'title' => ($sqlProduct['variation_id']) ? "{$sqlProduct['product_title']} - {$sqlProduct['variant_title']}" : "{$sqlProduct['product_title']}",
|
|
'pieces' => ($found) ? $orderItems[$found_key]->item->getItem()['pieces'] + 1 : 1,
|
|
'vat' => $sqlProduct['vat'],
|
|
'priceWithoutVat' => $sqlProduct['price'],
|
|
'discount' => null,
|
|
]
|
|
)
|
|
);
|
|
unset($orderItems[$found_key]);
|
|
$loadedProducts[] = $code;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function finishOrder($orderID): PosOrderFinish
|
|
{
|
|
$requiredStatus = PosUtil::getHandledOrderStatus();
|
|
$order = new \Order();
|
|
$order->createFromDB($orderID);
|
|
$order->fetchItems();
|
|
|
|
// Nelze vyřídit objednávku 2x, špatně by de odebíraly zásoby z pozic
|
|
if ($order->status === $requiredStatus) {
|
|
throw new ValidationException('Nelze znovu vyřídit již dokončenou objednávku');
|
|
}
|
|
|
|
sqlGetConnection()->transactional(function () use (&$order, $requiredStatus) {
|
|
// Přepne status objednávky na status vyřízení (podle nastavení)
|
|
$order->changeStatus(
|
|
$requiredStatus,
|
|
'Vyřízeno v pokladně '.date(\Settings::getDateFormat().' '.\Settings::getTimeFormat(), time()),
|
|
false,
|
|
);
|
|
|
|
if (findModule(\Modules::WAREHOUSE)) {
|
|
$posEntity = $this->posContext->getActive();
|
|
$warehouseOrder = $this->storeItemWorker->getWarehouseOrder($order->id);
|
|
|
|
// Pokud má objednávka datum vychystání, tak v pokladně nejde editovat -> je vychystána
|
|
// Kvůli dokončení se nemůžou znovu přesunout zásoby a proto se přesuny mezi boxy přeskakují
|
|
if (!isset($warehouseOrder['date_close'])) {
|
|
// Box v případě že není předvychystaný box, tak se použije virtuální
|
|
$idBox = $warehouseOrder['id_position'] ?? $posEntity->getVirtualBox();
|
|
|
|
// Najde se rozdíl položek mezi boxem a objednávkou
|
|
$warehouseDiff = $this->storeItemWorker->checkOrderMatchesBoxContent(
|
|
id_warehouse_order: $warehouseOrder['id'] ?? null,
|
|
id_order: $order->id,
|
|
id_box: $idBox
|
|
);
|
|
|
|
// Pokud není záznam objednávky ve warehouse_order tak ho vytvořím (nákup)
|
|
if (is_null($warehouseOrder['id'] ?? null)) {
|
|
$this->posWarehouseUtil->createWarehouseOrderRecord($order);
|
|
}
|
|
|
|
// Vyrovnání boxu, tak aby obsah odpovídal položkám objednávky
|
|
$this->posWarehouseUtil->updateBoxToMatchOrder(
|
|
order: $order,
|
|
warehouseDiffs: $warehouseDiff,
|
|
idBox: $idBox
|
|
);
|
|
|
|
// Vysype box ven
|
|
$this->storeItemWorker->cleanCompletedBox($idBox);
|
|
}
|
|
}
|
|
});
|
|
|
|
return new PosOrderFinish(
|
|
idOrder: $order->id,
|
|
status: $order->status
|
|
);
|
|
}
|
|
|
|
public function stornoOrder($orderID): PosOrderFinish
|
|
{
|
|
$order = new \Order();
|
|
$order->createFromDB($orderID);
|
|
if ($order->order_no ?? false) {
|
|
if (!$order->isActive()) {
|
|
throw new ValidationException('Objednávka není aktivní');
|
|
}
|
|
if ($order->isClosed()) {
|
|
throw new ValidationException('Objednávka je již uzavřena');
|
|
}
|
|
if (findModule(\Modules::ORDER_PAYMENT) && abs($order->getPayments()) > 1) {
|
|
throw new ValidationException('Objednávku nelze stronovat, protože k ní existuje platba, která doposud nebyla vrácena! Nejprve vraťte platby');
|
|
}
|
|
$order->storno();
|
|
} else {
|
|
throw new ValidationException('Objednávka nebyla nalezena');
|
|
}
|
|
|
|
return new PosOrderFinish(
|
|
idOrder: $order->id,
|
|
status: $order->status_storno,
|
|
);
|
|
}
|
|
}
|