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, ); } }