Files
kupshop/bundles/KupShop/WarehouseBundle/Util/StoreTransferWorker.php
2025-08-02 16:30:27 +02:00

283 lines
11 KiB
PHP

<?php
namespace KupShop\WarehouseBundle\Util;
use KupShop\StoresBundle\Utils\StoresInStore;
use KupShop\WarehouseBundle\Entity\StoreItem;
use Order;
use Query\Operator;
class StoreTransferWorker
{
/**
* @var Logger
*/
private $logger;
/**
* @var StoreItemWorker
*/
protected $storeItemWorker;
/**
* @return array
*/
public function startCheckout($transferCode, $box)
{
$transferId = $this->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;
}
}