diff --git a/app/assets/js/custom.js b/app/assets/js/custom.js index 1ae5dfa..0a54c2e 100644 --- a/app/assets/js/custom.js +++ b/app/assets/js/custom.js @@ -543,7 +543,7 @@ function load_location_config(callback) { onSuccess: function(res) { var s = res.output; advanced = s.advanced_location == 1; - label_rack = s.location_label_rack || (advanced ? 'Rack' : 'Location'); + label_bin = s.location_label_bin || (advanced ? 'Bin' : 'Location'); label_zone = s.location_label_zone || 'Zone'; label_aisle = s.location_label_aisle || 'Aisle'; if (callback) callback(); @@ -1156,9 +1156,9 @@ function show_stock_rows(source, source_id, label) { '0':'Draft', '1':'Approved'}[String(r.status)] || '—'; - var location = [r.zone, r.aisle, r.rack].filter(function(v) { - return v && v !== r.rack; - }).concat([r.rack]).filter(Boolean).join(' / '); + var location = [r.zone, r.aisle, r.bin].filter(function(v) { + return v && v !== r.bin; + }).concat([r.bin]).filter(Boolean).join(' / '); html += ` ${type_badge} ${escape_html(r.product_name || r.product_sku)}
${escape_html(r.product_sku)} diff --git a/app/assets/utils/classes/BarcodeManager.php b/app/assets/utils/classes/BarcodeManager.php index c871a66..7730375 100644 --- a/app/assets/utils/classes/BarcodeManager.php +++ b/app/assets/utils/classes/BarcodeManager.php @@ -121,17 +121,17 @@ class BarcodeManager { } /** - * Confirm a rack row exists for the given warehouse + zone + aisle + rack. + * Confirm a bin row exists for the given warehouse + zone + aisle + bin. * Throws if not found. */ - private function validateLocation(int $warehouse_id, string $zone, string $aisle, string $rack): void { + private function validateLocation(int $warehouse_id, string $zone, string $aisle, string $bin): void { $sth = $this->pdo->prepare( - "SELECT 1 FROM md_rack + "SELECT 1 FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse AND COALESCE(zone, '') = :zone AND COALESCE(aisle,'') = :aisle - AND rack = :rack + AND bin = :bin LIMIT 1" ); $sth->execute([ @@ -139,7 +139,7 @@ class BarcodeManager { ':warehouse' => $warehouse_id, ':zone' => $zone, ':aisle' => $aisle, - ':rack' => $rack, + ':bin' => $bin, ]); if (!$sth->fetchColumn()) throw new Exception('Location not found.'); } @@ -343,7 +343,7 @@ class BarcodeManager { */ public function listLocationLabels(): array { $sth = $this->pdo->prepare( - "SELECT barcode, warehouse_id, zone, aisle, rack, status, print_count, last_printed_dt + "SELECT barcode, warehouse_id, zone, aisle, bin, status, print_count, last_printed_dt FROM md_barcode WHERE company_id = :company_id AND barcode_type = 'loc'" @@ -355,30 +355,30 @@ class BarcodeManager { /** * Create a new location barcode label (or re-activate a previously disabled one). * - * Validates that the rack exists in md_rack before inserting. + * Validates that the bin exists in md_bin before inserting. * * @param string $barcode Full barcode string (e.g. "LOC|1|A|1|R1"). * @param int $warehouse_id Warehouse ID. * @param string $zone Zone code. * @param string $aisle Aisle code. - * @param string $rack Rack code. - * @return array Keys: barcode, warehouse_id, zone, aisle, rack. - * @throws Exception If the location is not found in md_rack. + * @param string $bin Bin code. + * @return array Keys: barcode, warehouse_id, zone, aisle, bin. + * @throws Exception If the location is not found in md_bin. */ - public function createLocationLabel(string $barcode, int $warehouse_id, string $zone, string $aisle, string $rack): array { - $this->validateLocation($warehouse_id, $zone, $aisle, $rack); + public function createLocationLabel(string $barcode, int $warehouse_id, string $zone, string $aisle, string $bin): array { + $this->validateLocation($warehouse_id, $zone, $aisle, $bin); $this->pdo->prepare( "INSERT INTO md_barcode - (company_id, barcode_type, barcode, warehouse_id, zone, aisle, rack) + (company_id, barcode_type, barcode, warehouse_id, zone, aisle, bin) VALUES - (:company_id, 'loc', :barcode, :warehouse_id, :zone, :aisle, :rack) + (:company_id, 'loc', :barcode, :warehouse_id, :zone, :aisle, :bin) ON DUPLICATE KEY UPDATE barcode_type = VALUES(barcode_type), warehouse_id = VALUES(warehouse_id), zone = VALUES(zone), aisle = VALUES(aisle), - rack = VALUES(rack), + bin = VALUES(bin), status = 1" )->execute([ ':company_id' => $this->company_id, @@ -386,7 +386,7 @@ class BarcodeManager { ':warehouse_id' => $warehouse_id, ':zone' => $zone, ':aisle' => $aisle, - ':rack' => $rack, + ':bin' => $bin, ]); return [ @@ -394,7 +394,7 @@ class BarcodeManager { 'warehouse_id' => $warehouse_id, 'zone' => $zone, 'aisle' => $aisle, - 'rack' => $rack, + 'bin' => $bin, ]; } @@ -465,7 +465,7 @@ class BarcodeManager { * resolve it from the caller via CompanySettingManager::get('advanced_location'). * * @param string $barcode Full barcode string to resolve. - * @param bool $advancedLocation True = zone/aisle/rack parts; false = rack only. + * @param bool $advancedLocation True = zone/aisle/bin parts; false = bin only. * @return array * @throws Exception On parse or validation failure. */ @@ -515,13 +515,13 @@ class BarcodeManager { if ($advancedLocation) { $zone = trim($parts[2]); $aisle = trim($parts[3]); - $rack = trim($parts[4]); + $bin = trim($parts[4]); } else { - $rack = trim($parts[2]); - $zone = $rack; - $aisle = $rack; + $bin = trim($parts[2]); + $zone = $bin; + $aisle = $bin; } - if (!$warehouse_id || $zone === '' || $aisle === '' || $rack === '') { + if (!$warehouse_id || $zone === '' || $aisle === '' || $bin === '') { throw new Exception('Location label is incomplete.'); } @@ -531,19 +531,19 @@ class BarcodeManager { (int)$label['warehouse_id'] !== $warehouse_id || $label['zone'] !== $zone || $label['aisle'] !== $aisle || - $label['rack'] !== $rack + $label['bin'] !== $bin ) { throw new Exception('Location barcode label does not match its registry.'); } - $this->validateLocation($warehouse_id, $zone, $aisle, $rack); + $this->validateLocation($warehouse_id, $zone, $aisle, $bin); return [ 'type' => 'loc', 'warehouse_id' => $warehouse_id, 'zone' => $zone, 'aisle' => $aisle, - 'rack' => $rack, + 'bin' => $bin, ]; } diff --git a/app/assets/utils/classes/CompanySettingManager.php b/app/assets/utils/classes/CompanySettingManager.php index 6a836d8..deb4808 100644 --- a/app/assets/utils/classes/CompanySettingManager.php +++ b/app/assets/utils/classes/CompanySettingManager.php @@ -8,8 +8,8 @@ * default_stock_status int 0 = draft, 1 = auto-approve on save * auto_invoice_and_credit_note int 0 = manual, 1 = auto-generate * auto_complete_on_ship int 0 = manual, 1 = auto-complete on shipped - * advanced_location int 0 = simple (single location), 1 = zone/aisle/rack - * location_label_rack string label for rack / single location field + * advanced_location int 0 = simple (single location), 1 = zone/aisle/bin + * location_label_bin string label for bin / single location field * location_label_zone string label for zone (advanced only) * location_label_aisle string label for aisle (advanced only) * @@ -27,7 +27,7 @@ class CompanySettingManager 'auto_invoice_and_credit_note' => 0, // manual by default 'auto_complete_on_ship' => 1, // auto-complete by default 'advanced_location' => 0, // simple mode by default - 'location_label_rack' => 'Location', // single label default + 'location_label_bin' => 'Location', // single label default 'location_label_zone' => 'Zone', // advanced level 1 default 'location_label_aisle' => 'Aisle', // advanced level 2 default 'posting_open_from' => '', // YYYY-MM-DD; empty = no lower bound diff --git a/app/assets/utils/classes/OrderManager.php b/app/assets/utils/classes/OrderManager.php index d5b029b..b07e94f 100644 --- a/app/assets/utils/classes/OrderManager.php +++ b/app/assets/utils/classes/OrderManager.php @@ -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_.date ASC. + * - confirmOrder() picks bins via FIFO — oldest approved stock-in row + * still bin-occupied, ordered by td_stock_.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_ 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'] ); diff --git a/app/assets/utils/classes/ProductManager.php b/app/assets/utils/classes/ProductManager.php index 130bb54..faa6a04 100644 --- a/app/assets/utils/classes/ProductManager.php +++ b/app/assets/utils/classes/ProductManager.php @@ -534,18 +534,18 @@ class ProductManager { // ───────────────────────────────────────────────────────────── /** - * Return all rack slots across all warehouses with occupancy status, + * Return all bin slots across all warehouses with occupancy status, * warehouse name, and product name. * - * Used by the rack occupancy dashboard to visualise which racks are + * Used by the bin occupancy dashboard to visualise which bins are * empty vs. occupied, and which product is in each slot. - * Results are ordered by warehouse → zone → aisle → rack, with + * Results are ordered by warehouse → zone → aisle → bin, with * numeric-first sorting via CAST so e.g. "2" sorts before "10". * * Security fix: was previously using $this->companyId (undefined property), * corrected to $this->company_id. * - * @return array All md_rack rows joined to warehouse and product, with 'status' field. + * @return array All md_bin rows joined to warehouse and product, with 'status' field. */ public function getRackOccupancy(): array { @@ -554,14 +554,14 @@ class ProductManager { r.id, r.zone, r.aisle, - r.rack, + r.bin, r.product_sku, r.td_stock_id, mw.warehouse_name, mw.id AS warehouse_id, p.product_name, CASE WHEN r.product_sku IS NOT NULL THEN 'occupied' ELSE 'empty' END AS status - FROM md_rack r + FROM md_bin r INNER JOIN md_warehouse mw ON mw.company_id = r.company_id AND mw.id = r.warehouse @@ -571,7 +571,7 @@ class ProductManager { WHERE r.company_id = :company_id ORDER BY mw.warehouse_name, r.zone, CAST(r.aisle AS UNSIGNED), r.aisle, - CAST(r.rack AS UNSIGNED), r.rack" + CAST(r.bin AS UNSIGNED), r.bin" ); $sth->execute([':company_id' => $this->company_id]); return $sth->fetchAll(PDO::FETCH_ASSOC); diff --git a/app/assets/utils/classes/PurchaseOrderManager.php b/app/assets/utils/classes/PurchaseOrderManager.php index 3f93ea1..c45317a 100644 --- a/app/assets/utils/classes/PurchaseOrderManager.php +++ b/app/assets/utils/classes/PurchaseOrderManager.php @@ -25,7 +25,7 @@ require_once __DIR__ . '/../classes_ac/PostingWindowGuard.php'; * Receipt progress (pending/partial/received) is derived via deriveReceiptStatus(), * same pattern as fulfillment_status on SO. Never stored in td_purchase_order. * - cancelPo() is blocked if any linked stock-in rows have been approved (status=1), - * because those have already modified rack and balance. + * because those have already modified bin and balance. * - Write methods do NOT manage their own DB transactions. * Callers must wrap multi-step operations inside dbTransaction(). * @@ -274,7 +274,7 @@ class PurchaseOrderManager { 'warehouse_id' => (int)($item['receive_wh'] ?? 0), 'zone' => $item['receive_zone'] ?? '', 'aisle' => $item['receive_aisle'] ?? '', - 'rack' => $item['receive_rack'] ?? '', + 'bin' => $item['receive_bin'] ?? '', ]); } } @@ -492,7 +492,7 @@ class PurchaseOrderManager { * * @param int $po_id td_purchase_order.id * @param array $receive_items Each item: { item_id, product_sku, warehouse_id, - * quantity, zone, aisle, rack, lot_number, + * quantity, zone, aisle, bin, lot_number, * expiry_date, serial_number, contact_id } * @param string $uuid UUID prefix for stock-in rows. * @param array $logging Audit entry. @@ -573,16 +573,16 @@ class PurchaseOrderManager { $item_uuid = $uuid . '_' . $j; $item_log = array_merge($logging, ['action' => 'receive_item']); - $rack = $recv['rack'] ?? ''; + $bin = $recv['bin'] ?? ''; $zone = $recv['zone'] ?? ''; $aisle = $recv['aisle'] ?? ''; - // Simple location mode: zone and aisle must mirror the rack value + // Simple location mode: zone and aisle must mirror the bin value // (same convention as manage_stock_in.php). - // occupyRack() looks up md_rack WHERE zone=:zone AND aisle=:aisle AND rack=:rack, - // so all three must match — a blank zone/aisle produces "Rack --b does not exist". - if ($zone === '' && $rack !== '') $zone = $rack; - if ($aisle === '' && $rack !== '') $aisle = $rack; + // occupyBin() looks up md_bin WHERE zone=:zone AND aisle=:aisle AND bin=:bin, + // so all three must match — a blank zone/aisle produces "Bin --b does not exist". + if ($zone === '' && $bin !== '') $zone = $bin; + if ($aisle === '' && $bin !== '') $aisle = $bin; // Resolve unit_price: prefer the receive-time override, fall back to PO item price $po_item_price = 0; @@ -599,7 +599,7 @@ class PurchaseOrderManager { 'price' => $unit_price, 'zone' => $zone, 'aisle' => $aisle, - 'rack' => $rack, + 'bin' => $bin, 'lot_number' => $recv['lot_number'] ?? '', 'expiry_date' => $recv['expiry_date'] ?? '', 'serial_number' => $recv['serial_number'] ?? '', @@ -626,7 +626,7 @@ class PurchaseOrderManager { ]); // approveStock() handles balance updates. saveStockIn() has - // already reserved the rack so draft receipts cannot be reused. + // already reserved the bin so draft receipts cannot be reused. if ($auto_approve) { $stock->approveStock($new_stock_in_id, $warehouse_id, 'in', $whMgmt); } @@ -708,7 +708,7 @@ class PurchaseOrderManager { * Cancel a PO. * * Blocked if any linked stock-in rows have already been approved (status=1) - * because those have modified rack occupancy and balance. + * because those have modified bin occupancy and balance. * * For draft stock-in rows (status=0), they are soft-deleted (status=-1). * @@ -756,7 +756,7 @@ class PurchaseOrderManager { $whMgmt = new WarehouseManager($this->pdo, $this->company_id); - // Soft-delete draft stock-in rows and release their rack reservations. + // Soft-delete draft stock-in rows and release their bin reservations. foreach ($tables as $table) { if (!preg_match('/^td_stock_(\d+)$/', $table, $m)) { continue; @@ -764,7 +764,7 @@ class PurchaseOrderManager { $warehouse_id = (int)$m[1]; $sth = $this->pdo->prepare( - "SELECT id, zone, aisle, rack + "SELECT id, zone, aisle, bin FROM `{$table}` WHERE company_id = :company_id AND source = 'po' @@ -783,11 +783,11 @@ class PurchaseOrderManager { ':company_id' => $this->company_id, ]); - $whMgmt->releaseRack( + $whMgmt->releaseBin( $warehouse_id, $row['zone'], $row['aisle'], - $row['rack'] + $row['bin'] ); } } @@ -894,21 +894,21 @@ class PurchaseOrderManager { } } - // Release racks from draft stock-in rows and negate them + // Release bins from draft stock-in rows and negate them $whMgmt = new WarehouseManager($this->pdo, $this->company_id); foreach ($tables as $table) { if (!preg_match('/^td_stock_(\d+)$/', (string)$table, $matches)) continue; $warehouse_id = (int)$matches[1]; $row_sth = $this->pdo->prepare( - "SELECT id, zone, aisle, rack FROM `{$table}` + "SELECT id, zone, aisle, bin FROM `{$table}` WHERE company_id = :cid AND source = 'po' AND source_id = :id AND status = 0" ); $row_sth->execute([':cid' => $this->company_id, ':id' => $po_id]); $draft_rows = $row_sth->fetchAll(PDO::FETCH_ASSOC); foreach ($draft_rows as $row) { - $whMgmt->releaseRack($warehouse_id, $row['zone'], $row['aisle'], $row['rack']); + $whMgmt->releaseBin($warehouse_id, $row['zone'], $row['aisle'], $row['bin']); } $this->pdo->prepare( diff --git a/app/assets/utils/classes/ReportManager.php b/app/assets/utils/classes/ReportManager.php index 3877dbf..8e38d53 100644 --- a/app/assets/utils/classes/ReportManager.php +++ b/app/assets/utils/classes/ReportManager.php @@ -12,7 +12,7 @@ * Dashboard reports → stats, movement charts, most moved, recent activity * Warehouse detail → capacity, space used, balance, movement, trend, activity * Expiry reports → expired / near-expiry stock - * Lot / Rack reports → lot stock log, product lots, rack log, rack occupancy + * Lot / Bin reports → lot stock log, product lots, bin log, bin occupancy * Balance summary → getWarehouseBalanceSummary * * Security: All SQL uses PDO prepared statements with bound parameters. @@ -159,25 +159,25 @@ class ReportManager } /** - * Total rack capacity (all racks across all warehouses) for this company. + * Total bin capacity (all bins across all warehouses) for this company. * Used by the reports_stats dashboard tile. */ public function getTotalCapacity(): int { $sql = "SELECT COUNT(*) - FROM md_rack + FROM md_bin WHERE company_id = :company_id"; return (int) $this->fetchScalar($sql); } /** - * Number of occupied racks (product_sku IS NOT NULL) for this company. + * Number of occupied bins (product_sku IS NOT NULL) for this company. * Used by the reports_stats dashboard tile. */ public function getSpaceUsed(): int { $sql = "SELECT COUNT(*) - FROM md_rack + FROM md_bin WHERE company_id = :company_id AND product_sku IS NOT NULL"; return (int) $this->fetchScalar($sql); @@ -710,15 +710,15 @@ class ReportManager // ───────────────────────────────────────────────────────────── /** - * Total rack capacity for a specific warehouse (all racks regardless of occupancy). + * Total bin capacity for a specific warehouse (all bins regardless of occupancy). * * @param int $warehouse_id The warehouse to query. - * @return int Total rack count. + * @return int Total bin count. */ public function getWarehouseCapacity(int $warehouse_id): int { $sth = $this->pdo->prepare( - "SELECT COUNT(*) FROM md_rack + "SELECT COUNT(*) FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse_id" ); $sth->execute([':company_id' => $this->companyId, ':warehouse_id' => $warehouse_id]); @@ -726,15 +726,15 @@ class ReportManager } /** - * Number of occupied racks (product_sku IS NOT NULL) for a specific warehouse. + * Number of occupied bins (product_sku IS NOT NULL) for a specific warehouse. * * @param int $warehouse_id The warehouse to query. - * @return int Occupied rack count. + * @return int Occupied bin count. */ public function getWarehouseSpaceUsed(int $warehouse_id): int { $sth = $this->pdo->prepare( - "SELECT COUNT(*) FROM md_rack + "SELECT COUNT(*) FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse_id AND product_sku IS NOT NULL" ); @@ -1008,14 +1008,14 @@ class ReportManager s.lot_number, s.zone, s.aisle, - s.rack, + s.bin, ROUND(SUM(s.`in`) - SUM(s.`out`), 2) AS quantity, {$wh['id']} AS warehouse_id FROM `{$table}` s WHERE s.company_id = {$cid} AND s.lot_number IS NOT NULL AND s.status = 1 - GROUP BY s.product_sku, s.lot_number, s.zone, s.aisle, s.rack + GROUP BY s.product_sku, s.lot_number, s.zone, s.aisle, s.bin HAVING quantity > 0"; }, $warehouses @@ -1026,7 +1026,7 @@ class ReportManager stock.lot_number, stock.zone, stock.aisle, - stock.rack, + stock.bin, stock.quantity, l.expiry_date, DATEDIFF(l.expiry_date, CURDATE()) AS days_remaining, @@ -1073,7 +1073,7 @@ class ReportManager 'lot_number' => $row['lot_number'], 'zone' => $row['zone'], 'aisle' => $row['aisle'], - 'rack' => $row['rack'], + 'bin' => $row['bin'], 'quantity' => (float) $row['quantity'], 'expiry_date' => $row['expiry_date'], 'days_remaining' => $days, @@ -1085,7 +1085,7 @@ class ReportManager } // ───────────────────────────────────────────────────────────── - // REPORT BASIS — Lot / Rack reports + // REPORT BASIS — Lot / Bin reports // ───────────────────────────────────────────────────────────── /** @@ -1097,7 +1097,7 @@ class ReportManager * * @param string $product_sku SKU to filter by. * @param string $lot_number Lot number to filter by. - * @return array Stock rows with date, type, zone/aisle/rack, in/out amounts, warehouse_name. + * @return array Stock rows with date, type, zone/aisle/bin, in/out amounts, warehouse_name. */ public function getLotStockLog(string $product_sku, string $lot_number): array { @@ -1123,7 +1123,7 @@ class ReportManager s.type, s.zone, s.aisle, - s.rack, + s.bin, ROUND(COALESCE(s.`in`, 0), 2) AS stock_in, ROUND(COALESCE(s.`out`, 0), 2) AS stock_out, s.serial_number, @@ -1247,21 +1247,21 @@ class ReportManager } /** - * Return the action log for a specific rack, with user name. + * Return the action log for a specific bin, with user name. * - * Used by the rack log dashboard report page. + * Used by the bin log dashboard report page. * Joins the wms.user table for the acting user's name. * * Security fix: the database name is now fetched once via a bound query * and stored as $db_name (string), then used as a backtick-quoted identifier * rather than being embedded in the JOIN without escaping. * - * @param int $rack_id The md_rack.id to fetch logs for (0 returns empty array). + * @param int $bin_id The md_bin.id to fetch logs for (0 returns empty array). * @return array Log rows with dt, action, product_sku, td_stock_id, login, user_name. */ - public function getRackLog(int $rack_id): array + public function getBinLog(int $bin_id): array { - if (!$rack_id) return []; + if (!$bin_id) return []; $main_db = $this->mainDb; if ($main_db === '' || !ctype_alnum(str_replace('_', '', $main_db))) { @@ -1277,30 +1277,30 @@ class ReportManager rl.td_stock_id, rl.login, u.name AS user_name - FROM td_rack_log rl + FROM td_bin_log rl LEFT JOIN `{$main_db}`.user u ON u.user_id = rl.user_id WHERE rl.company_id = :company_id - AND rl.md_rack_id = :rack_id + AND rl.md_bin_id = :bin_id ORDER BY rl.dt DESC" ); $sth->execute([ ':company_id' => $this->companyId, - ':rack_id' => $rack_id, + ':bin_id' => $bin_id, ]); return $sth->fetchAll(PDO::FETCH_ASSOC); } /** - * Return all rack slots across all warehouses with occupancy status, + * Return all bin slots across all warehouses with occupancy status, * warehouse name, and product name. * - * Used by the rack occupancy dashboard report page to visualise which - * racks are empty vs occupied and which product is in each slot. - * Results are ordered by warehouse → zone → aisle → rack with + * Used by the bin occupancy dashboard report page to visualise which + * bins are empty vs occupied and which product is in each slot. + * Results are ordered by warehouse → zone → aisle → bin with * numeric-first sorting via CAST. * - * @return array All md_rack rows joined to warehouse and product with 'status' field. + * @return array All md_bin rows joined to warehouse and product with 'status' field. */ public function getRackOccupancy(): array { @@ -1309,14 +1309,14 @@ class ReportManager r.id, r.zone, r.aisle, - r.rack, + r.bin, r.product_sku, r.td_stock_id, mw.warehouse_name, mw.id AS warehouse_id, p.product_name, CASE WHEN r.product_sku IS NOT NULL THEN 'occupied' ELSE 'empty' END AS status - FROM md_rack r + FROM md_bin r INNER JOIN md_warehouse mw ON mw.company_id = r.company_id AND mw.id = r.warehouse @@ -1326,7 +1326,7 @@ class ReportManager WHERE r.company_id = :company_id ORDER BY mw.warehouse_name, r.zone, CAST(r.aisle AS UNSIGNED), r.aisle, - CAST(r.rack AS UNSIGNED), r.rack" + CAST(r.bin AS UNSIGNED), r.bin" ); $sth->execute([':company_id' => $this->companyId]); return $sth->fetchAll(PDO::FETCH_ASSOC); @@ -1510,7 +1510,7 @@ class ReportManager s.serial_number, s.zone, s.aisle, - s.rack, + s.bin, ROUND(COALESCE(s.`in`, 0), 2) AS stock_in, ROUND(COALESCE(s.`out`, 0), 2) AS stock_out, CASE diff --git a/app/assets/utils/classes/ReturnManager.php b/app/assets/utils/classes/ReturnManager.php index fca3b21..cc88d81 100644 --- a/app/assets/utils/classes/ReturnManager.php +++ b/app/assets/utils/classes/ReturnManager.php @@ -17,13 +17,13 @@ require_once __DIR__ . '/../classes_ac/PostingWindowGuard.php'; * 1. INSERT td_stock_ in rows (source='return', source_id=return_id, * status=1 ALWAYS — confirming a return is a final business decision, * no separate warehouse approval step needed). - * 2. occupyRack() + adjustBalance() via WarehouseManager. + * 2. occupyBin() + adjustBalance() via WarehouseManager. * 3. If auto_invoice_and_credit_note=1 → InvoiceManager::createCreditNote() * auto-generates a CN linked to the original invoice (status=1 issued). * If =0 → user creates CN manually from the return detail page. * - Return items stored as JSON in td_return.items (same pattern as td_order). * - Each item references stock_out_id from the original order so the correct - * warehouse/rack/lot context is known for restock. + * warehouse/bin/lot context is known for restock. * * Note: Write methods do NOT manage their own DB transactions. * Callers must wrap multi-step operations inside dbTransaction(). @@ -242,7 +242,7 @@ class ReturnManager { * * items JSON array — each element mirrors td_order.items with stock_out_id * referencing the original stock-out row so confirmReturn() knows which - * rack/warehouse/lot to restock. + * bin/warehouse/lot to restock. * * Must be called inside dbTransaction() by the caller. * @@ -389,7 +389,7 @@ class ReturnManager { * Flow per item: * 1. INSERT td_stock_ in row with type='in', status=1 (force confirmed), * source='return', source_id=return_id. - * 2. occupyRack() — place returned stock into the user-selected empty rack. + * 2. occupyBin() — place returned stock into the user-selected empty bin. * 3. adjustBalance() — update etl_stock_summary. * * After all items: @@ -400,7 +400,7 @@ class ReturnManager { * * Stock-in rows respect $auto_approve (mirrors default_stock_status config). * When false, rows are created as draft (status=0) and warehouse staff approve - * them via approve_stock.php; rack occupation and balance are deferred. + * them via approve_stock.php; bin occupation and balance are deferred. * * Must be called inside dbTransaction() by the caller. * @@ -495,13 +495,13 @@ class ReturnManager { $this->pdo->prepare( "INSERT INTO `{$table}` (uuid, company_id, `date`, product_sku, `in`, - zone, aisle, rack, + zone, aisle, bin, contact_id, `description`, `log`, `type`, lot_number, serial_number, source, source_id, ref_id, price, status) VALUES (:uuid, :company_id, :date, :product_sku, :quantity, - :zone, :aisle, :rack, + :zone, :aisle, :bin, :contact_id, :description, :log, 'in', :lot_number, :serial_number, 'return', :source_id, :ref_id, :price, :status)" @@ -513,7 +513,7 @@ class ReturnManager { ':quantity' => $quantity, ':zone' => $item['zone'] ?? '', ':aisle' => $item['aisle'] ?? '', - ':rack' => $item['rack'] ?? '', + ':bin' => $item['bin'] ?? '', ':contact_id' => (int)$return['contact_id'], ':description' => $return['return_number'], ':log' => json_encode($item_log), @@ -528,11 +528,11 @@ class ReturnManager { $stock_in_id = (int)$this->pdo->lastInsertId(); if ($auto_approve) { - $whMgmt->occupyRack( + $whMgmt->occupyBin( $warehouse_id, $item['zone'] ?? '', $item['aisle'] ?? '', - $item['rack'] ?? '', + $item['bin'] ?? '', $product_sku, $stock_in_id, true @@ -583,9 +583,9 @@ class ReturnManager { * and doc_type. * * If the return was confirmed, stock-in rows (source='return', source_id=$return_id) - * were created with status=1 and rack/balance were already adjusted. + * were created with status=1 and bin/balance were already adjusted. * On cancel we soft-delete those rows (status → -1) and reverse the - * rack occupation and balance via WarehouseManager. + * bin occupation and balance via WarehouseManager. * * If the return was still draft, no stock rows exist — simple status flip. * @@ -636,7 +636,7 @@ class ReturnManager { } } - // ── If confirmed: reverse stock-in rows + rack/balance ──────────── + // ── If confirmed: reverse stock-in rows + bin/balance ──────────── if ($status === 1) { $sth2 = $this->pdo->prepare( "SELECT * FROM td_return_item @@ -657,7 +657,7 @@ class ReturnManager { // Fetch ALL non-cancelled stock-in rows (status=1 approved or status=0 draft) $sth = $this->pdo->prepare( - "SELECT id, status, zone, aisle, rack, `date` FROM `{$table}` + "SELECT id, status, zone, aisle, bin, `date` FROM `{$table}` WHERE company_id = :company_id AND source = 'return' AND source_id = :return_id @@ -679,11 +679,11 @@ class ReturnManager { if ((int)$row['status'] === 1) { $whMgmt->assertStockMovementWindow($row['date'] ?? null, 'Return stock cancellation'); - // Approved rows: reverse rack occupation + balance - $whMgmt->releaseRack($warehouse_id, $row['zone'], $row['aisle'], $row['rack']); + // Approved rows: reverse bin occupation + balance + $whMgmt->releaseBin($warehouse_id, $row['zone'], $row['aisle'], $row['bin']); $whMgmt->adjustBalance('out', $warehouse_id, $product_sku, 0, $quantity); } - // Draft (status=0) rows: just soft-delete — rack/balance were never applied + // Draft (status=0) rows: just soft-delete — bin/balance were never applied } } } diff --git a/app/assets/utils/classes/StockManager.php b/app/assets/utils/classes/StockManager.php index 8696502..ce8a88d 100644 --- a/app/assets/utils/classes/StockManager.php +++ b/app/assets/utils/classes/StockManager.php @@ -15,7 +15,7 @@ require_once __DIR__ . '/../notify_node.php'; * getTransferById, saveStockIn, saveStockOut, saveStockTransfer * Report basis → (none — reporting is handled by ReportManager) * - * Write operations delegate rack and balance side-effects to WarehouseManager. + * Write operations delegate bin and balance side-effects to WarehouseManager. * Delete operations are handled directly in WarehouseManager (deleteStockIn, etc.). * * Note: Write methods do NOT manage their own DB transactions. @@ -228,16 +228,16 @@ class StockManager { * Insert flow (id = 0): * 1. Upserts md_lot if lot_number + expiry_date are provided. * 2. Inserts the td_stock_ row. - * 3. Calls WarehouseManager::occupyRack() to reserve the rack. + * 3. Calls WarehouseManager::occupyBin() to reserve the bin. * etl_stock_summary is updated later by approveStock(). * * Update flow (id > 0): * - Updates contact_id, description, and log only. - * - Quantity, rack, lot, and serial are immutable after creation. + * - Quantity, bin, lot, and serial are immutable after creation. * * Must be called inside dbTransaction() by the caller. * - * @param array $data Keys: id, warehouse, product_sku, quantity, zone, aisle, rack, + * @param array $data Keys: id, warehouse, product_sku, quantity, zone, aisle, bin, * contact_id, description, lot_number, expiry_date, serial_number. * @param array $logging Audit entry to append to the log column. * @param string $uuid UUID for this transaction (shared across transfer pairs). @@ -303,10 +303,10 @@ class StockManager { $this->pdo->prepare( "INSERT INTO `$table` - (uuid, company_id, `date`, product_sku, `in`, price, zone, aisle, rack, + (uuid, company_id, `date`, product_sku, `in`, price, zone, aisle, bin, contact_id, `description`, `log`, `type`, lot_number, serial_number, status, updated_at) VALUES - (:uuid, :company_id, :date, :product_sku, :quantity, :price, :zone, :aisle, :rack, + (:uuid, :company_id, :date, :product_sku, :quantity, :price, :zone, :aisle, :bin, :contact_id, :description, :log, 'in', :lot_number, :serial_number, 0, NOW())" )->execute([ ':uuid' => $uuid, @@ -317,7 +317,7 @@ class StockManager { ':price' => (float)($data['price'] ?? 0), ':zone' => $data['zone'], ':aisle' => $data['aisle'], - ':rack' => $data['rack'], + ':bin' => $data['bin'], ':contact_id' => (int)($data['contact_id'] ?? 0), ':description' => $data['description'] ?? '', ':log' => json_encode($table_log), @@ -328,13 +328,13 @@ class StockManager { $td_stock_id = (int)$this->pdo->lastInsertId(); // Reserve the location immediately so draft stock-in rows cannot - // leave the same rack available for another receipt. Balance still + // leave the same bin available for another receipt. Balance still // changes only when approveStock() runs. - $whMgmt->occupyRack( + $whMgmt->occupyBin( $warehouse_id, $data['zone'], $data['aisle'], - $data['rack'], + $data['bin'], $data['product_sku'], $td_stock_id ); @@ -347,9 +347,9 @@ class StockManager { * Insert a new stock_out record or update metadata on an existing one. * * Insert flow (id = 0): - * 1. Validates the rack is occupied with the correct SKU / lot / serial. - * 2. Inserts the td_stock_ row, copying quantity and lot info from the rack. - * 3. Calls WarehouseManager::releaseRack() to free the rack slot. + * 1. Validates the bin is occupied with the correct SKU / lot / serial. + * 2. Inserts the td_stock_ row, copying quantity and lot info from the bin. + * 3. Calls WarehouseManager::releaseBin() to free the bin slot. * 4. Calls WarehouseManager::adjustBalance() to update etl_stock_summary. * * Update flow (id > 0): @@ -357,11 +357,11 @@ class StockManager { * * Must be called inside dbTransaction() by the caller. * - * @param array $data Keys: id, warehouse, product_sku, zone, aisle, rack, + * @param array $data Keys: id, warehouse, product_sku, zone, aisle, bin, * contact_id, description, lot_number, serial_number. * @param array $logging Audit entry to append to the log column. * @param string $uuid UUID for this transaction. - * @throws Exception If the rack is empty, holds a different SKU/lot/serial. + * @throws Exception If the bin is empty, holds a different SKU/lot/serial. */ public function saveStockOut(array $data, array $logging, string $uuid): int { @@ -400,33 +400,33 @@ class StockManager { } else { - // Validate rack holds the expected product / lot / serial - $source_stock = $whMgmt->getRackStock( + // Validate bin holds the expected product / lot / serial + $source_stock = $whMgmt->getBinStock( $warehouse_id, - $data['zone'], $data['aisle'], $data['rack'] + $data['zone'], $data['aisle'], $data['bin'] ); if (!$source_stock) { throw new Exception( - "Rack {$data['zone']}-{$data['aisle']}-{$data['rack']} is empty — nothing to take out." + "Bin {$data['zone']}-{$data['aisle']}-{$data['bin']} is empty — nothing to take out." ); } if ($source_stock['product_sku'] !== $data['product_sku']) { throw new Exception( - "Rack holds {$source_stock['product_sku']}, not {$data['product_sku']}." + "Bin holds {$source_stock['product_sku']}, not {$data['product_sku']}." ); } if (!empty($data['lot_number']) && $source_stock['lot_number'] !== $data['lot_number']) { throw new Exception( - "Rack holds lot '{$source_stock['lot_number']}', not '{$data['lot_number']}'." + "Bin holds lot '{$source_stock['lot_number']}', not '{$data['lot_number']}'." ); } if (!empty($data['serial_number']) && $source_stock['serial_number'] !== $data['serial_number']) { throw new Exception( - "Rack holds serial '{$source_stock['serial_number']}', not '{$data['serial_number']}'." + "Bin holds serial '{$source_stock['serial_number']}', not '{$data['serial_number']}'." ); } @@ -438,10 +438,10 @@ class StockManager { $this->pdo->prepare( "INSERT INTO `$table` - (uuid, company_id, `date`, product_sku, `out`, zone, aisle, rack, + (uuid, company_id, `date`, product_sku, `out`, zone, aisle, bin, contact_id, `description`, `log`, `type`, ref_id, lot_number, serial_number, status, updated_at) VALUES - (:uuid, :company_id, :date, :product_sku, :quantity, :zone, :aisle, :rack, + (:uuid, :company_id, :date, :product_sku, :quantity, :zone, :aisle, :bin, :contact_id, :description, :log, 'out', :ref_id, :lot_number, :serial_number, 0, NOW())" )->execute([ ':uuid' => $uuid, @@ -451,7 +451,7 @@ class StockManager { ':quantity' => $quantity, ':zone' => $data['zone'], ':aisle' => $data['aisle'], - ':rack' => $data['rack'], + ':bin' => $data['bin'], ':contact_id' => (int)($data['contact_id'] ?? 0), ':description' => $data['description'] ?? '', ':log' => json_encode($table_log), @@ -462,7 +462,7 @@ class StockManager { $out_stock_id = (int)$this->pdo->lastInsertId(); - // releaseRack and adjustBalance deferred — called from approveStock() only. + // releaseBin and adjustBalance deferred — called from approveStock() only. return $out_stock_id; } } @@ -475,11 +475,11 @@ class StockManager { * sharing the same UUID and cross-referencing each other via ref_id. * * Insert flow (id = 0): - * 1. Validates the source rack holds the correct SKU / lot / serial. + * 1. Validates the source bin holds the correct SKU / lot / serial. * 2. Inserts the outbound row in td_stock_. * 3. Inserts the inbound row in td_stock_ with ref_id pointing to from. * 4. Back-fills ref_id on the from row so both point at each other. - * 5. Releases the source rack, occupies the destination rack. + * 5. Releases the source bin, occupies the destination bin. * 6. Adjusts balance on both warehouses (out from source, in to dest). * * Update flow (id > 0): @@ -488,11 +488,11 @@ class StockManager { * Must be called inside dbTransaction() by the caller. * * @param array $data Keys: id, warehouse_from, warehouse_to, product_sku, - * zone_from, aisle_from, rack_from, zone_to, aisle_to, rack_to, + * zone_from, aisle_from, bin_from, zone_to, aisle_to, bin_to, * contact_id, description, lot_number, serial_number. * @param array $logging Audit entry to append to the log column on both rows. * @param string $uuid UUID shared by both the from and to rows. - * @throws Exception If source rack validation fails or paired record is missing on update. + * @throws Exception If source bin validation fails or paired record is missing on update. */ public function saveStockTransfer(array $data, array $logging, string $uuid): int { @@ -566,39 +566,39 @@ class StockManager { return 0; // update — no new row } - // Insert: validate source rack, then create paired rows + // Insert: validate source bin, then create paired rows $from_warehouse = (int)$data['warehouse_from']; $from_zone = $data['zone_from']; $from_aisle = $data['aisle_from']; - $from_rack = $data['rack_from']; + $from_bin = $data['bin_from']; $to_warehouse = (int)$data['warehouse_to']; $to_zone = $data['zone_to']; $to_aisle = $data['aisle_to']; - $to_rack = $data['rack_to']; + $to_bin = $data['bin_to']; - $source_stock = $whMgmt->getRackStock( - $from_warehouse, $from_zone, $from_aisle, $from_rack + $source_stock = $whMgmt->getBinStock( + $from_warehouse, $from_zone, $from_aisle, $from_bin ); if (!$source_stock) { - throw new Exception("Source rack {$from_zone}-{$from_aisle}-{$from_rack} is empty."); + throw new Exception("Source bin {$from_zone}-{$from_aisle}-{$from_bin} is empty."); } if ($source_stock['product_sku'] !== $data['product_sku']) { throw new Exception( - "Source rack holds {$source_stock['product_sku']}, not {$data['product_sku']}." + "Source bin holds {$source_stock['product_sku']}, not {$data['product_sku']}." ); } if (!empty($data['lot_number']) && $source_stock['lot_number'] !== $data['lot_number']) { throw new Exception( - "Source rack holds lot '{$source_stock['lot_number']}', not '{$data['lot_number']}'." + "Source bin holds lot '{$source_stock['lot_number']}', not '{$data['lot_number']}'." ); } if (!empty($data['serial_number']) && $source_stock['serial_number'] !== $data['serial_number']) { throw new Exception( - "Source rack holds serial '{$source_stock['serial_number']}', not '{$data['serial_number']}'." + "Source bin holds serial '{$source_stock['serial_number']}', not '{$data['serial_number']}'." ); } @@ -615,11 +615,11 @@ class StockManager { $this->pdo->prepare( "INSERT INTO `$from_table` (uuid, company_id, `date`, product_sku, `out`, - ref_warehouse, zone, aisle, rack, + ref_warehouse, zone, aisle, bin, contact_id, `description`, `log`, `type`, lot_number, serial_number, status, updated_at) VALUES (:uuid, :company_id, :date, :product_sku, :quantity, - :ref_warehouse, :zone, :aisle, :rack, + :ref_warehouse, :zone, :aisle, :bin, :contact_id, :description, :log, 'transfer', :lot_number, :serial_number, 0, NOW())" )->execute([ ':uuid' => $uuid, @@ -630,7 +630,7 @@ class StockManager { ':ref_warehouse' => $to_warehouse, ':zone' => $from_zone, ':aisle' => $from_aisle, - ':rack' => $from_rack, + ':bin' => $from_bin, ':contact_id' => $contact_id, ':description' => $description, ':log' => json_encode($table_log), @@ -643,11 +643,11 @@ class StockManager { $this->pdo->prepare( "INSERT INTO `$to_table` (uuid, company_id, `date`, product_sku, `in`, - ref_warehouse, ref_id, zone, aisle, rack, + ref_warehouse, ref_id, zone, aisle, bin, contact_id, `description`, `log`, `type`, lot_number, serial_number, status, updated_at) VALUES (:uuid, :company_id, :date, :product_sku, :quantity, - :ref_warehouse, :ref_id, :zone, :aisle, :rack, + :ref_warehouse, :ref_id, :zone, :aisle, :bin, :contact_id, :description, :log, 'transfer', :lot_number, :serial_number, 0, NOW())" )->execute([ ':uuid' => $uuid, @@ -659,7 +659,7 @@ class StockManager { ':ref_id' => $from_stock_id, ':zone' => $to_zone, ':aisle' => $to_aisle, - ':rack' => $to_rack, + ':bin' => $to_bin, ':contact_id' => $contact_id, ':description' => $description, ':log' => json_encode($table_log), @@ -678,7 +678,7 @@ class StockManager { ':company_id' => $this->company_id, ]); - // releaseRack, occupyRack and adjustBalance deferred — called from approveStock() only. + // releaseBin, occupyBin and adjustBalance deferred — called from approveStock() only. return $from_stock_id; } @@ -730,13 +730,13 @@ class StockManager { "UPDATE `{$table}` SET status = 1, updated_at = NOW() WHERE id = :id AND company_id = :company_id" )->execute([':id' => $id, ':company_id' => $this->company_id]); - // ── Rack state + balance ────────────────────────────────────────── + // ── Bin state + balance ────────────────────────────────────────── if ($type === 'in') { - // Occupy rack now that stock is approved - $whMgmt->occupyRack( + // Occupy bin now that stock is approved + $whMgmt->occupyBin( $warehouse_id, - $row['zone'], $row['aisle'], $row['rack'], + $row['zone'], $row['aisle'], $row['bin'], $row['product_sku'], $id ); @@ -766,9 +766,9 @@ class StockManager { $remaining_qty = (float)$remaining_sth->fetchColumn(); } - // Release the rack only when the source batch is fully consumed. + // Release the bin only when the source batch is fully consumed. if ($remaining_qty <= 0.000001) { - $whMgmt->releaseRack($warehouse_id, $row['zone'], $row['aisle'], $row['rack']); + $whMgmt->releaseBin($warehouse_id, $row['zone'], $row['aisle'], $row['bin']); } $whMgmt->adjustBalance('out', $warehouse_id, $row['product_sku'], 0, (float)$row['out'], $id, $row['source'] ?? '', (int)($row['source_id'] ?? 0), $row['date'] ?? ''); @@ -848,19 +848,19 @@ class StockManager { )->execute([':id' => $inbound_row['id'], ':company_id' => $this->company_id]); } - // Rack state: release source, occupy destination + // Bin state: release source, occupy destination if ($from_row && $from_wh_id) { - $whMgmt->releaseRack( + $whMgmt->releaseBin( $from_wh_id, - $from_row['zone'], $from_row['aisle'], $from_row['rack'] + $from_row['zone'], $from_row['aisle'], $from_row['bin'] ); $whMgmt->adjustBalance('out', $from_wh_id, $from_row['product_sku'], 0, (float)$from_row['out'], (int)$from_row['id'], $from_row['source'] ?? '', (int)($from_row['source_id'] ?? 0), $from_row['date'] ?? ''); } if ($inbound_row && $paired_wh_id) { - $whMgmt->occupyRack( + $whMgmt->occupyBin( $paired_wh_id, - $inbound_row['zone'], $inbound_row['aisle'], $inbound_row['rack'], + $inbound_row['zone'], $inbound_row['aisle'], $inbound_row['bin'], $inbound_row['product_sku'], $inbound_row['id'] ); diff --git a/app/assets/utils/classes/StockSourceManager.php b/app/assets/utils/classes/StockSourceManager.php index e07cff5..673f8b5 100644 --- a/app/assets/utils/classes/StockSourceManager.php +++ b/app/assets/utils/classes/StockSourceManager.php @@ -26,7 +26,7 @@ class StockSourceManager $warehouse_id = (int)$match[1]; $sth = $this->pdo->prepare( "SELECT s.id, s.type, s.product_sku, s.`in`, s.`out`, - s.zone, s.aisle, s.rack, s.lot_number, s.serial_number, + s.zone, s.aisle, s.bin, s.lot_number, s.serial_number, s.status, s.date, s.price, p.product_name FROM `{$table}` s diff --git a/app/assets/utils/classes/SupplierReturnManager.php b/app/assets/utils/classes/SupplierReturnManager.php index 08e4ded..a747ec5 100644 --- a/app/assets/utils/classes/SupplierReturnManager.php +++ b/app/assets/utils/classes/SupplierReturnManager.php @@ -104,10 +104,10 @@ class SupplierReturnManager { $sth = $this->pdo->prepare( "INSERT INTO td_supplier_return_item (company_id, return_id, item_id, product_sku, product_name, - quantity, unit_price, total_price, tax_amount, tax_rate, warehouse_id, stock_in_id, zone, aisle, rack) + quantity, unit_price, total_price, tax_amount, tax_rate, warehouse_id, stock_in_id, zone, aisle, bin) VALUES (:company_id, :return_id, :item_id, :product_sku, :product_name, - :quantity, :unit_price, :total_price, :tax_amount, :tax_rate, :warehouse_id, :stock_in_id, :zone, :aisle, :rack)" + :quantity, :unit_price, :total_price, :tax_amount, :tax_rate, :warehouse_id, :stock_in_id, :zone, :aisle, :bin)" ); foreach ($items as $pos => $item) { $sth->execute([ @@ -125,7 +125,7 @@ class SupplierReturnManager { ':stock_in_id' => (int)($item['stock_in_id'] ?? 0), ':zone' => $item['zone'] ?? '', ':aisle' => $item['aisle'] ?? '', - ':rack' => $item['rack'] ?? '', + ':bin' => $item['bin'] ?? '', ]); } } @@ -348,7 +348,7 @@ class SupplierReturnManager { * Flow per item: * 1. Validate stock-in row (from PO receive) is approved. * 2. INSERT td_stock_ out row, status respects $auto_approve. - * 3. If $auto_approve: releaseRack() + adjustBalance('out'). + * 3. If $auto_approve: releaseBin() + adjustBalance('out'). * After all items: * 4. UPDATE td_supplier_return.status = 1. * @@ -433,9 +433,9 @@ class SupplierReturnManager { $zone = $item['zone'] ?? ''; $aisle = $item['aisle'] ?? ''; - $rack = $item['rack'] ?? ''; - if ($zone === '' && $rack !== '') $zone = $rack; - if ($aisle === '' && $rack !== '') $aisle = $rack; + $bin = $item['bin'] ?? ''; + if ($zone === '' && $bin !== '') $zone = $bin; + if ($aisle === '' && $bin !== '') $aisle = $bin; $item_uuid = $uuid . '_srn_' . $i; $item_log = [array_merge($logging, ['action' => 'confirm_return_item'])]; @@ -443,13 +443,13 @@ class SupplierReturnManager { $this->pdo->prepare( "INSERT INTO `{$table}` (uuid, company_id, `date`, product_sku, `out`, - zone, aisle, rack, + zone, aisle, bin, contact_id, `description`, `log`, `type`, lot_number, serial_number, source, source_id, ref_id, price, status) VALUES (:uuid, :company_id, :date, :product_sku, :quantity, - :zone, :aisle, :rack, + :zone, :aisle, :bin, :contact_id, :description, :log, 'out', :lot_number, :serial_number, 'supplier_return', :source_id, :ref_id, :price, :status)" @@ -461,7 +461,7 @@ class SupplierReturnManager { ':quantity' => $quantity, ':zone' => $zone, ':aisle' => $aisle, - ':rack' => $rack, + ':bin' => $bin, ':contact_id' => (int)$return['contact_id'], ':description' => $return['return_number'], ':log' => json_encode($item_log), @@ -475,7 +475,7 @@ class SupplierReturnManager { if ($auto_approve) { $stock_out_id = (int)$this->pdo->lastInsertId(); - $whMgmt->releaseRack($warehouse_id, $zone, $aisle, $rack); + $whMgmt->releaseBin($warehouse_id, $zone, $aisle, $bin); $whMgmt->adjustBalance('out', $warehouse_id, $product_sku, 0, $quantity); } } @@ -560,7 +560,7 @@ class SupplierReturnManager { // Fetch ALL non-cancelled stock-out rows (status=1 approved or status=0 draft) $sth = $this->pdo->prepare( - "SELECT id, status, zone, aisle, rack, ref_id, `date` FROM `{$table}` + "SELECT id, status, zone, aisle, bin, ref_id, `date` FROM `{$table}` WHERE company_id = :company_id AND source = 'supplier_return' AND source_id = :return_id @@ -582,14 +582,14 @@ class SupplierReturnManager { if ((int)$row['status'] === 1) { $whMgmt->assertStockMovementWindow($row['date'] ?? null, 'Supplier return stock cancellation'); - // Approved rows: re-occupy rack (reverse the release) + reverse balance + // Approved rows: re-occupy bin (reverse the release) + reverse balance $ref_id = (int)$row['ref_id']; if ($ref_id > 0) { - $whMgmt->occupyRack($warehouse_id, $row['zone'], $row['aisle'], $row['rack'], $product_sku, $ref_id, false); + $whMgmt->occupyBin($warehouse_id, $row['zone'], $row['aisle'], $row['bin'], $product_sku, $ref_id, false); } $whMgmt->adjustBalance('in', $warehouse_id, $product_sku, 0, $quantity); } - // Draft (status=0) rows: just soft-delete — rack/balance were never applied + // Draft (status=0) rows: just soft-delete — bin/balance were never applied } } } diff --git a/app/assets/utils/classes/WarehouseManager.php b/app/assets/utils/classes/WarehouseManager.php index 33e5d8c..29991a5 100644 --- a/app/assets/utils/classes/WarehouseManager.php +++ b/app/assets/utils/classes/WarehouseManager.php @@ -4,13 +4,13 @@ require_once __DIR__ . '/../classes_ac/PostingWindowGuard.php'; /** * WarehouseManager * - * Handles all warehouse, storage, rack, lot/serial, and stock deletion operations. + * Handles all warehouse, storage, bin, lot/serial, and stock deletion operations. * * Method order: * Master file basis → Warehouse (get/save/delete), Storage (get/save/delete), - * Zone/Aisle/Rack resolution (getAll variants for admin views), + * Zone/Aisle/Bin resolution (getAll variants for admin views), * Lot/Serial queries - * Transaction basis → Stock context resolution, rack lifecycle (occupy/release/transfer), + * Transaction basis → Stock context resolution, bin lifecycle (occupy/release/transfer), * balance adjustment, delete operations (stock in/out/transfer) * Report basis → Warehouse list queries (getWarehouseList, getWarehouseListAll) * @@ -75,7 +75,7 @@ class WarehouseManager { * - Numeric: "1" → "10" expands to ["1", "2", ..., "10"] * - Single-letter alpha: "A" → "D" expands to ["A", "B", "C", "D"] * - * Used when syncing md_rack rows against an md_storage aisle/rack range. + * Used when syncing md_bin rows against an md_storage aisle/bin range. * * @param string $from Start of range (e.g. "1" or "A"). * @param string $to End of range (e.g. "10" or "Z"). @@ -162,7 +162,7 @@ class WarehouseManager { /** * Build the WHERE conditions and bound params for lot/serial filtering - * used across getZonesOut, getAislesOut, getRacksOut. + * used across getZonesOut, getAislesOut, getBinsOut. * * Conditionally appends lot_number and/or serial_number filters only * when those values are non-empty, preventing unnecessary join conditions. @@ -308,27 +308,27 @@ class WarehouseManager { } /** - * Insert a rack log entry for audit tracking of occupy/release/transfer events. + * Insert a bin log entry for audit tracking of occupy/release/transfer events. * - * Called internally by occupyRack, releaseRack, and transferRack. + * Called internally by occupyBin, releaseBin, and transferBin. * $extra may contain 'product_sku' and 'td_stock_id' to record what - * was in the rack at the time of the action. + * was in the bin at the time of the action. * - * @param int $rack_id The md_rack.id being acted on. + * @param int $bin_id The md_bin.id being acted on. * @param string $action Event label: 'occupy' | 'release' | 'transfer_in' | 'transfer_out'. * @param array $extra Optional keys: product_sku, td_stock_id. */ - private function insertRackLog(int $rack_id, string $action, array $extra = []): void { + private function insertBinLog(int $bin_id, string $action, array $extra = []): void { - $sql = "INSERT INTO td_rack_log - (company_id, md_rack_id, user_id, dt, login, action, product_sku, td_stock_id) + $sql = "INSERT INTO td_bin_log + (company_id, md_bin_id, user_id, dt, login, action, product_sku, td_stock_id) VALUES - (:company_id, :md_rack_id, :user_id, :dt, :login, :action, :product_sku, :td_stock_id)"; + (:company_id, :md_bin_id, :user_id, :dt, :login, :action, :product_sku, :td_stock_id)"; $sth = $this->pdo->prepare($sql); $sth->execute([ ':company_id' => $this->company_id, - ':md_rack_id' => $rack_id, + ':md_bin_id' => $bin_id, ':user_id' => $_SESSION['login_user_id'] ?? null, ':dt' => date('Y-m-d H:i:s'), ':login' => isset($_SESSION['otpTime']) @@ -341,16 +341,16 @@ class WarehouseManager { } /** - * Soft-delete rack logs created by a stock movement and its reversal. + * Soft-delete bin logs created by a stock movement and its reversal. * - * td_rack_log follows the same soft-delete convention as other tenant rows: + * td_bin_log follows the same soft-delete convention as other tenant rows: * negate company_id so normal company_id filters hide the records. */ - private function softDeleteRackLogs( + private function softDeleteBinLogs( $warehouse_id, string $zone, string $aisle, - string $rack, + string $bin, array $actions, string $product_sku, array $td_stock_ids = [], @@ -360,30 +360,30 @@ class WarehouseManager { return; } - $rack_sth = $this->pdo->prepare( - "SELECT id FROM md_rack + $bin_sth = $this->pdo->prepare( + "SELECT id FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone AND aisle = :aisle - AND rack = :rack + AND bin = :bin LIMIT 1" ); - $rack_sth->execute([ + $bin_sth->execute([ ':company_id' => $this->company_id, ':warehouse' => $warehouse_id, ':zone' => $zone, ':aisle' => $aisle, - ':rack' => $rack, + ':bin' => $bin, ]); - $rack_id = (int)$rack_sth->fetchColumn(); - if (!$rack_id) { + $bin_id = (int)$bin_sth->fetchColumn(); + if (!$bin_id) { return; } $params = [ ':company_id' => $this->company_id, - ':md_rack_id' => $rack_id, + ':md_bin_id' => $bin_id, ':product_sku' => $product_sku, ]; @@ -412,10 +412,10 @@ class WarehouseManager { $params[':since'] = $since; } - $sql = "UPDATE td_rack_log + $sql = "UPDATE td_bin_log SET company_id = company_id * -1 WHERE company_id = :company_id - AND md_rack_id = :md_rack_id + AND md_bin_id = :md_bin_id AND product_sku = :product_sku AND action IN (" . implode(', ', $action_placeholders) . ") {$td_stock_cond} @@ -425,23 +425,23 @@ class WarehouseManager { } /** - * Validate that the occupied racks under a storage_id all fall inside a new range. + * Validate that the occupied bins under a storage_id all fall inside a new range. * - * Called before syncRacks to prevent shrinking a range that still has occupied racks. - * If any occupied rack has an aisle or rack value outside the proposed range, throws. + * Called before syncBins to prevent shrinking a range that still has occupied bins. + * If any occupied bin has an aisle or bin value outside the proposed range, throws. * * @param int $storage_id The md_storage.id whose range is being changed. - * @param array $range Keys: aisle_from, aisle_to, rack_from, rack_to. - * @throws Exception If occupied racks would fall outside the new range. + * @param array $range Keys: aisle_from, aisle_to, bin_from, bin_to. + * @throws Exception If occupied bins would fall outside the new range. */ private function validateRangeChange(int $storage_id, array $range, bool $advanced_location): void { $valid_aisles = $this->rangeToArray($range['aisle_from'], $range['aisle_to']); - $valid_racks = $this->rangeToArray($range['rack_from'], $range['rack_to']); + $valid_bins = $this->rangeToArray($range['bin_from'], $range['bin_to']); - // Fetch all occupied racks under this storage_id + // Fetch all occupied bins under this storage_id $sth = $this->pdo->prepare( - "SELECT zone, aisle, rack FROM md_rack + "SELECT zone, aisle, bin FROM md_bin WHERE company_id = :company_id AND storage_id = :storage_id AND product_sku IS NOT NULL" @@ -454,64 +454,64 @@ class WarehouseManager { foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) { $z = trim($row['zone']); $a = trim($row['aisle']); - $r = trim($row['rack']); + $r = trim($row['bin']); if ($advanced_location) { - if (in_array($a, $valid_aisles, true) && in_array($r, $valid_racks, true)) { + if (in_array($a, $valid_aisles, true) && in_array($r, $valid_bins, true)) { continue; } - throw new Exception("Cannot shrink range — some racks still have stock"); + throw new Exception("Cannot shrink range — some bins still have stock"); } - if ($z !== $a || $a !== $r || !in_array($r, $valid_racks, true)) { - throw new Exception("Cannot shrink range — some racks still have stock"); + if ($z !== $a || $a !== $r || !in_array($r, $valid_bins, true)) { + throw new Exception("Cannot shrink range — some bins still have stock"); } } } /** - * Insert rack rows for the full aisle × rack range (INSERT IGNORE skips duplicates). + * Insert bin rows for the full aisle × bin range (INSERT IGNORE skips duplicates). * - * Called by syncRacks after cleaning up out-of-range empty racks. - * Supports both numeric (1-10) and alpha (A-Z) aisle/rack values. + * Called by syncBins after cleaning up out-of-range empty bins. + * Supports both numeric (1-10) and alpha (A-Z) aisle/bin values. * * @param int $storage_id The md_storage.id this range belongs to. - * @param array $range Keys: warehouse, zone, aisle_from, aisle_to, rack_from, rack_to. + * @param array $range Keys: warehouse, zone, aisle_from, aisle_to, bin_from, bin_to. */ - private function insertRacksInRange(int $storage_id, array $range, bool $advanced_location): void + private function insertBinsInRange(int $storage_id, array $range, bool $advanced_location): void { - $sql = "INSERT IGNORE INTO md_rack - (company_id, warehouse, storage_id, zone, aisle, rack) + $sql = "INSERT IGNORE INTO md_bin + (company_id, warehouse, storage_id, zone, aisle, bin) VALUES - (:company_id, :warehouse, :storage_id, :zone, :aisle, :rack)"; + (:company_id, :warehouse, :storage_id, :zone, :aisle, :bin)"; $sth = $this->pdo->prepare($sql); $aisles = $this->rangeToArray($range['aisle_from'], $range['aisle_to']); - $racks = $this->rangeToArray($range['rack_from'], $range['rack_to']); + $bins = $this->rangeToArray($range['bin_from'], $range['bin_to']); if (!$advanced_location) { - foreach ($racks as $location) { + foreach ($bins as $location) { $sth->execute([ ":company_id" => $this->company_id, ":warehouse" => $range['warehouse'], ":storage_id" => $storage_id, ":zone" => $location, ":aisle" => $location, - ":rack" => $location, + ":bin" => $location, ]); } return; } foreach ($aisles as $a) { - foreach ($racks as $r) { + foreach ($bins as $r) { $sth->execute([ ":company_id" => $this->company_id, ":warehouse" => $range['warehouse'], ":storage_id" => $storage_id, ":zone" => $range['zone'], ":aisle" => $a, - ":rack" => $r, + ":bin" => $r, ]); } } @@ -522,11 +522,11 @@ class WarehouseManager { // ───────────────────────────────────────────────────────────── /** - * Return all warehouses with rack statistics and manager name. + * Return all warehouses with bin statistics and manager name. * * Used by the warehouse listing page (/inventory/warehouse.php). - * Aggregates total_racks, occupied_racks, and unique_product per warehouse - * from md_rack, and joins the wms.user table for the manager name. + * Aggregates total_bins, occupied_bins, and unique_product per warehouse + * from md_bin, and joins the wms.user table for the manager name. * * @return array All md_warehouse rows for this company with capacity/occupancy fields. */ @@ -535,8 +535,8 @@ class WarehouseManager { $sth = $this->pdo->prepare( "SELECT a.*, - COALESCE(r.total_racks, 0) AS capacity, - COALESCE(r.occupied_racks, 0) AS space_used, + COALESCE(r.total_bins, 0) AS capacity, + COALESCE(r.occupied_bins, 0) AS space_used, COALESCE(r.unique_product, 0) AS unique_product, c.name AS manager_name, c.surname AS manager_surname @@ -544,10 +544,10 @@ class WarehouseManager { LEFT JOIN ( SELECT company_id, warehouse, - COUNT(*) AS total_racks, - COUNT(product_sku) AS occupied_racks, + COUNT(*) AS total_bins, + COUNT(product_sku) AS occupied_bins, COUNT(DISTINCT product_sku) AS unique_product - FROM md_rack + FROM md_bin GROUP BY company_id, warehouse ) r ON a.company_id = r.company_id AND a.id = r.warehouse LEFT JOIN wms.user c ON a.manager = c.user_id @@ -803,17 +803,17 @@ class WarehouseManager { } /** - * Insert a new storage record or update an existing one, then sync md_rack rows. + * Insert a new storage record or update an existing one, then sync md_bin rows. * - * After saving md_storage, calls syncRacks() to ensure the md_rack table - * exactly matches the new aisle × rack range. On update, existing occupied - * racks that fall outside the new range will block the operation (via syncRacks). + * After saving md_storage, calls syncBins() to ensure the md_bin table + * exactly matches the new aisle × bin range. On update, existing occupied + * bins that fall outside the new range will block the operation (via syncBins). * * Pass $data['id'] = 0 to insert; pass $data['id'] > 0 to update. * Must be called inside dbTransaction() by the caller. * * @param array $data Keys: id, warehouse, zone, aisle_from, aisle_to, - * rack_from, rack_to, description, status. + * bin_from, bin_to, description, status. * @param array $logging Audit entry to append to the log column. */ public function saveStorage(array $data, array $logging, bool $advanced_location = false): void @@ -824,20 +824,20 @@ class WarehouseManager { $zone = trim((string)($data['zone'] ?? '')); $aisle_from = trim((string)($data['aisle_from'] ?? '')); $aisle_to = trim((string)($data['aisle_to'] ?? '')); - $rack_from = trim((string)($data['rack_from'] ?? '')); - $rack_to = trim((string)($data['rack_to'] ?? '')); + $bin_from = trim((string)($data['bin_from'] ?? '')); + $bin_to = trim((string)($data['bin_to'] ?? '')); if ($warehouse === '') { throw new Exception("Warehouse is required."); } if ($advanced_location) { - if ($zone === '' || $aisle_from === '' || $aisle_to === '' || $rack_from === '' || $rack_to === '') { - throw new Exception("Zone, aisle range, and rack range are required."); + if ($zone === '' || $aisle_from === '' || $aisle_to === '' || $bin_from === '' || $bin_to === '') { + throw new Exception("Zone, aisle range, and bin range are required."); } } else { - $location_from = $rack_from !== '' ? $rack_from : $aisle_from; - $location_to = $rack_to !== '' ? $rack_to : $aisle_to; + $location_from = $bin_from !== '' ? $bin_from : $aisle_from; + $location_to = $bin_to !== '' ? $bin_to : $aisle_to; if ($location_from === '') { throw new Exception("Location From is required."); @@ -849,8 +849,8 @@ class WarehouseManager { $zone = $location_from; $aisle_from = $location_from; $aisle_to = $location_to; - $rack_from = $location_from; - $rack_to = $location_to; + $bin_from = $location_from; + $bin_to = $location_to; } $sth = $this->pdo->prepare( @@ -867,8 +867,8 @@ class WarehouseManager { ':zone' => $zone, ':aisle_from' => $aisle_from, ':aisle_to' => $aisle_to, - ':rack_from' => $rack_from, - ':rack_to' => $rack_to, + ':bin_from' => $bin_from, + ':bin_to' => $bin_to, ':description' => $data['description'] ?? '', ':status' => (int)($data['status'] ?? 1), ':log' => json_encode($table_log, JSON_UNESCAPED_UNICODE), @@ -882,8 +882,8 @@ class WarehouseManager { zone = :zone, aisle_from = :aisle_from, aisle_to = :aisle_to, - rack_from = :rack_from, - rack_to = :rack_to, + bin_from = :bin_from, + bin_to = :bin_to, description = :description, status = :status, `log` = :log @@ -895,37 +895,37 @@ class WarehouseManager { $this->pdo->prepare( "INSERT INTO md_storage (company_id, warehouse, zone, aisle_from, aisle_to, - rack_from, rack_to, description, status, `log`) + bin_from, bin_to, description, status, `log`) VALUES (:company_id, :warehouse, :zone, :aisle_from, :aisle_to, - :rack_from, :rack_to, :description, :status, :log)" + :bin_from, :bin_to, :description, :status, :log)" )->execute($params); $storage_id = (int)$this->pdo->lastInsertId(); } - // Sync md_rack rows to exactly match the new aisle × rack range - $this->syncRacks($storage_id, [ + // Sync md_bin rows to exactly match the new aisle × bin range + $this->syncBins($storage_id, [ 'warehouse' => $warehouse, 'zone' => $zone, 'aisle_from' => $aisle_from, 'aisle_to' => $aisle_to, - 'rack_from' => $rack_from, - 'rack_to' => $rack_to, + 'bin_from' => $bin_from, + 'bin_to' => $bin_to, ], $advanced_location); } /** - * Soft-delete a storage record and its associated rack rows. + * Soft-delete a storage record and its associated bin rows. * - * Blocks deletion if any rack under this storage_id is occupied. - * Soft-deletes md_rack rows (company_id negation) and retains td_rack_log + * Blocks deletion if any bin under this storage_id is occupied. + * Soft-deletes md_bin rows (company_id negation) and retains td_bin_log * for audit purposes. Then soft-deletes md_storage. * * Must be called inside dbTransaction() by the caller. * * @param int $storage_id The md_storage.id to delete. - * @throws Exception If storage is not found or has occupied racks. + * @throws Exception If storage is not found or has occupied bins. */ public function deleteStorage(int $storage_id): void { @@ -943,9 +943,9 @@ class WarehouseManager { throw new Exception("Storage not found."); } - // Block if any rack under this storage is occupied + // Block if any bin under this storage is occupied $sth = $this->pdo->prepare( - "SELECT COUNT(*) FROM md_rack + "SELECT COUNT(*) FROM md_bin WHERE company_id = :company_id AND storage_id = :storage_id AND product_sku IS NOT NULL" @@ -958,13 +958,13 @@ class WarehouseManager { if ($sth->fetchColumn() > 0) { throw new Exception( "Cannot delete — storage zone \"{$row['zone']}\" " . - "still has occupied racks with stock." + "still has occupied bins with stock." ); } - // Soft-delete all md_rack rows under this storage + // Soft-delete all md_bin rows under this storage $this->pdo->prepare( - "UPDATE md_rack + "UPDATE md_bin SET company_id = company_id * -1 WHERE company_id = :company_id AND storage_id = :storage_id" @@ -991,7 +991,7 @@ class WarehouseManager { } // ───────────────────────────────────────────────────────────── - // MASTER FILE BASIS — Zone / Aisle / Rack (admin read-only views) + // MASTER FILE BASIS — Zone / Aisle / Bin (admin read-only views) // ───────────────────────────────────────────────────────────── /** @@ -1007,7 +1007,7 @@ class WarehouseManager { public function getZonesAll(int $warehouse_id): array { $sth = $this->pdo->prepare( - "SELECT DISTINCT zone FROM md_rack + "SELECT DISTINCT zone FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse ORDER BY CAST(zone AS UNSIGNED), zone" ); @@ -1027,7 +1027,7 @@ class WarehouseManager { public function getAislesAll(int $warehouse_id, string $zone): array { $sth = $this->pdo->prepare( - "SELECT DISTINCT aisle FROM md_rack + "SELECT DISTINCT aisle FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone ORDER BY CAST(aisle AS UNSIGNED), aisle" ); @@ -1036,34 +1036,34 @@ class WarehouseManager { } /** - * Return all distinct racks in an aisle — for admin/read-only listing. + * Return all distinct bins in an aisle — for admin/read-only listing. * - * Returns every rack regardless of occupancy. Used on storage admin pages. + * Returns every bin regardless of occupancy. Used on storage admin pages. * * @param int $warehouse_id The md_warehouse.id to query. * @param string $zone The zone to filter by. * @param string $aisle The aisle to filter by. - * @return array Flat array of rack values (strings). + * @return array Flat array of bin values (strings). */ - public function getRacksAll(int $warehouse_id, string $zone, string $aisle): array + public function getBinsAll(int $warehouse_id, string $zone, string $aisle): array { if ($zone === '' || $aisle === '') { $sth = $this->pdo->prepare( - "SELECT DISTINCT rack FROM md_rack + "SELECT DISTINCT bin FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse - ORDER BY CAST(rack AS UNSIGNED), rack" + ORDER BY CAST(bin AS UNSIGNED), bin" ); $sth->execute([':company_id' => $this->company_id, ':warehouse' => $warehouse_id]); - return array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'rack'); + return array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'bin'); } $sth = $this->pdo->prepare( - "SELECT DISTINCT rack FROM md_rack + "SELECT DISTINCT bin FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone AND aisle = :aisle - ORDER BY CAST(rack AS UNSIGNED), rack" + ORDER BY CAST(bin AS UNSIGNED), bin" ); $sth->execute([':company_id' => $this->company_id, ':warehouse' => $warehouse_id, ':zone' => $zone, ':aisle' => $aisle]); - return array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'rack'); + return array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'bin'); } // ───────────────────────────────────────────────────────────── @@ -1123,8 +1123,8 @@ class WarehouseManager { /** * Return distinct active lots for a product_sku across all warehouses. * - * "Active" means the rack linked to the stock_in row is still occupied - * (md_rack.product_sku IS NOT NULL). Includes expiry_date from md_lot. + * "Active" means the bin linked to the stock_in row is still occupied + * (md_bin.product_sku IS NOT NULL). Includes expiry_date from md_lot. * Deduplicates across warehouses so each lot_number appears once. * Used to populate the lot dropdown on stock_out forms. * @@ -1158,7 +1158,7 @@ class WarehouseManager { AND s.status = 1 AND s.lot_number IS NOT NULL AND EXISTS ( - SELECT 1 FROM md_rack r + SELECT 1 FROM md_bin r WHERE r.company_id = s.company_id AND r.warehouse = :warehouse_id AND r.td_stock_id = s.id @@ -1183,7 +1183,7 @@ class WarehouseManager { * Return distinct active serial numbers for a product_sku (and optional lot) * across all warehouses. * - * "Active" means the rack linked to the stock_in row is still occupied. + * "Active" means the bin linked to the stock_in row is still occupied. * Used to populate the serial dropdown on stock_out forms. * * @param string $product_sku The SKU to find active serials for. @@ -1213,7 +1213,7 @@ class WarehouseManager { AND s.status = 1 AND s.serial_number IS NOT NULL AND EXISTS ( - SELECT 1 FROM md_rack r + SELECT 1 FROM md_bin r WHERE r.company_id = s.company_id AND r.warehouse = {$wh_id} AND r.td_stock_id = s.id @@ -1295,31 +1295,31 @@ class WarehouseManager { } // ───────────────────────────────────────────────────────────── - // TRANSACTION BASIS — Rack lifecycle + // TRANSACTION BASIS — Bin lifecycle // ───────────────────────────────────────────────────────────── /** - * Sync md_rack rows for a storage record to exactly match a new aisle × rack range. + * Sync md_bin rows for a storage record to exactly match a new aisle × bin range. * * Steps: - * 1. Validates that no occupied racks fall outside the new range (blocks if so). - * 2. Hard-deletes all empty racks belonging to this storage_id. - * 3. Re-inserts the full set of racks for the new range (INSERT IGNORE skips existing occupied racks). + * 1. Validates that no occupied bins fall outside the new range (blocks if so). + * 2. Hard-deletes all empty bins belonging to this storage_id. + * 3. Re-inserts the full set of bins for the new range (INSERT IGNORE skips existing occupied bins). * * Called by saveStorage after every insert or update. * Must be called inside a DB transaction by the caller. * * @param int $storage_id The md_storage.id this range belongs to. - * @param array $range Keys: warehouse, zone, aisle_from, aisle_to, rack_from, rack_to. - * @throws Exception If occupied racks would be removed by the new range. + * @param array $range Keys: warehouse, zone, aisle_from, aisle_to, bin_from, bin_to. + * @throws Exception If occupied bins would be removed by the new range. */ - public function syncRacks(int $storage_id, array $range, bool $advanced_location): void { + public function syncBins(int $storage_id, array $range, bool $advanced_location): void { - // Guard: refuse if any occupied rack under this storage_id falls outside the new range + // Guard: refuse if any occupied bin under this storage_id falls outside the new range $this->validateRangeChange($storage_id, $range, $advanced_location); - // Wipe all empty racks belonging to this storage_id - $sql = "DELETE FROM md_rack + // Wipe all empty bins belonging to this storage_id + $sql = "DELETE FROM md_bin WHERE company_id = :company_id AND storage_id = :storage_id AND product_sku IS NULL"; @@ -1330,19 +1330,19 @@ class WarehouseManager { ':storage_id' => $storage_id, ]); - // Reinsert the full range fresh (INSERT IGNORE skips any occupied racks already there) - $this->insertRacksInRange($storage_id, $range, $advanced_location); + // Reinsert the full range fresh (INSERT IGNORE skips any occupied bins already there) + $this->insertBinsInRange($storage_id, $range, $advanced_location); } - private function getRackRow(int $warehouse_id, string $zone, string $aisle, string $rack): array|false + private function getBinRow(int $warehouse_id, string $zone, string $aisle, string $bin): array|false { $sql = "SELECT id, product_sku, td_stock_id - FROM md_rack + FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone AND aisle = :aisle - AND rack = :rack + AND bin = :bin LIMIT 1"; $sth = $this->pdo->prepare($sql); @@ -1351,7 +1351,7 @@ class WarehouseManager { ':warehouse' => $warehouse_id, ':zone' => $zone, ':aisle' => $aisle, - ':rack' => $rack, + ':bin' => $bin, ]); return $sth->fetch(PDO::FETCH_ASSOC); @@ -1362,23 +1362,23 @@ class WarehouseManager { int $warehouse_id, string $zone, string $aisle, - string $rack, + string $bin, string $product_sku = '', string $lot_number = '', string $serial_number = '' ): void { - if (!$warehouse_id || $zone === '' || $aisle === '' || $rack === '') { + if (!$warehouse_id || $zone === '' || $aisle === '' || $bin === '') { throw new Exception('Location label is incomplete.'); } - $rack_row = $this->getRackRow($warehouse_id, $zone, $aisle, $rack); - if (!$rack_row) { - throw new Exception("Location {$zone}-{$aisle}-{$rack} does not exist."); + $bin_row = $this->getBinRow($warehouse_id, $zone, $aisle, $bin); + if (!$bin_row) { + throw new Exception("Location {$zone}-{$aisle}-{$bin} does not exist."); } if ($mode === 'in' || $mode === 'to') { - if ($rack_row['product_sku'] !== null) { - throw new Exception("Location {$zone}-{$aisle}-{$rack} is already occupied by {$rack_row['product_sku']}."); + if ($bin_row['product_sku'] !== null) { + throw new Exception("Location {$zone}-{$aisle}-{$bin} is already occupied by {$bin_row['product_sku']}."); } if ($mode === 'in' && $product_sku !== '') { @@ -1393,9 +1393,9 @@ class WarehouseManager { throw new Exception('Scan the SKU label first.'); } - $stock = $this->getRackStock($warehouse_id, $zone, $aisle, $rack); + $stock = $this->getBinStock($warehouse_id, $zone, $aisle, $bin); if (!$stock) { - throw new Exception("Location {$zone}-{$aisle}-{$rack} has no approved stock to move out."); + throw new Exception("Location {$zone}-{$aisle}-{$bin} has no approved stock to move out."); } if ($stock['product_sku'] !== $product_sku) { @@ -1417,30 +1417,30 @@ class WarehouseManager { } /** - * Fetch the stock record currently occupying a rack. + * Fetch the stock record currently occupying a bin. * - * Under the 1:1 rack model, each occupied rack's md_rack.td_stock_id points + * Under the 1:1 bin model, each occupied bin's md_bin.td_stock_id points * to exactly one td_stock_ row. This method resolves that pointer and - * returns the full stock row, or null if the rack is empty or the link is broken. + * returns the full stock row, or null if the bin is empty or the link is broken. * - * Used by saveStockOut and saveStockTransfer to validate rack contents before + * Used by saveStockOut and saveStockTransfer to validate bin contents before * allowing a movement. * - * @param int|string $warehouse_id The warehouse the rack belongs to. + * @param int|string $warehouse_id The warehouse the bin belongs to. * @param string $zone Zone identifier. * @param string $aisle Aisle identifier. - * @param string $rack Rack identifier. - * @return array|null The full td_stock row linked to the rack, or null if empty. + * @param string $bin Bin identifier. + * @return array|null The full td_stock row linked to the bin, or null if empty. */ - public function getRackStock($warehouse_id, $zone, $aisle, $rack): ?array { + public function getBinStock($warehouse_id, $zone, $aisle, $bin): ?array { - // Get the stock pointer from md_rack - $sql = "SELECT td_stock_id FROM md_rack + // Get the stock pointer from md_bin + $sql = "SELECT td_stock_id FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone AND aisle = :aisle - AND rack = :rack"; + AND bin = :bin"; $sth = $this->pdo->prepare($sql); $sth->execute([ @@ -1448,17 +1448,17 @@ class WarehouseManager { ":warehouse" => $warehouse_id, ":zone" => $zone, ":aisle" => $aisle, - ":rack" => $rack, + ":bin" => $bin, ]); $td_stock_id = $sth->fetchColumn(); if (!$td_stock_id) { - return null; // Rack is empty or doesn't exist + return null; // Bin is empty or doesn't exist } // Fetch the full stock row from the per-warehouse table. Source // movements can only consume approved stock-in rows; draft stock-in - // rows may reserve a rack but are not available inventory yet. + // rows may reserve a bin but are not available inventory yet. $context = $this->getStockContext($warehouse_id, $td_stock_id); $row = $context['row'] ?: null; @@ -1470,18 +1470,18 @@ class WarehouseManager { } /** - * Validate that a sku + lot + serial combination is not already active in any rack. + * Validate that a sku + lot + serial combination is not already active in any bin. * - * "Active" means the td_stock row is currently linked to an occupied md_rack slot. + * "Active" means the td_stock row is currently linked to an occupied md_bin slot. * Enforces uniqueness rules: - * - SKU alone → allowed in multiple racks (normal stocking) - * - SKU + lot → allowed in multiple racks (lot spread across locations) + * - SKU alone → allowed in multiple bins (normal stocking) + * - SKU + lot → allowed in multiple bins (lot spread across locations) * - SKU + serial → must be unique (one physical unit = one location) * - SKU + lot + serial → must be unique * - * Called from occupyRack to prevent double-stocking the same serialised unit. + * Called from occupyBin to prevent double-stocking the same serialised unit. * The $exclude_stock_id parameter skips the row just inserted (prevents self-conflict - * in insert flows where the row exists but md_rack has not yet been updated). + * in insert flows where the row exists but md_bin has not yet been updated). * * @param string $product_sku SKU to validate. * @param string|null $lot_number Lot number to check (or null to skip). @@ -1514,19 +1514,19 @@ class WarehouseManager { $wh_id = (int)$wh['id']; $table = $this->stockTableNameFromWarehouseId($wh_id); - // Find any stock_in row for this SKU that is still rack-linked (active) + // Find any stock_in row for this SKU that is still bin-linked (active) // and matches the lot/serial combination being validated. // The exclude_id skips the just-inserted row to avoid false self-conflict. - // r.warehouse = :warehouse_id scopes the rack check to THIS warehouse only — + // r.warehouse = :warehouse_id scopes the bin check to THIS warehouse only — // td_stock_id values are per-table integers, not globally unique, so without - // this filter a rack in a different warehouse could cause a false positive. + // this filter a bin in a different warehouse could cause a false positive. $sql = "SELECT s.id FROM `{$table}` s WHERE s.company_id = :company_id AND s.product_sku = :product_sku AND s.`in` > 0 AND EXISTS ( - SELECT 1 FROM md_rack r + SELECT 1 FROM md_bin r WHERE r.company_id = :company_id2 AND r.warehouse = :warehouse_id AND r.td_stock_id = s.id @@ -1571,32 +1571,32 @@ class WarehouseManager { } /** - * Assign a product to an empty rack and link it to a td_stock row. + * Assign a product to an empty bin and link it to a td_stock row. * - * Uses FOR UPDATE to lock the rack row and prevent concurrent occupancy. + * Uses FOR UPDATE to lock the bin row and prevent concurrent occupancy. * Also calls validateStockUnique to enforce no-duplicate-serial rules unless * the caller is replaying a validated source transaction, such as a return. - * Updates md_rack state (product_sku + td_stock_id) in a single UPDATE - * and appends a rack log entry. + * Updates md_bin state (product_sku + td_stock_id) in a single UPDATE + * and appends a bin log entry. * * Must be called inside a DB transaction by the caller. * - * @param int|string $warehouse_id Warehouse the rack belongs to. + * @param int|string $warehouse_id Warehouse the bin belongs to. * @param string $zone Zone identifier. * @param string $aisle Aisle identifier. - * @param string $rack Rack identifier. - * @param string $product_sku SKU to assign to this rack. + * @param string $bin Bin identifier. + * @param string $product_sku SKU to assign to this bin. * @param int $td_stock_id The td_stock_.id to link. - * @throws Exception If the rack does not exist or is already occupied. + * @throws Exception If the bin does not exist or is already occupied. */ - public function occupyRack($warehouse_id, $zone, $aisle, $rack, $product_sku, $td_stock_id, bool $skip_unique_validation = false): void { + public function occupyBin($warehouse_id, $zone, $aisle, $bin, $product_sku, $td_stock_id, bool $skip_unique_validation = false): void { - $sql = "SELECT id, product_sku, td_stock_id FROM md_rack + $sql = "SELECT id, product_sku, td_stock_id FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone AND aisle = :aisle - AND rack = :rack + AND bin = :bin FOR UPDATE"; $sth = $this->pdo->prepare($sql); @@ -1605,12 +1605,12 @@ class WarehouseManager { ":warehouse" => $warehouse_id, ":zone" => $zone, ":aisle" => $aisle, - ":rack" => $rack, + ":bin" => $bin, ]); $current = $sth->fetch(PDO::FETCH_ASSOC); if (!$current) { - throw new Exception("Rack {$zone}-{$aisle}-{$rack} does not exist"); + throw new Exception("Bin {$zone}-{$aisle}-{$bin} does not exist"); } if ($current['product_sku'] !== null) { @@ -1622,12 +1622,12 @@ class WarehouseManager { } throw new Exception( - "Rack {$zone}-{$aisle}-{$rack} is already occupied by {$current['product_sku']}" + "Bin {$zone}-{$aisle}-{$bin} is already occupied by {$current['product_sku']}" ); } if (!$skip_unique_validation) { - // Validate sku + lot + serial uniqueness before linking the rack + // Validate sku + lot + serial uniqueness before linking the bin $ctx = $this->getStockContext($warehouse_id, $td_stock_id); $row = $ctx['row'] ?? null; @@ -1642,7 +1642,7 @@ class WarehouseManager { } // Atomic UPDATE: set state and link in one statement - $sql = "UPDATE md_rack + $sql = "UPDATE md_bin SET product_sku = :product_sku, td_stock_id = :td_stock_id WHERE id = :id"; @@ -1654,34 +1654,34 @@ class WarehouseManager { ":id" => $current['id'], ]); - $this->insertRackLog($current['id'], 'occupy', [ + $this->insertBinLog($current['id'], 'occupy', [ 'product_sku' => $product_sku, 'td_stock_id' => $td_stock_id, ]); } /** - * Release a rack — clear its product_sku and td_stock_id link. + * Release a bin — clear its product_sku and td_stock_id link. * - * Uses FOR UPDATE to lock the rack row. If the rack is already empty, - * exits silently (idempotent: safe to call on an already-empty rack). - * Appends a rack log entry on successful release. + * Uses FOR UPDATE to lock the bin row. If the bin is already empty, + * exits silently (idempotent: safe to call on an already-empty bin). + * Appends a bin log entry on successful release. * * Must be called inside a DB transaction by the caller. * - * @param int|string $warehouse_id Warehouse the rack belongs to. + * @param int|string $warehouse_id Warehouse the bin belongs to. * @param string $zone Zone identifier. * @param string $aisle Aisle identifier. - * @param string $rack Rack identifier. + * @param string $bin Bin identifier. */ - public function releaseRack($warehouse_id, $zone, $aisle, $rack): void { + public function releaseBin($warehouse_id, $zone, $aisle, $bin): void { - $sql = "SELECT id, product_sku, td_stock_id FROM md_rack + $sql = "SELECT id, product_sku, td_stock_id FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone AND aisle = :aisle - AND rack = :rack + AND bin = :bin FOR UPDATE"; $sth = $this->pdo->prepare($sql); @@ -1690,7 +1690,7 @@ class WarehouseManager { ":warehouse" => $warehouse_id, ":zone" => $zone, ":aisle" => $aisle, - ":rack" => $rack, + ":bin" => $bin, ]); $current = $sth->fetch(PDO::FETCH_ASSOC); @@ -1700,7 +1700,7 @@ class WarehouseManager { } // Clear both state fields in a single UPDATE - $sql = "UPDATE md_rack + $sql = "UPDATE md_bin SET product_sku = NULL, td_stock_id = NULL WHERE id = :id"; @@ -1708,18 +1708,18 @@ class WarehouseManager { $sth = $this->pdo->prepare($sql); $sth->execute([":id" => $current['id']]); - $this->insertRackLog($current['id'], 'release', [ + $this->insertBinLog($current['id'], 'release', [ 'product_sku' => $current['product_sku'], 'td_stock_id' => $current['td_stock_id'], ]); } /** - * Move a rack assignment from one location to another. + * Move a bin assignment from one location to another. * * Transfers both product_sku and td_stock_id together from the source - * rack to the destination rack. The source must be occupied; the destination - * must be empty. Both racks are locked in id-ascending order to prevent + * bin to the destination bin. The source must be occupied; the destination + * must be empty. Both bins are locked in id-ascending order to prevent * deadlocks under concurrent transfers. * * Must be called inside a DB transaction by the caller. @@ -1727,29 +1727,29 @@ class WarehouseManager { * @param int|string $from_warehouse Source warehouse. * @param string $from_zone Source zone. * @param string $from_aisle Source aisle. - * @param string $from_rack Source rack. + * @param string $from_bin Source bin. * @param int|string $to_warehouse Destination warehouse. * @param string $to_zone Destination zone. * @param string $to_aisle Destination aisle. - * @param string $to_rack Destination rack. - * @throws Exception If source/destination racks are not found, source is empty, + * @param string $to_bin Destination bin. + * @throws Exception If source/destination bins are not found, source is empty, * or destination is already occupied. */ - public function transferRack( - $from_warehouse, $from_zone, $from_aisle, $from_rack, - $to_warehouse, $to_zone, $to_aisle, $to_rack + public function transferBin( + $from_warehouse, $from_zone, $from_aisle, $from_bin, + $to_warehouse, $to_zone, $to_aisle, $to_bin ): void { - // Lock both racks ordered by id to prevent deadlocks on concurrent transfers - $sql = "SELECT id, warehouse, zone, aisle, rack, product_sku, td_stock_id - FROM md_rack + // Lock both bins ordered by id to prevent deadlocks on concurrent transfers + $sql = "SELECT id, warehouse, zone, aisle, bin, product_sku, td_stock_id + FROM md_bin WHERE company_id = :company_id AND ( (warehouse = :from_wh AND zone = :from_zone - AND aisle = :from_aisle AND rack = :from_rack) + AND aisle = :from_aisle AND bin = :from_bin) OR (warehouse = :to_wh AND zone = :to_zone - AND aisle = :to_aisle AND rack = :to_rack) + AND aisle = :to_aisle AND bin = :to_bin) ) ORDER BY id FOR UPDATE"; @@ -1760,40 +1760,40 @@ class WarehouseManager { ":from_wh" => $from_warehouse, ":from_zone" => $from_zone, ":from_aisle" => $from_aisle, - ":from_rack" => $from_rack, + ":from_bin" => $from_bin, ":to_wh" => $to_warehouse, ":to_zone" => $to_zone, ":to_aisle" => $to_aisle, - ":to_rack" => $to_rack, + ":to_bin" => $to_bin, ]); - $racks = $sth->fetchAll(PDO::FETCH_ASSOC); + $bins = $sth->fetchAll(PDO::FETCH_ASSOC); // Identify source and destination from the fetched rows $from = null; $to = null; - foreach ($racks as $r) { + foreach ($bins as $r) { if ($r['warehouse'] == $from_warehouse && $r['zone'] == $from_zone - && $r['aisle'] == $from_aisle && $r['rack'] == $from_rack) { + && $r['aisle'] == $from_aisle && $r['bin'] == $from_bin) { $from = $r; } if ($r['warehouse'] == $to_warehouse && $r['zone'] == $to_zone - && $r['aisle'] == $to_aisle && $r['rack'] == $to_rack) { + && $r['aisle'] == $to_aisle && $r['bin'] == $to_bin) { $to = $r; } } if (!$from) { - throw new Exception("Source rack not found"); + throw new Exception("Source bin not found"); } if (!$to) { - throw new Exception("Destination rack not found"); + throw new Exception("Destination bin not found"); } if ($from['product_sku'] === null) { - throw new Exception("Source rack is empty"); + throw new Exception("Source bin is empty"); } if ($to['product_sku'] !== null) { - throw new Exception("Destination rack is already occupied"); + throw new Exception("Destination bin is already occupied"); } // Carry both SKU and stock reference from source to destination @@ -1801,19 +1801,19 @@ class WarehouseManager { $td_stock_id = $from['td_stock_id']; // Clear source - $this->pdo->prepare("UPDATE md_rack SET product_sku = NULL, td_stock_id = NULL WHERE id = :id") + $this->pdo->prepare("UPDATE md_bin SET product_sku = NULL, td_stock_id = NULL WHERE id = :id") ->execute([":id" => $from['id']]); - $this->insertRackLog($from['id'], 'transfer_out', [ + $this->insertBinLog($from['id'], 'transfer_out', [ 'product_sku' => $sku, 'td_stock_id' => $td_stock_id, ]); // Populate destination - $this->pdo->prepare("UPDATE md_rack SET product_sku = :sku, td_stock_id = :td_stock_id WHERE id = :id") + $this->pdo->prepare("UPDATE md_bin SET product_sku = :sku, td_stock_id = :td_stock_id WHERE id = :id") ->execute([":sku" => $sku, ":td_stock_id" => $td_stock_id, ":id" => $to['id']]); - $this->insertRackLog($to['id'], 'transfer_in', [ + $this->insertBinLog($to['id'], 'transfer_in', [ 'product_sku' => $sku, 'td_stock_id' => $td_stock_id, ]); @@ -1870,13 +1870,13 @@ class WarehouseManager { } // ───────────────────────────────────────────────────────────── - // TRANSACTION BASIS — Zone / Aisle / Rack (stock movement selectors) + // TRANSACTION BASIS — Zone / Aisle / Bin (stock movement selectors) // ───────────────────────────────────────────────────────────── /** - * Return distinct zones with at least one empty rack — for stock_in destination selector. + * Return distinct zones with at least one empty bin — for stock_in destination selector. * - * Only zones that have at least one available (empty) rack slot are returned, + * Only zones that have at least one available (empty) bin slot are returned, * so the user cannot direct stock to a fully occupied zone. * * @param int $warehouse_id The warehouse to query. @@ -1885,7 +1885,7 @@ class WarehouseManager { public function getZonesIn(int $warehouse_id): array { $sth = $this->pdo->prepare( - "SELECT DISTINCT zone FROM md_rack + "SELECT DISTINCT zone FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse AND product_sku IS NULL @@ -1898,7 +1898,7 @@ class WarehouseManager { /** * Return distinct zones holding active stock for a sku/lot/serial — for stock_out source selector. * - * Only zones that currently have at least one occupied rack with the matching + * Only zones that currently have at least one occupied bin with the matching * product/lot/serial combination are returned. * * @param int $warehouse_id The warehouse to query. @@ -1922,7 +1922,7 @@ class WarehouseManager { $sth = $this->pdo->prepare( "SELECT DISTINCT r.zone - FROM md_rack r + FROM md_bin r WHERE r.company_id = :company_id AND r.warehouse = :warehouse AND r.product_sku = :product_sku @@ -1942,7 +1942,7 @@ class WarehouseManager { } /** - * Return distinct aisles with at least one empty rack in a zone — for stock_in destination. + * Return distinct aisles with at least one empty bin in a zone — for stock_in destination. * * @param int $warehouse_id The warehouse to query. * @param string $zone Zone to filter by. @@ -1951,7 +1951,7 @@ class WarehouseManager { public function getAislesIn(int $warehouse_id, string $zone): array { $sth = $this->pdo->prepare( - "SELECT DISTINCT aisle FROM md_rack + "SELECT DISTINCT aisle FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone @@ -1993,7 +1993,7 @@ class WarehouseManager { $sth = $this->pdo->prepare( "SELECT DISTINCT r.aisle - FROM md_rack r + FROM md_bin r WHERE r.company_id = :company_id AND r.warehouse = :warehouse AND r.zone = :zone @@ -2014,38 +2014,38 @@ class WarehouseManager { } /** - * Return distinct empty racks in an aisle — for stock_in destination selector. + * Return distinct empty bins in an aisle — for stock_in destination selector. * * @param int $warehouse_id The warehouse to query. * @param string $zone Zone to filter by. * @param string $aisle Aisle to filter by. - * @return array Flat array of rack values (strings). + * @return array Flat array of bin values (strings). */ - public function getRacksIn(int $warehouse_id, string $zone, string $aisle): array + public function getBinsIn(int $warehouse_id, string $zone, string $aisle): array { if ($zone === '' || $aisle === '') { $sth = $this->pdo->prepare( - "SELECT DISTINCT rack FROM md_rack + "SELECT DISTINCT bin FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse AND product_sku IS NULL - ORDER BY CAST(rack AS UNSIGNED), rack" + ORDER BY CAST(bin AS UNSIGNED), bin" ); $sth->execute([ ':company_id' => $this->company_id, ':warehouse' => $warehouse_id, ]); - return array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'rack'); + return array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'bin'); } $sth = $this->pdo->prepare( - "SELECT DISTINCT rack FROM md_rack + "SELECT DISTINCT bin FROM md_bin WHERE company_id = :company_id AND warehouse = :warehouse AND zone = :zone AND aisle = :aisle AND product_sku IS NULL - ORDER BY CAST(rack AS UNSIGNED), rack" + ORDER BY CAST(bin AS UNSIGNED), bin" ); $sth->execute([ ':company_id' => $this->company_id, @@ -2053,11 +2053,11 @@ class WarehouseManager { ':zone' => $zone, ':aisle' => $aisle, ]); - return array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'rack'); + return array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'bin'); } /** - * Return distinct racks holding active stock for a sku/lot/serial in an aisle — for stock_out source. + * Return distinct bins holding active stock for a sku/lot/serial in an aisle — for stock_out source. * * @param int $warehouse_id The warehouse to query. * @param string $zone Zone to filter by. @@ -2065,9 +2065,9 @@ class WarehouseManager { * @param string $product_sku SKU to filter by. * @param string|null $lot_number Optional lot filter. * @param string|null $serial_number Optional serial filter. - * @return array Flat array of rack values (strings). + * @return array Flat array of bin values (strings). */ - public function getRacksOut( + public function getBinsOut( int $warehouse_id, string $zone, string $aisle, @@ -2089,8 +2089,8 @@ class WarehouseManager { } $sth = $this->pdo->prepare( - "SELECT DISTINCT r.rack - FROM md_rack r + "SELECT DISTINCT r.bin + FROM md_bin r WHERE r.company_id = :company_id AND r.warehouse = :warehouse AND r.product_sku = :product_sku @@ -2104,10 +2104,10 @@ class WarehouseManager { {$lot_cond} {$serial_cond} ) - ORDER BY CAST(r.rack AS UNSIGNED), r.rack" + ORDER BY CAST(r.bin AS UNSIGNED), r.bin" ); $sth->execute($params); - return array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'rack'); + return array_column($sth->fetchAll(PDO::FETCH_ASSOC), 'bin'); } // ───────────────────────────────────────────────────────────── @@ -2120,7 +2120,7 @@ class WarehouseManager { * Steps: * 1. Validates this is the globally latest transaction for the SKU. * 2. Soft-deletes the td_stock row (negates company_id). - * 3. Releases the reserved/occupied rack back to empty. + * 3. Releases the reserved/occupied bin back to empty. * 4. Reverses the balance only if the row was approved. * * Must be called inside a DB transaction by the caller. @@ -2155,12 +2155,12 @@ class WarehouseManager { WHERE id = :id AND company_id = :company_id" )->execute([':id' => $stock_id, ':company_id' => $this->company_id]); - // Stock-in reserves the rack at creation time, even while draft. + // Stock-in reserves the bin at creation time, even while draft. // Balance is only applied for approved rows. - $this->releaseRack($warehouse_id, $row['zone'], $row['aisle'], $row['rack']); - $this->softDeleteRackLogs( + $this->releaseBin($warehouse_id, $row['zone'], $row['aisle'], $row['bin']); + $this->softDeleteBinLogs( $warehouse_id, - $row['zone'], $row['aisle'], $row['rack'], + $row['zone'], $row['aisle'], $row['bin'], ['occupy', 'release'], $product_sku, [$stock_id], @@ -2178,7 +2178,7 @@ class WarehouseManager { * Steps: * 1. Validates this is the globally latest transaction for the SKU. * 2. Soft-deletes the td_stock row (negates company_id). - * 3. Re-occupies the rack with the original stock_in batch (via ref_id). + * 3. Re-occupies the bin with the original stock_in batch (via ref_id). * 4. Reverses the balance (adjustBalance with new_qty = 0). * * Must be called inside a DB transaction by the caller. @@ -2215,16 +2215,16 @@ class WarehouseManager { // Only reverse side effects if the row was approved — draft rows never had them applied if ((int)$row['status'] === 1) { - // Re-occupy the rack with the original stock_in batch that was consumed - $this->occupyRack( + // Re-occupy the bin with the original stock_in batch that was consumed + $this->occupyBin( $warehouse_id, - $row['zone'], $row['aisle'], $row['rack'], + $row['zone'], $row['aisle'], $row['bin'], $product_sku, (int)$row['ref_id'] ); - $this->softDeleteRackLogs( + $this->softDeleteBinLogs( $warehouse_id, - $row['zone'], $row['aisle'], $row['rack'], + $row['zone'], $row['aisle'], $row['bin'], ['release', 'occupy'], $product_sku, [(int)$row['ref_id']], @@ -2240,8 +2240,8 @@ class WarehouseManager { * Steps: * 1. Validates this is the globally latest transaction for the SKU. * 2. Soft-deletes both the from and to rows (negates company_id on each). - * 3. Releases the destination rack (which was occupied by the transfer_in). - * 4. Re-occupies the source rack with the original from_stock_id batch. + * 3. Releases the destination bin (which was occupied by the transfer_in). + * 4. Re-occupies the source bin with the original from_stock_id batch. * 5. Reverses balances on both warehouses. * * Must be called inside a DB transaction by the caller. @@ -2297,30 +2297,30 @@ class WarehouseManager { // Only reverse side effects if approved — draft rows never had them applied if ((int)$from_row['status'] === 1) { - // Destination rack was occupied by transfer_in — release it - $this->releaseRack( + // Destination bin was occupied by transfer_in — release it + $this->releaseBin( $to_warehouse, - $to_row['zone'], $to_row['aisle'], $to_row['rack'] + $to_row['zone'], $to_row['aisle'], $to_row['bin'] ); - // Source rack was released by transfer_out — re-occupy with original batch - $this->occupyRack( + // Source bin was released by transfer_out — re-occupy with original batch + $this->occupyBin( $from_warehouse_id, - $from_row['zone'], $from_row['aisle'], $from_row['rack'], + $from_row['zone'], $from_row['aisle'], $from_row['bin'], $product_sku, $from_stock_id ); - $this->softDeleteRackLogs( + $this->softDeleteBinLogs( $to_warehouse, - $to_row['zone'], $to_row['aisle'], $to_row['rack'], + $to_row['zone'], $to_row['aisle'], $to_row['bin'], ['occupy', 'release'], $product_sku, [$ref_id], $from_row['date'] ); - $this->softDeleteRackLogs( + $this->softDeleteBinLogs( $from_warehouse_id, - $from_row['zone'], $from_row['aisle'], $from_row['rack'], + $from_row['zone'], $from_row['aisle'], $from_row['bin'], ['release', 'occupy'], $product_sku, [], @@ -2339,7 +2339,7 @@ class WarehouseManager { // ───────────────────────────────────────────────────────────── /** - * Return warehouse ids that currently hold an approved, rack-occupied stock-in + * Return warehouse ids that currently hold an approved, bin-occupied stock-in * row for a given product_sku + lot_number combination. * * Used to filter the warehouse dropdown in stock-out/transfer when a lot is selected, @@ -2373,7 +2373,7 @@ class WarehouseManager { AND s.`in` > 0 AND s.status = 1 AND EXISTS ( - SELECT 1 FROM md_rack r + SELECT 1 FROM md_bin r WHERE r.company_id = s.company_id AND r.warehouse = :warehouse_id AND r.td_stock_id = s.id @@ -2397,12 +2397,12 @@ class WarehouseManager { } /** - * Return warehouses filtered by rack availability — for stock movement warehouse selectors. + * Return warehouses filtered by bin availability — for stock movement warehouse selectors. * * Used on stock_in / stock_out / transfer forms to present only valid warehouse options: - * type = 'to' → warehouses with at least one empty rack (valid destination) + * type = 'to' → warehouses with at least one empty bin (valid destination) * type = 'from' → warehouses holding the given product_sku (valid source) - * other → all warehouses with any racks + * other → all warehouses with any bins * * Passing $id > 0 bypasses the occupancy filter (edit mode — warehouse is already set). * @@ -2432,7 +2432,7 @@ class WarehouseManager { $stock = $this->pdo->prepare( "SELECT 1 - FROM md_rack r + FROM md_bin r INNER JOIN `{$table}` s ON s.company_id = r.company_id AND s.id = r.td_stock_id @@ -2476,7 +2476,7 @@ class WarehouseManager { FROM md_warehouse w WHERE w.company_id = :company_id AND EXISTS ( - SELECT 1 FROM md_rack r + SELECT 1 FROM md_bin r WHERE r.company_id = w.company_id AND r.warehouse = w.id $product_sku_filter diff --git a/app/ics/api/engine/location_barcode_label.php b/app/ics/api/engine/location_barcode_label.php index 8b5e21a..74ece1b 100644 --- a/app/ics/api/engine/location_barcode_label.php +++ b/app/ics/api/engine/location_barcode_label.php @@ -8,7 +8,7 @@ $warehouse_id = (int)($data['warehouse_id'] ?? 0); $zone = trim($data['zone'] ?? ''); $aisle = trim($data['aisle'] ?? ''); - $rack = trim($data['rack'] ?? ''); + $bin = trim($data['bin'] ?? ''); if ($label_action !== 'list') { require_role($user_role, ['owner', 'admin', 'staff']); @@ -56,14 +56,14 @@ } // Default: create - if ($barcode === '' || !$warehouse_id || $rack === '') { + if ($barcode === '' || !$warehouse_id || $bin === '') { $answer['message'] = 'Location barcode data is incomplete.'; http_response_code(400); exit(json_encode($answer)); } try { - $output = $bm->createLocationLabel($barcode, $warehouse_id, $zone, $aisle, $rack); + $output = $bm->createLocationLabel($barcode, $warehouse_id, $zone, $aisle, $bin); $answer['success'] = 1; $answer['output'] = $output; } catch (Exception $e) { diff --git a/app/ics/api/engine/retrieve_rack.php b/app/ics/api/engine/retrieve_rack.php index deda264..a2d211b 100644 --- a/app/ics/api/engine/retrieve_rack.php +++ b/app/ics/api/engine/retrieve_rack.php @@ -13,11 +13,11 @@ if (isset($data['warehouse_to']) && $type === 'to') { $warehouse = (int)$data['warehouse_to']; $zone = $data['zone_to'] ?? $zone; $aisle = $data['aisle_to'] ?? $aisle; } if (!empty($data['id'])) { - $output = $whMgmt->getRacksAll($warehouse, $zone, $aisle); + $output = $whMgmt->getBinsAll($warehouse, $zone, $aisle); } elseif ($type === 'to') { - $output = $whMgmt->getRacksIn($warehouse, $zone, $aisle); + $output = $whMgmt->getBinsIn($warehouse, $zone, $aisle); } elseif ($type === 'from') { - $output = $whMgmt->getRacksOut( + $output = $whMgmt->getBinsOut( $warehouse, $zone, $aisle, @@ -26,7 +26,7 @@ $data['serial_number'] ?? null ); } else { - $output = $whMgmt->getRacksAll($warehouse, $zone, $aisle); + $output = $whMgmt->getBinsAll($warehouse, $zone, $aisle); } $answer['output'] = $output; diff --git a/app/ics/api/engine/retrieve_warehouse.php b/app/ics/api/engine/retrieve_warehouse.php index 357264b..88071f5 100644 --- a/app/ics/api/engine/retrieve_warehouse.php +++ b/app/ics/api/engine/retrieve_warehouse.php @@ -10,7 +10,7 @@ $id = (int)($data['id'] ?? 0); // When type=from and a lot is selected, filter warehouses to only those - // holding that specific lot in an approved, rack-occupied stock-in row. + // holding that specific lot in an approved, bin-occupied stock-in row. if ($type === 'from' && $sku && $lot) { $answer['output'] = $wh->getWarehousesByLot($sku, $lot); } else { diff --git a/app/ics/api/engine/validate_scan_location.php b/app/ics/api/engine/validate_scan_location.php index 46d70c0..19a8691 100644 --- a/app/ics/api/engine/validate_scan_location.php +++ b/app/ics/api/engine/validate_scan_location.php @@ -11,7 +11,7 @@ $warehouse_id = (int)($data['warehouse_id'] ?? 0); $zone = trim($data['zone'] ?? ''); $aisle = trim($data['aisle'] ?? ''); - $rack = trim($data['rack'] ?? ''); + $bin = trim($data['bin'] ?? ''); $product_sku = trim($data['product_sku'] ?? ''); $lot_number = trim($data['lot_number'] ?? ''); $serial = trim($data['serial_number'] ?? ''); @@ -22,7 +22,7 @@ $warehouse_id, $zone, $aisle, - $rack, + $bin, $product_sku, $lot_number, $serial diff --git a/app/ics/location_barcode_label.php b/app/ics/location_barcode_label.php index 876b1ca..3f22556 100644 --- a/app/ics/location_barcode_label.php +++ b/app/ics/location_barcode_label.php @@ -18,7 +18,7 @@

Location Barcode Labels

-

Print or reprint labels from the rack master list.

+

Print or reprint labels from the bin master list.

- - +
@@ -157,7 +157,7 @@ // ── Location config ─────────────────────────────────────────────────────── var advanced = false; - var label_rack = 'Location'; + var label_bin = 'Location'; var label_zone = 'Zone'; var label_aisle = 'Aisle'; var scan_sku_ready = false; @@ -200,7 +200,7 @@ set_select_value('#zone_' + role, data.zone, data.zone); set_select_value('#aisle_' + role, data.aisle, data.aisle); } - set_select_value('#rack_' + role, data.rack, data.rack); + set_select_value('#bin_' + role, data.bin, data.bin); if (role === 'from') { scan_from_ready = true; @@ -214,7 +214,7 @@ return String($('#warehouse_from').val()) === String(data.warehouse_id) && String($('#zone_from').val()) === String(data.zone) && String($('#aisle_from').val()) === String(data.aisle) - && String($('#rack_from').val()) === String(data.rack); + && String($('#bin_from').val()) === String(data.bin); } function validate_scanned_location(role, data, onSuccess) { @@ -230,7 +230,7 @@ warehouse_id: data.warehouse_id, zone: data.zone, aisle: data.aisle, - rack: data.rack, + bin: data.bin, product_sku: $('#product_sku').attr('secondary') || $('#product_sku').val(), lot_number: $('#lot_number').val(), serial_number: $('#serial_number').val() @@ -243,7 +243,7 @@ $('#warehouse_to').val(''); $('#zone_to').html('').val(''); $('#aisle_to').html('').val(''); - $('#rack_to').html('').val(''); + $('#bin_to').html('').val(''); } function reset_stock_transfer_inputs() { @@ -269,11 +269,11 @@ bootbox.alert('Scan the SKU label first.'); return; } - if (!scan_from_ready && !$('#rack_from').val()) { + if (!scan_from_ready && !$('#bin_from').val()) { bootbox.alert('Scan the from-location label before saving.'); return; } - if (!scan_to_ready && !$('#rack_to').val()) { + if (!scan_to_ready && !$('#bin_to').val()) { bootbox.alert('Scan the to-location label before saving.'); return; } @@ -290,19 +290,19 @@ $('#label_zone_to').text('To ' + label_zone); $('#label_aisle_from').text('From ' + label_aisle); $('#label_aisle_to').text('To ' + label_aisle); - $('#label_rack_from').text('From ' + label_rack); - $('#label_rack_to').text('To ' + label_rack); + $('#label_bin_from').text('From ' + label_bin); + $('#label_bin_to').text('To ' + label_bin); // Update select placeholders $('#zone_from, #zone_to').find('option:first').text('Please select ' + label_zone.toLowerCase()); $('#aisle_from, #aisle_to').find('option:first').text('Please select ' + label_aisle.toLowerCase()); - $('#rack_from, #rack_to').find('option:first').text('Please select ' + label_rack.toLowerCase()); + $('#bin_from, #bin_to').find('option:first').text('Please select ' + label_bin.toLowerCase()); } else { $('#group_zone_from, #group_zone_to').hide(); $('#group_aisle_from, #group_aisle_to').hide(); $('#zone_from, #zone_to, #aisle_from, #aisle_to').prop('required', false).removeClass('is-invalid is-valid'); - $('#label_rack_from').text('From ' + label_rack); - $('#label_rack_to').text('To ' + label_rack); - $('#rack_from, #rack_to').find('option:first').text('Please select ' + label_rack.toLowerCase()); + $('#label_bin_from').text('From ' + label_bin); + $('#label_bin_to').text('To ' + label_bin); + $('#bin_from, #bin_to').find('option:first').text('Please select ' + label_bin.toLowerCase()); } } @@ -310,16 +310,16 @@ // ── Submit ──────────────────────────────────────────────────────────────── function manage_stock_transfer() { - var rack_from_val = $('#rack_from').val(); - var rack_to_val = $('#rack_to').val(); + var bin_from_val = $('#bin_from').val(); + var bin_to_val = $('#bin_to').val(); var sku_val = $('#product_sku').attr('secondary') || $('#product_sku').val(); - // Simple mode — mirror rack value to zone and aisle before submit + // Simple mode — mirror bin value to zone and aisle before submit if (!advanced) { - $('#zone_from').val(rack_from_val); - $('#aisle_from').val(rack_from_val); - $('#zone_to').val(rack_to_val); - $('#aisle_to').val(rack_to_val); + $('#zone_from').val(bin_from_val); + $('#aisle_from').val(bin_from_val); + $('#zone_to').val(bin_to_val); + $('#aisle_to').val(bin_to_val); } return ajax_request({ @@ -330,12 +330,12 @@ 'contact_id': $('#contact').attr('data-id'), 'warehouse_from': $('#warehouse_from').val(), 'warehouse_to': $('#warehouse_to').val(), - 'zone_from': advanced ? $('#zone_from').val() : rack_from_val, - 'aisle_from': advanced ? $('#aisle_from').val() : rack_from_val, - 'rack_from': rack_from_val, - 'zone_to': advanced ? $('#zone_to').val() : rack_to_val, - 'aisle_to': advanced ? $('#aisle_to').val() : rack_to_val, - 'rack_to': rack_to_val, + 'zone_from': advanced ? $('#zone_from').val() : bin_from_val, + 'aisle_from': advanced ? $('#aisle_from').val() : bin_from_val, + 'bin_from': bin_from_val, + 'zone_to': advanced ? $('#zone_to').val() : bin_to_val, + 'aisle_to': advanced ? $('#aisle_to').val() : bin_to_val, + 'bin_to': bin_to_val, 'product_sku': sku_val, 'lot_number': $('#lot_number').val(), 'serial_number': $('#serial_number').val(), @@ -467,7 +467,7 @@ } - // ── Zone / Aisle / Rack loaders ─────────────────────────────────────────── + // ── Zone / Aisle / Bin loaders ─────────────────────────────────────────── function retrieve_zone(type, callback) { var extra = { 'warehouse_from': $('#warehouse_from').val(), @@ -532,7 +532,7 @@ }); } - function retrieve_rack(type, callback) { + function retrieve_bin(type, callback) { var extra = { 'warehouse_from': $('#warehouse_from').val(), 'warehouse_to': $('#warehouse_to').val(), @@ -549,7 +549,7 @@ }); } return ajax_request({ - url: 'ics/api/engine/retrieve_rack.php', + url: 'ics/api/engine/retrieve_bin.php', autoPrepare: true, checkRequired: 0, noLoading: true, @@ -557,11 +557,11 @@ data: Object.assign({ 'type': type }, extra), action: 'read', onSuccess: function(res) { - var option = ''; + var option = ''; $.each(res.output, function(key, item) { option += ''; }); - $('select[name="rack"][role="' + type + '"]').html(option).prop('disabled', false); + $('select[name="bin"][role="' + type + '"]').html(option).prop('disabled', false); if (callback) callback(); } }); @@ -573,7 +573,7 @@ $('#warehouse_from').html('').prop('disabled', true); $('#zone_from').html('').prop('disabled', true); $('#aisle_from').html('').prop('disabled', true); - $('#rack_from').html('').prop('disabled', true); + $('#bin_from').html('').prop('disabled', true); } @@ -616,23 +616,23 @@ $('select[name="zone"][role="from"]').val(data.zone).prop('disabled', true); retrieve_aisle('from', function() { $('select[name="aisle"][role="from"]').val(data.aisle).prop('disabled', true); - retrieve_rack('from', function() { - $('select[name="rack"][role="from"]').val(data.rack).prop('disabled', true); + retrieve_bin('from', function() { + $('select[name="bin"][role="from"]').val(data.bin).prop('disabled', true); populate_to_side(data, to_wh); }); }); }); } else { - // Simple mode — skip zone/aisle cascade, load rack directly - retrieve_rack('from', function() { - $('select[name="rack"][role="from"]').val(data.rack).prop('disabled', true); + // Simple mode — skip zone/aisle cascade, load bin directly + retrieve_bin('from', function() { + $('select[name="bin"][role="from"]').val(data.bin).prop('disabled', true); populate_to_side_simple(data, to_wh); }); } // Common fields $.each(data, function(key, item) { - if (!['warehouse', 'zone', 'aisle', 'rack', 'lot_number', 'serial_number', 'ref'].includes(key)) { + if (!['warehouse', 'zone', 'aisle', 'bin', 'lot_number', 'serial_number', 'ref'].includes(key)) { $('#' + key).val(item); } }); @@ -655,16 +655,16 @@ $('select[name="zone"][role="to"]').val(data.ref.zone).prop('disabled', true); retrieve_aisle('to', function() { $('select[name="aisle"][role="to"]').val(data.ref.aisle).prop('disabled', true); - retrieve_rack('to', function() { - $('select[name="rack"][role="to"]').val(data.ref.rack).prop('disabled', true); + retrieve_bin('to', function() { + $('select[name="bin"][role="to"]').val(data.ref.bin).prop('disabled', true); }); }); }); } function populate_to_side_simple(data, to_wh) { - retrieve_rack('to', function() { - $('select[name="rack"][role="to"]').val(data.ref.rack).prop('disabled', true); + retrieve_bin('to', function() { + $('select[name="bin"][role="to"]').val(data.ref.bin).prop('disabled', true); }); } @@ -717,9 +717,9 @@ retrieve_warehouse('from'); }); - // From warehouse → zone (advanced) or rack directly (simple) + // From warehouse → zone (advanced) or bin directly (simple) $('select[name="warehouse"][role="from"]').on('change', function() { - if (advanced) { retrieve_zone('from'); } else { retrieve_rack('from'); } + if (advanced) { retrieve_zone('from'); } else { retrieve_bin('from'); } }); // From zone → aisle (advanced only) @@ -727,14 +727,14 @@ if (advanced) retrieve_aisle('from'); }); - // From aisle → rack (advanced only) + // From aisle → bin (advanced only) $('select[name="aisle"][role="from"]').on('change', function() { - if (advanced) retrieve_rack('from'); + if (advanced) retrieve_bin('from'); }); - // To warehouse → zone (advanced) or rack directly (simple) + // To warehouse → zone (advanced) or bin directly (simple) $('select[name="warehouse"][role="to"]').on('change', function() { - if (advanced) { retrieve_zone('to'); } else { retrieve_rack('to'); } + if (advanced) { retrieve_zone('to'); } else { retrieve_bin('to'); } }); // To zone → aisle (advanced only) @@ -742,9 +742,9 @@ if (advanced) retrieve_aisle('to'); }); - // To aisle → rack (advanced only) + // To aisle → bin (advanced only) $('select[name="aisle"][role="to"]').on('change', function() { - if (advanced) retrieve_rack('to'); + if (advanced) retrieve_bin('to'); }); diff --git a/app/ics/stock_in.php b/app/ics/stock_in.php index f987de6..df0648d 100644 --- a/app/ics/stock_in.php +++ b/app/ics/stock_in.php @@ -64,7 +64,7 @@ Source Zone Aisle - Rack + Bin Status Action @@ -90,7 +90,7 @@