Files
wms-app/app/ics/manage_stock_transfer.php
T
2026-05-20 10:17:02 +07:00

755 lines
30 KiB
PHP

<?php
session_start();
require '../config.php';
if (!in_array($_SESSION['login_role'] ?? 'viewer', ['owner', 'admin', 'staff'], true)) {
http_response_code(403);
exit('Access denied.');
}
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php';?>
<?php require '../include_sidebar.php';?>
<!-- MAIN CONTENT -->
<main id="content" class="content py-15">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="mb-6">
<h1 class="fs-3 mb-0">Stock Transfer</h1>
</div>
</div>
</div>
<div class="row g">
<div class="col-lg-8 col-12">
<div class="card">
<div class="card-body p-5">
<h2 class="fs-5 mb-4">Move Stock</h2>
<form class="row gx-5">
<!-- Step 1: Product -->
<div class="mb-3 col-lg-3">
<label for="product_sku" class="form-label">SKU</label>
<div class="input-group">
<input type="text" id="product_sku" class="form-control product_search"
placeholder="Type product sku..." autocomplete="off" required>
<button class="btn btn-outline-secondary" type="button" onclick="scanner.open_camera()" title="Scan barcode label">
<i class="ti ti-scan"></i>
</button>
</div>
</div>
<div class="mb-3 col-lg-3">
<label for="product_name" class="form-label">Name</label>
<input type="text" id="product_name" class="form-control" autocomplete="off" disabled>
</div>
<!-- Step 2: Lot Number (active lots for selected SKU) -->
<div class="mb-3 col-lg-3">
<label for="lot_number" class="form-label">Lot Number <span class="text-muted fw-normal">(optional)</span></label>
<select id="lot_number" class="form-select">
<option value="">Select lot...</option>
</select>
</div>
<!-- Step 3: Serial Number (active serials for selected SKU + lot) -->
<div class="mb-3 col-lg-3">
<label for="serial_number" class="form-label">Serial Number <span class="text-muted fw-normal">(optional)</span></label>
<select id="serial_number" class="form-select">
<option value="">Select serial...</option>
</select>
</div>
<div class="col-12"><hr class="my-2"></div>
<!-- From location -->
<div class="col-12 mb-2">
<span class="fw-semibold text-muted">From</span>
</div>
<div class="mb-3 col-lg-3">
<label class="form-label">From Warehouse</label>
<select name="warehouse" id="warehouse_from" role="from" class="form-select" required disabled>
<option value="">Select warehouse...</option>
</select>
</div>
<div class="mb-3 col-lg-3" id="group_zone_from">
<label class="form-label" id="label_zone_from">From Zone</label>
<select name="zone" id="zone_from" role="from" class="form-select" required disabled>
<option value="">Please select zone</option>
</select>
</div>
<div class="mb-3 col-lg-3" id="group_aisle_from">
<label class="form-label" id="label_aisle_from">From Aisle</label>
<select name="aisle" id="aisle_from" role="from" class="form-select" required disabled>
<option value="">Please select aisle</option>
</select>
</div>
<div class="mb-3 col-lg-3">
<label class="form-label" id="label_rack_from">From Rack</label>
<select name="rack" id="rack_from" role="from" class="form-select" required disabled>
<option value="">Please select rack</option>
</select>
</div>
<div class="col-12"><hr class="my-2"></div>
<!-- To location -->
<div class="col-12 mb-2">
<span class="fw-semibold text-muted">To</span>
</div>
<div class="mb-3 col-lg-3">
<label class="form-label">To Warehouse</label>
<select name="warehouse" id="warehouse_to" role="to" class="form-select" required>
<option value="">Select warehouse...</option>
</select>
</div>
<div class="mb-3 col-lg-3" id="group_zone_to">
<label class="form-label" id="label_zone_to">To Zone</label>
<select name="zone" id="zone_to" role="to" class="form-select" required>
<option value="">Please select zone</option>
</select>
</div>
<div class="mb-3 col-lg-3" id="group_aisle_to">
<label class="form-label" id="label_aisle_to">To Aisle</label>
<select name="aisle" id="aisle_to" role="to" class="form-select" required>
<option value="">Please select aisle</option>
</select>
</div>
<div class="mb-3 col-lg-3">
<label class="form-label" id="label_rack_to">To Rack</label>
<select name="rack" id="rack_to" role="to" class="form-select" required>
<option value="">Please select rack</option>
</select>
</div>
<!-- Contact -->
<div class="mb-3 col-lg-6">
<label for="contact" class="form-label">Contact</label>
<input type="text" class="form-control contact_search" id="contact" placeholder="Enter contact name">
</div>
<!-- Notes / Description -->
<div class="mb-3 col-lg-12">
<label for="description" class="form-label">Notes / Description</label>
<textarea class="form-control" id="description" rows="3" placeholder="Enter any notes"></textarea>
</div>
<!-- Submit -->
<div class="mb-3 col-lg-12">
<button type="submit" class="btn btn-primary" data-min-role="staff" onclick="manage_stock_transfer();">Transfer Stock</button>
<button type="button" class="btn btn-success ms-2 d-none" id="approve_stock_btn" onclick="approve_current_stock();">Approve</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</main>
<?php require "../include_ending.php";?>
<script>
// custom.js globals: set_select_value(), load_location_config()
// ── Location config ───────────────────────────────────────────────────────
var advanced = false;
var label_rack = 'Location';
var label_zone = 'Zone';
var label_aisle = 'Aisle';
var scan_sku_ready = false;
var scan_from_ready = false;
var scan_to_ready = false;
var pending_scan_lot = '';
var pending_scan_serial = '';
function apply_sku_label_scan(data) {
$('#product_sku').val(data.sku).attr('secondary', data.sku);
$('#product_name').val(data.product_name || '').attr('secondary', data.product_name || '');
pending_scan_lot = data.lot_number || '';
pending_scan_serial = data.serial_number || '';
set_select_value('#lot_number', data.lot_number, data.lot_number);
set_select_value('#serial_number', data.serial_number, data.serial_number);
scan_sku_ready = true;
scan_from_ready = false;
scan_to_ready = false;
reset_from_location();
reset_to_location();
retrieve_active_lot();
}
function apply_location_label_scan(data) {
if (!scan_sku_ready && !($('#product_sku').attr('secondary') || $('#product_sku').val())) {
bootbox.alert('Scan the SKU label first, then scan the from-location label.');
return;
}
var role = scan_from_ready ? 'to' : 'from';
if (role === 'to' && same_location_as_from(data)) {
bootbox.alert('To-location must be different from from-location.');
return;
}
validate_scanned_location(role, data, function() {
set_select_value('#warehouse_' + role, data.warehouse_id, data.warehouse_id);
if (advanced) {
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);
if (role === 'from') {
scan_from_ready = true;
} else {
scan_to_ready = true;
}
});
}
function same_location_as_from(data) {
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);
}
function validate_scanned_location(role, data, onSuccess) {
ajax_request({
url: '<?php echo $server_url?>ics/api/engine/validate_scan_location.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
queueLock: false,
noLoading: true,
data: {
mode: role,
warehouse_id: data.warehouse_id,
zone: data.zone,
aisle: data.aisle,
rack: data.rack,
product_sku: $('#product_sku').attr('secondary') || $('#product_sku').val(),
lot_number: $('#lot_number').val(),
serial_number: $('#serial_number').val()
},
onSuccess: onSuccess
});
}
function reset_to_location() {
$('#warehouse_to').val('');
$('#zone_to').html('<option value="">Please select ' + label_zone.toLowerCase() + '</option>').val('');
$('#aisle_to').html('<option value="">Please select ' + label_aisle.toLowerCase() + '</option>').val('');
$('#rack_to').html('<option value="">Please select ' + label_rack.toLowerCase() + '</option>').val('');
}
function reset_stock_transfer_inputs() {
$('form')[0].reset();
$('#product_sku').removeAttr('secondary').prop('disabled', false);
$('#product_name').removeAttr('secondary').val('');
$('#lot_number').html('<option value="">Select lot...</option>').prop('disabled', false);
$('#serial_number').html('<option value="">Select serial...</option>').prop('disabled', false);
$('#contact').removeAttr('secondary data-id');
reset_from_location();
reset_to_location();
retrieve_warehouse('to');
scan_sku_ready = false;
scan_from_ready = false;
scan_to_ready = false;
pending_scan_lot = '';
pending_scan_serial = '';
$('#product_sku').focus();
}
function save_scanned_stock_transfer() {
if (!scan_sku_ready && !($('#product_sku').attr('secondary') || $('#product_sku').val())) {
bootbox.alert('Scan the SKU label first.');
return;
}
if (!scan_from_ready && !$('#rack_from').val()) {
bootbox.alert('Scan the from-location label before saving.');
return;
}
if (!scan_to_ready && !$('#rack_to').val()) {
bootbox.alert('Scan the to-location label before saving.');
return;
}
manage_stock_transfer();
}
function apply_location_labels() {
if (advanced) {
$('#group_zone_from, #group_zone_to').show();
$('#group_aisle_from, #group_aisle_to').show();
$('#zone_from, #zone_to, #aisle_from, #aisle_to').prop('required', true);
$('#label_zone_from').text('From ' + label_zone);
$('#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);
// 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());
} 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());
}
}
// ── Submit ────────────────────────────────────────────────────────────────
function manage_stock_transfer() {
var rack_from_val = $('#rack_from').val();
var rack_to_val = $('#rack_to').val();
var sku_val = $('#product_sku').attr('secondary') || $('#product_sku').val();
// Simple mode — mirror rack 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);
}
return ajax_request({
url: '<?php echo $server_url?>ics/api/engine/manage_stock_transfer.php',
autoPrepare: true,
checkRequired: 1,
data: {
'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,
'product_sku': sku_val,
'lot_number': $('#lot_number').val(),
'serial_number': $('#serial_number').val(),
},
action: 'manage',
onSuccess: function() {
<?php if(empty($_GET['id'])){ ?>
reset_stock_transfer_inputs();
<?php } else { ?>
window.location.href = '<?php echo $server_url?>ics/stock_transfer.php';
<?php } ?>
}
});
}
function approve_current_stock() {
bootbox.confirm({
message: 'Approve this stock-transfer record?',
buttons: { confirm: { label: 'Approve', className: 'btn-success' }, cancel: { label: 'Cancel', className: 'btn-secondary' } },
callback: function(result) {
if (!result) return;
ajax_request({
url: '<?php echo $server_url?>ics/api/engine/approve_stock.php',
autoPrepare: true,
checkRequired: 0,
action: 'update',
data: {
id: '<?php echo $_GET["id"] ?? "";?>',
warehouse: '<?php echo $_GET["wh"] ?? "";?>',
type: 'transfer'
},
onSuccess: function() {
window.location.reload();
}
});
}
});
}
// ── Step 1: Product selected → load active lots + from-warehouses ─────────
function retrieve_active_lot() {
var sku = $('#product_sku').attr('secondary') || $('#product_sku').val();
if (!sku) return;
return ajax_request({
url: '<?php echo $server_url?>ics/api/engine/retrieve_active_lot.php',
autoPrepare: true,
checkRequired: 0,
noLoading: true,
queueLock: false,
action: 'read',
data: { 'product_sku': sku },
onSuccess: function(res) {
var option = '<option value="">Select lot... (optional)</option>';
$.each(res.output, function(i, item) {
var label = item.lot_number;
if (item.expiry_date) label += ' — exp: ' + item.expiry_date;
option += '<option value="' + item.lot_number + '">' + label + '</option>';
});
$('#lot_number').html(option).prop('disabled', false);
if (pending_scan_lot) {
set_select_value('#lot_number', pending_scan_lot, pending_scan_lot);
}
$('#serial_number').html('<option value="">Select serial... (optional)</option>').prop('disabled', false);
if (!scan_from_ready) reset_from_location();
if (pending_scan_lot) {
retrieve_active_serial();
} else {
retrieve_warehouse('from');
}
}
});
}
// ── Step 2: Lot selected → load active serials ────────────────────────────
function retrieve_active_serial() {
var sku = $('#product_sku').attr('secondary') || $('#product_sku').val();
if (!sku) return;
return ajax_request({
url: '<?php echo $server_url?>ics/api/engine/retrieve_active_serial.php',
autoPrepare: true,
checkRequired: 0,
noLoading: true,
queueLock: false,
action: 'read',
data: { 'product_sku': sku, 'lot_number': $('#lot_number').val() },
onSuccess: function(res) {
var option = '<option value="">Select serial... (optional)</option>';
$.each(res.output, function(i, serial) {
option += '<option value="' + serial + '">' + serial + '</option>';
});
$('#serial_number').html(option).prop('disabled', false);
if (pending_scan_serial) {
set_select_value('#serial_number', pending_scan_serial, pending_scan_serial);
pending_scan_serial = '';
}
if (!scan_from_ready) reset_from_location();
retrieve_warehouse('from');
}
});
}
// ── Warehouse loaders ─────────────────────────────────────────────────────
function retrieve_warehouse(type) {
return ajax_request({
url: '<?php echo $server_url?>ics/api/engine/retrieve_warehouse.php',
autoPrepare: true,
checkRequired: 0,
noLoading: true,
queueLock: false,
data: {
'type': type,
'product_sku': type === 'from' ? ($('#product_sku').attr('secondary') || $('#product_sku').val()) : '',
'lot_number': type === 'from' ? $('#lot_number').val() : ''
},
action: 'read',
onSuccess: function(res) {
var current = $('#warehouse_' + type).val();
var option = '<option value="">Select warehouse...</option>';
$.each(res.output, function(key, item) {
option += '<option value="' + item.id + '">' + item.warehouse_name + '</option>';
});
$('select[name="warehouse"][role="' + type + '"]').html(option).prop('disabled', false);
if (current) set_select_value('#warehouse_' + type, current, current);
}
});
}
// ── Zone / Aisle / Rack loaders ───────────────────────────────────────────
function retrieve_zone(type, callback) {
var extra = {
'warehouse_from': $('#warehouse_from').val(),
'warehouse_to': $('#warehouse_to').val()
};
if (type === 'from') {
extra = Object.assign(extra, {
'product_sku': $('#product_sku').attr('secondary') || $('#product_sku').val(),
'lot_number': $('#lot_number').val(),
'serial_number': $('#serial_number').val()
});
}
return ajax_request({
url: '<?php echo $server_url?>ics/api/engine/retrieve_zone.php',
autoPrepare: true,
checkRequired: 0,
noLoading: true,
queueLock: false,
data: Object.assign({ 'type': type }, extra),
action: 'read',
onSuccess: function(res) {
var option = '<option value="">Please select ' + label_zone.toLowerCase() + '</option>';
$.each(res.output, function(key, item) {
option += '<option value="' + item.zone + '">' + item.zone + '</option>';
});
$('select[name="zone"][role="' + type + '"]').html(option).prop('disabled', false);
if (callback) callback();
}
});
}
function retrieve_aisle(type, callback) {
var extra = {
'warehouse_from': $('#warehouse_from').val(),
'warehouse_to': $('#warehouse_to').val(),
'zone_from': $('#zone_from').val(),
'zone_to': $('#zone_to').val()
};
if (type === 'from') {
extra = Object.assign(extra, {
'product_sku': $('#product_sku').attr('secondary') || $('#product_sku').val(),
'lot_number': $('#lot_number').val(),
'serial_number': $('#serial_number').val()
});
}
return ajax_request({
url: '<?php echo $server_url?>ics/api/engine/retrieve_aisle.php',
autoPrepare: true,
checkRequired: 0,
noLoading: true,
queueLock: false,
data: Object.assign({ 'type': type }, extra),
action: 'read',
onSuccess: function(res) {
var option = '<option value="">Please select ' + label_aisle.toLowerCase() + '</option>';
$.each(res.output, function(key, item) {
option += '<option value="' + item + '">' + item + '</option>';
});
$('select[name="aisle"][role="' + type + '"]').html(option).prop('disabled', false);
if (callback) callback();
}
});
}
function retrieve_rack(type, callback) {
var extra = {
'warehouse_from': $('#warehouse_from').val(),
'warehouse_to': $('#warehouse_to').val(),
'zone_from': $('#zone_from').val(),
'zone_to': $('#zone_to').val(),
'aisle_from': $('#aisle_from').val(),
'aisle_to': $('#aisle_to').val()
};
if (type === 'from') {
extra = Object.assign(extra, {
'product_sku': $('#product_sku').attr('secondary') || $('#product_sku').val(),
'lot_number': $('#lot_number').val(),
'serial_number': $('#serial_number').val()
});
}
return ajax_request({
url: '<?php echo $server_url?>ics/api/engine/retrieve_rack.php',
autoPrepare: true,
checkRequired: 0,
noLoading: true,
queueLock: false,
data: Object.assign({ 'type': type }, extra),
action: 'read',
onSuccess: function(res) {
var option = '<option value="">Please select ' + label_rack.toLowerCase() + '</option>';
$.each(res.output, function(key, item) {
option += '<option value="' + item + '">' + item + '</option>';
});
$('select[name="rack"][role="' + type + '"]').html(option).prop('disabled', false);
if (callback) callback();
}
});
}
// ── Reset from-location dropdowns ─────────────────────────────────────────
function reset_from_location() {
$('#warehouse_from').html('<option value="">Select warehouse...</option>').prop('disabled', true);
$('#zone_from').html('<option value="">Please select ' + label_zone.toLowerCase() + '</option>').prop('disabled', true);
$('#aisle_from').html('<option value="">Please select ' + label_aisle.toLowerCase() + '</option>').prop('disabled', true);
$('#rack_from').html('<option value="">Please select ' + label_rack.toLowerCase() + '</option>').prop('disabled', true);
}
// ── Edit mode ─────────────────────────────────────────────────────────────
function retrieve_for_edit() {
<?php if(empty($_GET['id'])){ ?>
return false;
<?php } ?>
return ajax_request({
url: '<?php echo $server_url?>ics/api/engine/retrieve_stock_transfer.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var data = res.output;
var from_wh = data.ref.ref_warehouse;
var to_wh = data.ref_warehouse;
// Lock lot/serial — use DOM construction (not .html()) to prevent XSS
$('#lot_number').empty()
.append($('<option>').val(data.lot_number || '').text(data.lot_number || '—'))
.val(data.lot_number || '').prop('disabled', true);
$('#serial_number').empty()
.append($('<option>').val(data.serial_number || '').text(data.serial_number || '—'))
.val(data.serial_number || '').prop('disabled', true);
$('#product_name').val(data.product_name);
$('#product_sku').attr('secondary', data.product_sku).prop('disabled', true);
retrieve_warehouse('from').then(function() {
return retrieve_warehouse('to');
}).then(function() {
$('select[name="warehouse"][role="from"]').val(from_wh).prop('disabled', true);
$('select[name="warehouse"][role="to"]').val(to_wh).prop('disabled', true);
if (advanced) {
// Full cascade for From
retrieve_zone('from', function() {
$('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);
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);
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)) {
$('#' + key).val(item);
}
});
$('#contact').val(data.contact_name)
.attr('secondary', data.contact_name)
.attr('data-id', data.contact_id);
$('button[type=submit]').text('Update');
$('button[type=reset]').hide();
if (data.status == 0) {
$('#approve_stock_btn').removeClass('d-none');
}
});
}
});
}
function populate_to_side(data, to_wh) {
retrieve_zone('to', function() {
$('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);
});
});
});
}
function populate_to_side_simple(data, to_wh) {
retrieve_rack('to', function() {
$('select[name="rack"][role="to"]').val(data.ref.rack).prop('disabled', true);
});
}
// ── Boot ─────────────────────────────────────────────────────────────────
$(async function() {
try {
await load_location_config(apply_location_labels);
<?php if(empty($_GET['id'])){ ?>
await retrieve_warehouse('to');
<?php } ?>
await retrieve_for_edit();
scanner.init({
allowRaw: false,
allowedTypes: ['sku', 'loc'],
onScan: function(sku, product_name, uom, data) {
if (data.type === 'sku') apply_sku_label_scan(data);
if (data.type === 'loc') apply_location_label_scan(data);
}
});
} catch(e) {
console.log(e);
}
});
// ── Change handlers (create mode only) ───────────────────────────────────
<?php if(empty($_GET['id'])){ ?>
$(document).on('keydown', function(e) {
if (e.key === 'F2') {
e.preventDefault();
save_scanned_stock_transfer();
}
});
$('input#product_sku').on('blur', function() {
var sku = $(this).attr('secondary');
if (!sku) return;
retrieve_active_lot();
retrieve_warehouse('from');
});
$('#lot_number').on('change', function() {
retrieve_active_serial();
retrieve_warehouse('from');
});
$('#serial_number').on('change', function() {
retrieve_warehouse('from');
});
// From warehouse → zone (advanced) or rack directly (simple)
$('select[name="warehouse"][role="from"]').on('change', function() {
if (advanced) { retrieve_zone('from'); } else { retrieve_rack('from'); }
});
// From zone → aisle (advanced only)
$('select[name="zone"][role="from"]').on('change', function() {
if (advanced) retrieve_aisle('from');
});
// From aisle → rack (advanced only)
$('select[name="aisle"][role="from"]').on('change', function() {
if (advanced) retrieve_rack('from');
});
// To warehouse → zone (advanced) or rack directly (simple)
$('select[name="warehouse"][role="to"]').on('change', function() {
if (advanced) { retrieve_zone('to'); } else { retrieve_rack('to'); }
});
// To zone → aisle (advanced only)
$('select[name="zone"][role="to"]').on('change', function() {
if (advanced) retrieve_aisle('to');
});
// To aisle → rack (advanced only)
$('select[name="aisle"][role="to"]').on('change', function() {
if (advanced) retrieve_rack('to');
});
<?php } ?>
</script>
</body>
</html>