getIdFromCode($transferCode); $box_code = $box; $box = sqlQueryBuilder()->select('wpos.id, st.id as transfer_id, wo.id_order', 'COUNT(wp.id_product) products, wpos.code') ->from('warehouse_positions', 'wpos') ->leftJoin('wpos', 'stores_transfers', 'st', 'wpos.id = st.id_position') ->leftJoin('wpos', 'warehouse_orders', 'wo', 'wpos.id = wo.id_position') ->leftJoin('wpos', 'warehouse_products', 'wp', 'wp.id_position = wpos.id') ->where(Operator::like(['wpos.code' => $box, 'wpos.id_location' => StoreItemWorker::LOCATION_BOX])) ->groupBy('wpos.id') ->execute()->fetch(); $transfer = sqlQueryBuilder()->select('st.*, wp.code, s.type as store_type') ->from('stores_transfers', 'st') ->join('st', 'stores', 's', 'st.id_store_from = s.id') ->leftJoin('st', 'warehouse_positions', 'wp', 'wp.id = st.id_position') ->where(Operator::equals(['st.id' => $transferId])) ->groupBy('st.id') ->execute()->fetch(); if (!in_array($transfer['state'], ['O'])) { return $this->storeItemWorker->returnError('Převodku nelze vychystat, protože je v nesprávném stavu!'); } if ($transfer['store_type'] == StoresInStore::TYPE_EXTERNAL_STORE) { return $this->storeItemWorker->returnError('Převodku nelze vychystat, protože je z externího skladu!'); } if (!$box) { return $this->storeItemWorker->returnError("Přepravka {$box_code} neexistuje!"); } elseif ($box['transfer_id'] && $box['transfer_id'] != $transferId) { return $this->storeItemWorker->returnError("Přepravka {$box_code} už obsahuje převodku {$box['transfer_id']}!"); } elseif ($box['id_order']) { return $this->storeItemWorker->returnError("Přepravka {$box_code} už obsahuje objednávku {$box['id_order']}!"); } elseif (($transfer['code'] ?? false) && $box['code'] != $transfer['code']) { return $this->storeItemWorker->returnError("Převodka už je vychystávána do přepravky {$transfer['code']}!"); } // Allow continuing of picking if (empty($transfer['code'])) { // Assert box is empty if (!$transfer['checkouts']) { assert((int) returnSQLResult('SELECT COUNT(*) FROM warehouse_products WHERE id_position=:id', ['id' => $box['id']]) === 0, 'Box for new order is empty'); } // Assign box to transfer $update = sqlQueryBuilder()->update('stores_transfers')->set('id_position', $box['id'])->where(Operator::equals(['id' => $transferId]))->execute(); assert($update == 1, 'StoreTransfer: neaktualizoval se box u transferu'); } [$checkouts, $items] = $this->getCheckoutItems($transferId); // // Log to order used BOX // $order->logHistory("Objednávka se vychystává do přepravky: {$box_code}", false); $order = [ 'id' => $transferCode, 'id_warehouse' => $transferCode, 'order_no' => $transferCode, 'items' => array_values($items), // Pass as array to preserve ordering ]; $state = json_decode_strict($transfer['checkouts'] ?? '{}'); $state->checkouts = $checkouts; return [ 'order' => $order, 'state' => $state, 'result' => !empty($order), ]; } public function checkTransferMatchesBoxContent($transferId): array { $transfer = sqlQueryBuilder() ->select('st.*, s.type') ->from('stores_transfers', 'st') ->join('st', 'stores', 's', 's.id = st.id_store_from') ->where(Operator::equals(['st.id' => $transferId])) ->execute()->fetch(); if ($transfer['type'] == StoresInStore::TYPE_EXTERNAL_STORE) { return []; } $transferItems = sqlQueryBuilder()->select('sti.id_product, NULLIF(sti.id_variation, 0) as id_variation, SUM(sti.quantity) as pieces, sti.id as id_item') ->addSelect('(SELECT CONCAT(wl.sort_index, "|", wpos.code) FROM warehouse_locations wl JOIN warehouse_positions wpos ON wpos.id_location = wl.id AND wpos.id_location > 2 JOIN warehouse_products wp ON wpos.id = wp.id_position WHERE sti.id_product = wp.id_product AND (sti.id_variation=wp.id_variation OR wp.id_variation IS NULL) ORDER BY wp.pieces > 0 DESC, wp.over_supply = "N" DESC, wl.sort_index ASC, wp.pieces ASC, wpos.id ASC LIMIT 1) AS position') ->from('stores_transfers_items', 'sti') ->where(Operator::equals(['sti.id_stores_transfer' => $transferId])) ->andWhere('sti.id_product IS NOT NULL') ->groupBy('sti.id_product, NULLIF(sti.id_variation, 0)') ->execute()->fetchAll(); $box_items = sqlQueryBuilder()->select('wp.id_product, wp.id_variation, SUM(wp.pieces) as pieces') ->addSelect('(SELECT CONCAT(MIN(wl.sort_index), "|", MIN(wpos.code)) FROM warehouse_locations wl JOIN warehouse_positions wpos ON wpos.id_location = wl.id WHERE wpos.id = wp.id_position) AS position') ->from('warehouse_products', 'wp') ->where(Operator::equals(['id_position' => $transfer['id_position']])) ->groupBy('wp.id_product, wp.id_variation') ->execute()->fetchAll(); $errors = $this->storeItemWorker->compareItemsVsBox($transferItems, $box_items); return $errors; } /** * @return array * * @throws \Exception * @throws \Throwable */ public function commit($transferCode, &$state) { $transferId = $this->getIdFromCode($transferCode); sqlGetConnection()->transactional(function () use ($transferId, &$state, &$return) { $checkouts = &$state->checkouts; $transfer = sqlQueryBuilder()->select('*') ->from('stores_transfers') ->where(Operator::equals(['id' => $transferId])) ->execute() ->fetch(); if (!$transfer) { return $return = $this->storeItemWorker->returnError('Nemohu najít rozpracovanou převodku!'); } $commits = json_decode_strict($transfer['checkouts'] ?? '{}', true)['commits'] ?? []; $this->logger->activateStoreTransfer($transferId, function () use ($transfer, $checkouts, &$commits) { foreach ($checkouts as &$checkedItem) { if (!($checkedItem->commited ?? false) && ($checkedItem->uuid ?? '')) { // Skip if this uuid already committed if (array_key_exists($checkedItem->uuid, $commits)) { continue; } if (strpos($checkedItem->id_item, '_')) { // V id neni id polozky, ale produkt_variata, protoze je to smazana polozka $parts = explode('_', $checkedItem->id_item); $order_item = ['id_product' => $parts[0], 'id_variation' => $parts[1] ?: null]; } else { $order_item = sqlQueryBuilder()->select('*') ->from('stores_transfers_items', 'sti') ->where(Operator::equals(['sti.id' => $checkedItem->id_item])) ->execute() ->fetch(); } $item_position = $this->storeItemWorker->getPositionIdByCode($checkedItem->position); if (findModule(\Modules::PRODUCTS_BATCHES) && ($checkedItem->productBatch ?? false)) { $order_item['id_product_batch'] = $checkedItem->productBatch->id ?? null; } $date = $checkedItem->date ?? null; if ($date) { $date = new \DateTime($date); $date->setTimezone(new \DateTimeZone(date_default_timezone_get())); } $storeItem = new StoreItem($order_item); if ($checkedItem->pieces > 0) { $commits[$checkedItem->uuid] = $this->storeItemWorker->moveBetweenPositions($storeItem, $checkedItem->pieces, $item_position, $transfer['id_position'], $date); } else { $commits[$checkedItem->uuid] = $this->storeItemWorker->moveBetweenPositions($storeItem, -$checkedItem->pieces, $transfer['id_position'], $item_position, $date); } } } }); $state->commits = $commits; // Save order status sqlQuery('UPDATE stores_transfers SET checkouts=:checkouts WHERE id=:id', ['id' => $transferId, 'checkouts' => json_encode($state)]); $return = [ 'result' => true, ]; }); return $return; } /** * @throws \Exception * @throws \Throwable */ public function finish($id_warehouse_order, $state) { return self::commit($id_warehouse_order, $state); } private function getIdFromCode($transferCode) { return str_replace('TR', '', $transferCode); } /** * @return array */ public function getCheckoutItems($transferId) { // Get box vs order difference $difference = $this->checkTransferMatchesBoxContent($transferId); // Get unfinished order items and generate checkouts list($checkouts, $items) = $this->storeItemWorker->generateCheckoutItems($difference); // Get EANs from suppliers $suppliers_eans = sqlQueryBuilder()->select('sti.id id_item, pos.ean') ->from('stores_transfers_items', 'sti') ->join('sti', 'products_of_suppliers', 'pos', 'pos.id_product = sti.id_product AND '.Operator::equalsToOrNullable('pos.id_variation', 'sti.id_variation')) ->where(Operator::inIntArray(array_map(function ($item) { return $item['id']; }, $items), 'sti.id')) ->groupBy('sti.id') ->execute(); foreach ($suppliers_eans as $row) { if (isset($items[$row['id_item']])) { $items[$row['id_item']]['eans'][] = formatEAN($row['ean']); } } return [$checkouts, $items]; } public function loadOrderStoreItems($transferId) { return $this->getCheckoutItems( $this->getIdFromCode($transferId) ); } /** * @required */ public function setLogger(Logger $logger): void { $this->logger = $logger; } /** * @required */ public function setStoreItemWorker(StoreItemWorker $storeItemWorker): void { $this->storeItemWorker = $storeItemWorker; } }