283 lines
11 KiB
PHP
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;
|
|
}
|
|
}
|