Change 'Rack' to 'Bin'

This commit is contained in:
Thanakorn S
2026-05-27 17:14:53 +07:00
parent ea94b6a6ae
commit 987cf7ded9
41 changed files with 931 additions and 933 deletions
+43 -43
View File
@@ -17,19 +17,19 @@ require_once __DIR__ . '/../classes_ac/PostingWindowGuard.php';
* - Order items are stored as a JSON array in td_order.items.
* No separate child table exists. SKU-level reporting is served by
* td_stock_* rows (source='order') rather than querying items JSON.
* - confirmOrder() picks racks via FIFO — oldest approved stock-in row
* still rack-occupied, ordered by td_stock_<warehouse_id>.date ASC.
* - confirmOrder() picks bins via FIFO — oldest approved stock-in row
* still bin-occupied, ordered by td_stock_<warehouse_id>.date ASC.
* - confirmOrder() creates stock-out rows with status=0 (draft).
* Warehouse staff approve them via the existing approve_stock.php
* engine, which handles rack release and balance adjustment.
* engine, which handles bin release and balance adjustment.
* - cancelOrder() sets td_order.status = -1 and soft-deletes ALL linked
* stock-out rows (status → -1) across all td_stock_* tables. If linked
* stock-out rows were already approved, their rack and balance effects are
* stock-out rows were already approved, their bin and balance effects are
* reversed before the rows are cancelled.
* Cancellation is blocked if any active invoice (status != 4 / void) or
* active return (status != -1 / cancelled) is linked to the order.
* - Cancel logic stays in the order domain, but uses WarehouseManager for
* the same rack and balance reversal rules as manual stock-out deletion.
* the same bin and balance reversal rules as manual stock-out deletion.
*
* Note: Write methods do NOT manage their own DB transactions.
* Callers must wrap multi-step operations inside dbTransaction().
@@ -78,15 +78,15 @@ class OrderManager {
}
/**
* FIFO rack pick — find the oldest approved stock-in row for a SKU
* in a given warehouse that is still rack-occupied.
* FIFO bin pick — find the oldest approved stock-in row for a SKU
* in a given warehouse that is still bin-occupied.
*
* "Oldest" = smallest date value among status=1 stock-in rows
* that have an md_rack row pointing back to them (td_stock_id IS set).
* that have an md_bin row pointing back to them (td_stock_id IS set).
*
* @param int $warehouse_id
* @param string $product_sku
* @return array|null Full td_stock row + zone/aisle/rack from md_rack,
* @return array|null Full td_stock row + zone/aisle/bin from md_bin,
* or null if no available stock in this warehouse.
*/
private function pickFifoRack(int $warehouse_id, string $product_sku): ?array
@@ -94,7 +94,7 @@ class OrderManager {
$table = $this->stockTableNameFromWarehouseId($warehouse_id);
$sth = $this->pdo->prepare(
"SELECT s.*, r.zone, r.aisle, r.rack, l.expiry_date,
"SELECT s.*, r.zone, r.aisle, r.bin, l.expiry_date,
(s.`in` - COALESCE((
SELECT SUM(o.`out`)
FROM `{$table}` o
@@ -104,7 +104,7 @@ class OrderManager {
AND o.status != -1
), 0)) AS available_qty
FROM `{$table}` s
INNER JOIN md_rack r
INNER JOIN md_bin r
ON r.company_id = s.company_id
AND r.warehouse = :warehouse_id
AND r.td_stock_id = s.id
@@ -358,7 +358,7 @@ class OrderManager {
if ($stock_out_warehouse_id > 0 && $stock_out_id > 0) {
$table = $this->stockTableNameFromWarehouseId($stock_out_warehouse_id);
$sth = $this->pdo->prepare(
"SELECT id, status, zone, aisle, rack, lot_number, serial_number
"SELECT id, status, zone, aisle, bin, lot_number, serial_number
FROM `{$table}`
WHERE company_id = :company_id
AND id = :id
@@ -374,7 +374,7 @@ class OrderManager {
if ($stock_out) {
$item['zone'] = $stock_out['zone'] ?? ($item['zone'] ?? '');
$item['aisle'] = $stock_out['aisle'] ?? ($item['aisle'] ?? '');
$item['rack'] = $stock_out['rack'] ?? ($item['rack'] ?? '');
$item['bin'] = $stock_out['bin'] ?? ($item['bin'] ?? '');
$item['lot_number'] = $stock_out['lot_number'] ?? ($item['lot_number'] ?? '');
$item['serial_number'] = $stock_out['serial_number'] ?? ($item['serial_number'] ?? '');
$item['stock_out_status'] = (int)$stock_out['status'];
@@ -599,10 +599,10 @@ class OrderManager {
/**
* Confirm a draft order — create stock-out rows (status=0) for each item
* using FIFO rack selection, then advance the order to status=1.
* using FIFO bin selection, then advance the order to status=1.
*
* Flow per item:
* 1. pickFifoRack() — find oldest rack-occupied stock-in row for the SKU.
* 1. pickFifoRack() — find oldest bin-occupied stock-in row for the SKU.
* 2. INSERT into td_stock_<warehouse_id> with type='out', status=0,
* source='order', source_id=order_id.
* 3. Write back stock_out_id into the item object in td_order.items.
@@ -611,7 +611,7 @@ class OrderManager {
* 4. UPDATE td_order.items with stock_out_id values written back.
* 5. UPDATE td_order.status = 1 (confirmed).
*
* Rack release and balance adjustment are intentionally deferred —
* Bin release and balance adjustment are intentionally deferred —
* they happen when warehouse staff approve the stock-out rows via
* the existing approve_stock.php engine (StockManager::approveStock).
*
@@ -621,7 +621,7 @@ class OrderManager {
* @param string $uuid UUID prefix — each stock-out row gets uuid_{$i}.
* @param array $logging Audit entry appended to td_order.log.
* @throws Exception If order not found, not in draft status, or any item
* has no available FIFO rack in its assigned warehouse.
* has no available FIFO bin in its assigned warehouse.
*/
public function confirmOrder(int $order_id, string $uuid, array $logging, bool $auto_approve = false): void
{
@@ -668,22 +668,22 @@ class OrderManager {
continue;
}
$rack_stock = $this->pickFifoRack($warehouse_id, $product_sku);
if (!$rack_stock) {
$bin_stock = $this->pickFifoRack($warehouse_id, $product_sku);
if (!$bin_stock) {
$preflight_errors[] = "No available stock for \"{$name}\" in the selected warehouse.";
continue;
}
$available_qty = (float)($rack_stock['available_qty'] ?? $rack_stock['in'] ?? 0);
$available_qty = (float)($bin_stock['available_qty'] ?? $bin_stock['in'] ?? 0);
if ($quantity - $available_qty > 0.000001) {
$preflight_errors[] =
"Insufficient stock for \"{$name}\": " .
"{$available_qty} available, {$quantity} requested.";
}
$expiry = $rack_stock['expiry_date'] ?? null;
$expiry = $bin_stock['expiry_date'] ?? null;
if ($expiry && strtotime($expiry) < strtotime(date('Y-m-d'))) {
$lot = $rack_stock['lot_number'] ?? '';
$lot = $bin_stock['lot_number'] ?? '';
$preflight_errors[] =
"Lot {$lot} for \"{$name}\" expired on {$expiry} and cannot be picked.";
}
@@ -699,8 +699,8 @@ class OrderManager {
$product_sku = $item['product_sku'] ?? '';
$quantity = (float)($item['quantity'] ?? 0);
$rack_stock = $this->pickFifoRack($warehouse_id, $product_sku);
$available_qty = (float)($rack_stock['available_qty'] ?? $rack_stock['in'] ?? 0);
$bin_stock = $this->pickFifoRack($warehouse_id, $product_sku);
$available_qty = (float)($bin_stock['available_qty'] ?? $bin_stock['in'] ?? 0);
$table = $this->stockTableNameFromWarehouseId($warehouse_id);
$item_uuid = $uuid . '_' . $i;
@@ -709,13 +709,13 @@ class OrderManager {
$this->pdo->prepare(
"INSERT INTO `{$table}`
(uuid, company_id, `date`, product_sku, `out`,
zone, aisle, rack,
zone, aisle, bin,
contact_id, `description`, `log`, `type`,
ref_id, lot_number, serial_number,
source, source_id, price, status)
VALUES
(:uuid, :company_id, :date, :product_sku, :quantity,
:zone, :aisle, :rack,
:zone, :aisle, :bin,
:contact_id, :description, :log, 'out',
:ref_id, :lot_number, :serial_number,
'order', :source_id, :price, 0)"
@@ -725,28 +725,28 @@ class OrderManager {
':date' => date('Y-m-d H:i:s'),
':product_sku' => $product_sku,
':quantity' => $quantity,
':zone' => $rack_stock['zone'],
':aisle' => $rack_stock['aisle'],
':rack' => $rack_stock['rack'],
':zone' => $bin_stock['zone'],
':aisle' => $bin_stock['aisle'],
':bin' => $bin_stock['bin'],
':contact_id' => (int)$order['contact_id'],
':description' => $order['order_number'],
':log' => json_encode($item_log),
':ref_id' => (int)$rack_stock['id'],
':lot_number' => $rack_stock['lot_number'] ?? '',
':serial_number' => $rack_stock['serial_number'] ?? '',
':ref_id' => (int)$bin_stock['id'],
':lot_number' => $bin_stock['lot_number'] ?? '',
':serial_number' => $bin_stock['serial_number'] ?? '',
':source_id' => $order_id,
':price' => (float)($item['unit_price'] ?? 0),
]);
// Write rack context + stock_out_id back into the item object
$item['zone'] = $rack_stock['zone'];
$item['aisle'] = $rack_stock['aisle'];
$item['rack'] = $rack_stock['rack'];
$item['lot_number'] = $rack_stock['lot_number'] ?? '';
$item['serial_number'] = $rack_stock['serial_number'] ?? '';
// Write bin context + stock_out_id back into the item object
$item['zone'] = $bin_stock['zone'];
$item['aisle'] = $bin_stock['aisle'];
$item['bin'] = $bin_stock['bin'];
$item['lot_number'] = $bin_stock['lot_number'] ?? '';
$item['serial_number'] = $bin_stock['serial_number'] ?? '';
$item['stock_out_id'] = (int)$this->pdo->lastInsertId();
// Auto-approve: immediately release rack + adjust balance
// Auto-approve: immediately release bin + adjust balance
if ($auto_approve) {
$stock = new StockManager($this->pdo, $this->company_id);
$whMgmt = new WarehouseManager($this->pdo, $this->company_id);
@@ -802,7 +802,7 @@ class OrderManager {
* - td_return where order_id = $order_id AND status != -1
*
* Stock-out rows are children of the order — they follow the parent.
* On cancel, approved rows first re-occupy the original source rack and
* On cancel, approved rows first re-occupy the original source bin and
* reverse the stock-out balance. Then ALL stock-out rows linked to this
* order (any status except already -1) are soft-deleted (status → -1)
* across all td_stock_* tables.
@@ -882,7 +882,7 @@ class OrderManager {
$warehouse_id = (int)$matches[1];
$row_sth = $this->pdo->prepare(
"SELECT id, product_sku, `out`, zone, aisle, rack, ref_id, status, `date`
"SELECT id, product_sku, `out`, zone, aisle, bin, ref_id, status, `date`
FROM `{$table}`
WHERE company_id = :company_id
AND source = 'order'
@@ -903,11 +903,11 @@ class OrderManager {
$whMgmt->assertStockMovementWindow($row['date'] ?? null, 'Order stock cancellation');
$whMgmt->occupyRack(
$whMgmt->occupyBin(
$warehouse_id,
(string)$row['zone'],
(string)$row['aisle'],
(string)$row['rack'],
(string)$row['bin'],
(string)$row['product_sku'],
(int)$row['ref_id']
);