warehouse layers [switchable]

This commit is contained in:
Thanakorn S
2026-05-04 13:43:32 +07:00
parent a90975d9f1
commit 414f1cd81d
26 changed files with 2022 additions and 584 deletions
+136 -82
View File
@@ -68,22 +68,22 @@
<select name="warehouse" id="warehouse" class="form-select" required></select>
</div>
<div class="mb-3 col-lg-6">
<label for="zone" class="form-label">Zone</label>
<select name="zone" id="zone" class="form-select" required>
<div class="mb-3 col-lg-6" id="group_zone">
<label for="zone" class="form-label" id="label_zone">Zone</label>
<select name="zone" id="zone" class="form-select">
<option value="">Please select zone</option>
</select>
</div>
<div class="mb-3 col-lg-6">
<label for="aisle" class="form-label">Aisle</label>
<select name="aisle" id="aisle" class="form-select" required>
<div class="mb-3 col-lg-6" id="group_aisle">
<label for="aisle" class="form-label" id="label_aisle">Aisle</label>
<select name="aisle" id="aisle" class="form-select">
<option value="">Please select aisle</option>
</select>
</div>
<div class="mb-3 col-lg-6">
<label for="rack" class="form-label">Rack</label>
<div class="mb-3 col-lg-6" id="group_rack">
<label for="rack" class="form-label" id="label_rack">Rack</label>
<select name="rack" id="rack" class="form-select" required>
<option value="">Please select rack</option>
</select>
@@ -118,24 +118,72 @@
<?php require "../include_ending.php";?>
<script>
var advanced = false;
var label_rack = 'Location';
var label_zone = 'Zone';
var label_aisle = 'Aisle';
function load_location_config(callback) {
return ajax_request({
url: server_url + 'setting/api/engine/company_setting.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var s = res.output;
advanced = s.advanced_location == 1;
label_rack = s.location_label_rack || (advanced ? 'Rack' : 'Location');
label_zone = s.location_label_zone || 'Zone';
label_aisle = s.location_label_aisle || 'Aisle';
if (callback) callback();
}
});
}
function apply_location_labels() {
if (advanced) {
$('#group_zone').show();
$('#group_aisle').show();
$('#label_zone').text(label_zone);
$('#label_aisle').text(label_aisle);
$('#label_rack').text(label_rack);
$('#zone').attr('placeholder', 'Please select ' + label_zone.toLowerCase());
$('#aisle').attr('placeholder', 'Please select ' + label_aisle.toLowerCase());
$('#rack').attr('placeholder', 'Please select ' + label_rack.toLowerCase());
} else {
$('#group_zone').hide();
$('#group_aisle').hide();
$('#label_rack').text(label_rack);
$('#rack').attr('placeholder', 'Please select ' + label_rack.toLowerCase());
}
}
function manage_stock_in() {
// In simple mode, mirror rack value to zone and aisle
if (!advanced) {
var rack_val = $('#rack').val();
$('#zone').val(rack_val);
$('#aisle').val(rack_val);
}
return ajax_request({
url: "<?php echo $server_url?>ics/api/engine/manage_stock_in.php",
url: '<?php echo $server_url?>ics/api/engine/manage_stock_in.php',
autoPrepare: true,
checkRequired: 1,
debugMode: 0,
data: {
"contact_id": $("#contact").attr('data-id'),
"lot_number": $("#lot_number").val(),
"expiry_date": $("#expiry_date").val(),
"serial_number": $("#serial_number").val()
'contact_id': $('#contact').attr('data-id'),
'lot_number': $('#lot_number').val(),
'expiry_date': $('#expiry_date').val(),
'serial_number': $('#serial_number').val(),
'zone': $('#zone').val(),
'aisle': $('#aisle').val(),
},
action: 'manage',
onSuccess: function(res) {
window.location.href = "<?php echo $server_url?>ics/stock_in.php";
onSuccess: function() {
window.location.href = '<?php echo $server_url?>ics/stock_in.php';
}
});
@@ -179,11 +227,11 @@
debugMode: 0,
action: 'read',
onSuccess: function(res) {
var option = `<option value="">Please select zone</option>`;
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#zone`).html(option);
option += '<option value="' + item.zone + '">' + item.zone + '</option>';
});
$('select#zone').html(option);
if (callback) callback(); // ← add this
}
@@ -205,11 +253,11 @@
},
action: 'read',
onSuccess: function(res) {
var option = `<option value="">Please select aisle</option>`;
var option = '<option value="">Please select ' + label_aisle.toLowerCase() + '</option>';
$.each(res.output, function(key, item) {
option += `<option value='${item}'>${item}</option>`;
})
$(`select#aisle`).html(option);
option += '<option value="' + item + '">' + item + '</option>';
});
$('select#aisle').html(option);
if (callback) callback(); // ← add this
}
@@ -231,11 +279,11 @@
},
action: 'read',
onSuccess: function(res) {
var option = `<option value="">Please select rack</option>`;
var option = '<option value="">Please select ' + label_rack.toLowerCase() + '</option>';
$.each(res.output, function(key, item) {
option += `<option value='${item}'>${item}</option>`;
})
$(`select#rack`).html(option);
option += '<option value="' + item + '">' + item + '</option>';
});
$('select#rack').html(option);
if (callback) callback(); // ← add this
}
@@ -261,55 +309,53 @@
var data = res.output;
// 1. Set warehouse (already populated from page load)
$('#warehouse').val('<?php echo $_GET["wh"];?>').attr('disabled', true);
$(`#warehouse`).val('<?php echo $_GET["wh"];?>').attr('disabled', true); // set warehouse BEFORE retrieve_zone
function populate_fields() {
$.each(data, function(key, item) {
if (!['warehouse', 'zone', 'aisle', 'rack'].includes(key)) {
$('#' + key).val(item);
}
});
$('#contact').val(data.contact_name)
.attr('secondary', data.contact_name)
.attr('data-id', data.contact_id);
$('#product_name').val(data.product_name);
$('#lot_number').val(data.lot_number || '').prop('disabled', true);
$('#serial_number').val(data.serial_number || '').prop('disabled', true);
if (data.lot_number) {
if (expiryPicker) { expiryPicker.setDate(data.expiry_date || '', true, 'Y-m-d'); }
$('#expiry_date').prop('disabled', true)
.attr('title', 'Expiry date is locked for existing lots')
.val(data.expiry_date);
if (expiryPicker && expiryPicker.altInput) { expiryPicker.altInput.disabled = true; }
} else {
if (expiryPicker) { expiryPicker.clear(); }
}
$('#product_sku').attr('secondary', data.product_sku);
$('#product_sku, #quantity').prop('disabled', true);
$('button[type=submit]').text('Update');
$('button[type=reset]').hide();
}
// 2. Retrieve zone (autoPrepare reads warehouse value)
retrieve_zone('to', function() {
$(`select#zone`).val(data.zone).attr('disabled', true); // set zone BEFORE retrieve_aisle
// 3. Retrieve aisle (autoPrepare reads zone value)
retrieve_aisle('to', function() {
$(`#aisle`).val(data.aisle).attr('disabled', true); // set aisle BEFORE retrieve_rack
// 4. Retrieve rack (autoPrepare reads aisle value)
retrieve_rack('to', function() {
$(`#rack`).val(data.rack).attr('disabled', true);
// retrieve for edit
$.each(data, function(key, item) {
if (!['warehouse', 'zone', 'aisle', 'rack'].includes(key)) {
$(`#${key}`).val(item);
}
if (advanced) {
retrieve_zone('to', function() {
$('select#zone').val(data.zone).attr('disabled', true);
retrieve_aisle('to', function() {
$('#aisle').val(data.aisle).attr('disabled', true);
retrieve_rack('to', function() {
$('#rack').val(data.rack).attr('disabled', true);
populate_fields();
});
$('#contact').val(data.contact_name)
.attr('secondary', data.contact_name)
.attr('data-id', data.contact_id);
$('#product_name').val(data.product_name);
$('#lot_number').val(data.lot_number || '').prop('disabled', true);
$('#serial_number').val(data.serial_number || '').prop('disabled', true);
if (data.lot_number) {
if (expiryPicker) { expiryPicker.setDate(data.expiry_date || '', true, 'Y-m-d'); }
$('#expiry_date').prop('disabled', true).attr('title', 'Expiry date is locked for existing lots')
.val(data.expiry_date);
if (expiryPicker && expiryPicker.altInput) { expiryPicker.altInput.disabled = true; }
} else {
if (expiryPicker) { expiryPicker.clear(); }
}
$('#product_sku').attr('secondary', data.product_sku);
$('#product_sku,#quantity').prop('disabled', true);
$(`button[type=submit]`).text('Update');
$(`button[type=reset]`).hide();
});
});
});
// Set re
} else {
// Simple mode — skip zone/aisle cascade
retrieve_rack('to', function() {
$('#rack').val(data.rack).attr('disabled', true);
populate_fields();
});
}
@@ -351,13 +397,16 @@
if (wh) {
$('#warehouse').val(wh).prop('disabled', true);
// Chain-load location selects for the locked warehouse.
// Unlike edit mode, we DON'T preselect zone/aisle/rack — the user picks.
retrieve_zone('to', function() {
retrieve_aisle('to', function() {
retrieve_rack('to');
// Chain-load location selects — skip zone/aisle in simple mode
if (advanced) {
retrieve_zone('to', function() {
retrieve_aisle('to', function() {
retrieve_rack('to');
});
});
});
} else {
retrieve_rack('to');
}
}
return true;
@@ -366,6 +415,7 @@
$(async function() {
try {
await load_location_config(apply_location_labels);
await retrieve_warehouse('to');
await retrieve_for_edit();
@@ -395,15 +445,19 @@
<?php if(empty($_GET["id"])){ ?>
$("select#warehouse").change(function() {
retrieve_zone('to');
if (advanced) {
retrieve_zone('to');
} else {
retrieve_rack('to');
}
});
$("select#zone").change(function() {
retrieve_aisle('to');
if (advanced) retrieve_aisle('to');
});
$("select#aisle").change(function() {
retrieve_rack('to');
if (advanced) retrieve_rack('to');
});
<?php } ?>
+102 -48
View File
@@ -61,24 +61,24 @@
</div>
<!-- Step 5: Zone -->
<div class="mb-3 col-lg-6">
<label for="zone" class="form-label">Zone</label>
<select name="zone" id="zone" class="form-select" required disabled>
<div class="mb-3 col-lg-6" id="group_zone">
<label for="zone" class="form-label" id="label_zone">Zone</label>
<select name="zone" id="zone" class="form-select" disabled>
<option value="">Please select zone</option>
</select>
</div>
<!-- Step 6: Aisle -->
<div class="mb-3 col-lg-6">
<label for="aisle" class="form-label">Aisle</label>
<select name="aisle" id="aisle" class="form-select" required disabled>
<div class="mb-3 col-lg-6" id="group_aisle">
<label for="aisle" class="form-label" id="label_aisle">Aisle</label>
<select name="aisle" id="aisle" class="form-select" disabled>
<option value="">Please select aisle</option>
</select>
</div>
<!-- Step 7: Rack -->
<div class="mb-3 col-lg-6">
<label for="rack" class="form-label">Rack</label>
<div class="mb-3 col-lg-6" id="group_rack">
<label for="rack" class="form-label" id="label_rack">Rack</label>
<select name="rack" id="rack" class="form-select" required disabled>
<option value="">Please select rack</option>
</select>
@@ -111,19 +111,63 @@
<?php require "../include_ending.php";?>
<script>
var advanced = false;
var label_rack = 'Location';
var label_zone = 'Zone';
var label_aisle = 'Aisle';
function load_location_config(callback) {
return ajax_request({
url: server_url + 'setting/api/engine/company_setting.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var s = res.output;
advanced = s.advanced_location == 1;
label_rack = s.location_label_rack || (advanced ? 'Rack' : 'Location');
label_zone = s.location_label_zone || 'Zone';
label_aisle = s.location_label_aisle || 'Aisle';
if (callback) callback();
}
});
}
function apply_location_labels() {
if (advanced) {
$('#group_zone').show();
$('#group_aisle').show();
$('#label_zone').text(label_zone);
$('#label_aisle').text(label_aisle);
$('#label_rack').text(label_rack);
} else {
$('#group_zone').hide();
$('#group_aisle').hide();
$('#label_rack').text(label_rack);
}
}
function manage_stock_out() {
if (!advanced) {
var rack_val = $('#rack').val();
$('#zone').val(rack_val);
$('#aisle').val(rack_val);
}
return ajax_request({
url: "<?php echo $server_url?>ics/api/engine/manage_stock_out.php",
url: '<?php echo $server_url?>ics/api/engine/manage_stock_out.php',
autoPrepare: true,
checkRequired: 1,
debugMode: 0,
data: {
"contact_id": $("#contact").attr('data-id')
'contact_id': $('#contact').attr('data-id'),
'zone': $('#zone').val(),
'aisle': $('#aisle').val(),
},
action: 'manage',
onSuccess: function(res) {
window.location.href = "<?php echo $server_url?>ics/stock_out.php";
onSuccess: function() {
window.location.href = '<?php echo $server_url?>ics/stock_out.php';
}
});
}
@@ -227,7 +271,7 @@
},
action: 'read',
onSuccess: function(res) {
var option = '<option value="">Please select zone</option>';
var option = '<option value="">Please select ' + label_zone.toLowerCase() + '</option>';
$.each(res.output, function(key, item) {
option += `<option value='${item.zone}'>${item.zone}</option>`;
});
@@ -252,7 +296,7 @@
},
action: 'read',
onSuccess: function(res) {
var option = '<option value="">Please select aisle</option>';
var option = '<option value="">Please select ' + label_aisle.toLowerCase() + '</option>';
$.each(res.output, function(key, item) {
option += `<option value='${item}'>${item}</option>`;
});
@@ -277,7 +321,7 @@
},
action: 'read',
onSuccess: function(res) {
var option = '<option value="">Please select rack</option>';
var option = '<option value="">Please select ' + label_rack.toLowerCase() + '</option>';
$.each(res.output, function(key, item) {
option += `<option value='${item}'>${item}</option>`;
});
@@ -291,9 +335,9 @@
// ── Reset location dropdowns ──────────────────────────────────────────
function reset_location() {
$('#warehouse').html('<option value="">Select warehouse...</option>').prop('disabled', true);
$('#zone').html('<option value="">Please select zone</option>').prop('disabled', true);
$('#aisle').html('<option value="">Please select aisle</option>').prop('disabled', true);
$('#rack').html('<option value="">Please select rack</option>').prop('disabled', true);
$('#zone').html('<option value="">Please select ' + label_zone.toLowerCase() + '</option>').prop('disabled', true);
$('#aisle').html('<option value="">Please select ' + label_aisle.toLowerCase() + '</option>').prop('disabled', true);
$('#rack').html('<option value="">Please select ' + label_rack.toLowerCase() + '</option>').prop('disabled', true);
}
@@ -322,34 +366,39 @@
$('#warehouse').val('<?php echo $_GET["wh"];?>').prop('disabled', true);
retrieve_zone(function() {
$('#zone').val(data.zone).prop('disabled', true);
function populate_fields() {
$.each(data, function(key, item) {
if (!['warehouse', 'zone', 'aisle', 'rack', 'lot_number', 'serial_number'].includes(key)) {
$('#' + key).val(item);
}
});
$('#contact').val(data.contact_name)
.attr('secondary', data.contact_name)
.attr('data-id', data.contact_id);
$('#product_name').val(data.product_name);
$('#product_sku').attr('secondary', data.product_sku).prop('disabled', true);
$('button[type=submit]').text('Update');
$('button[type=reset]').hide();
}
retrieve_aisle(function() {
$('#aisle').val(data.aisle).prop('disabled', true);
retrieve_rack(function() {
$('#rack').val(data.rack).prop('disabled', true);
$.each(data, function(key, item) {
if (!['warehouse', 'zone', 'aisle', 'rack', 'lot_number', 'serial_number'].includes(key)) {
$(`#${key}`).val(item);
}
if (advanced) {
retrieve_zone(function() {
$('#zone').val(data.zone).prop('disabled', true);
retrieve_aisle(function() {
$('#aisle').val(data.aisle).prop('disabled', true);
retrieve_rack(function() {
$('#rack').val(data.rack).prop('disabled', true);
populate_fields();
});
$('#contact').val(data.contact_name)
.attr('secondary', data.contact_name)
.attr('data-id', data.contact_id);
$('#product_name').val(data.product_name);
$('#product_sku').attr('secondary', data.product_sku);
$('#product_sku').prop('disabled', true);
$('button[type=submit]').text('Update');
$('button[type=reset]').hide();
});
});
});
} else {
// Simple mode — skip zone/aisle cascade
retrieve_rack(function() {
$('#rack').val(data.rack).prop('disabled', true);
populate_fields();
});
}
}
});
}
@@ -358,6 +407,7 @@
// ── Boot ─────────────────────────────────────────────────────────────
$(async function() {
try {
await load_location_config(apply_location_labels);
await retrieve_warehouse();
await retrieve_for_edit();
} catch(e) {
@@ -388,19 +438,23 @@
retrieve_warehouse();
});
// Warehouse selected → load zone
// Warehouse selected → load zone (advanced) or rack directly (simple)
$('#warehouse').on('change', function() {
retrieve_zone();
if (advanced) {
retrieve_zone();
} else {
retrieve_rack();
}
});
// Zone selected → load aisle
// Zone selected → load aisle (advanced only)
$('#zone').on('change', function() {
retrieve_aisle();
if (advanced) retrieve_aisle();
});
// Aisle selected → load rack
// Aisle selected → load rack (advanced only)
$('#aisle').on('change', function() {
retrieve_rack();
if (advanced) retrieve_rack();
});
<?php } ?>
+184 -133
View File
@@ -65,20 +65,20 @@
<option value="">Select warehouse...</option>
</select>
</div>
<div class="mb-3 col-lg-3">
<label class="form-label">From Zone</label>
<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">
<label class="form-label">From Aisle</label>
<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">From Rack</label>
<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>
@@ -97,20 +97,20 @@
<option value="">Select warehouse...</option>
</select>
</div>
<div class="mb-3 col-lg-3">
<label class="form-label">To Zone</label>
<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">
<label class="form-label">To Aisle</label>
<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">To Rack</label>
<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>
@@ -143,43 +143,97 @@
<?php require "../include_ending.php";?>
<script>
// ── Location config ───────────────────────────────────────────────────────
var advanced = false;
var label_rack = 'Location';
var label_zone = 'Zone';
var label_aisle = 'Aisle';
function load_location_config(callback) {
return ajax_request({
url: '<?php echo $server_url?>setting/api/engine/company_setting.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var s = res.output;
advanced = s.advanced_location == 1;
label_rack = s.location_label_rack || (advanced ? 'Rack' : 'Location');
label_zone = s.location_label_zone || 'Zone';
label_aisle = s.location_label_aisle || 'Aisle';
if (callback) callback();
}
});
}
function apply_location_labels() {
if (advanced) {
$('#group_zone_from, #group_zone_to').show();
$('#group_aisle_from, #group_aisle_to').show();
$('#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();
$('#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() {
// Simple mode — mirror rack value to zone and aisle before submit
if (!advanced) {
var rack_from_val = $('#rack_from').val();
var rack_to_val = $('#rack_to').val();
$('#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",
url: '<?php echo $server_url?>ics/api/engine/manage_stock_transfer.php',
autoPrepare: true,
checkRequired: 1,
debugMode: 0,
data: {
"contact_id": $("#contact").attr('data-id')
},
data: { 'contact_id': $('#contact').attr('data-id') },
action: 'manage',
onSuccess: function(res) {
window.location.href = "<?php echo $server_url?>ics/stock_transfer.php";
onSuccess: function() {
window.location.href = '<?php echo $server_url?>ics/stock_transfer.php';
}
});
}
// ── Step 1: Product selected → load active lots + to-warehouses ───────
// ── 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",
url: '<?php echo $server_url?>ics/api/engine/retrieve_active_lot.php',
autoPrepare: true,
checkRequired: 0,
noLoading: true,
action: 'read',
data: { "product_sku": sku },
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>`;
option += '<option value="' + item.lot_number + '">' + label + '</option>';
});
$('#lot_number').html(option).prop('disabled', false);
$('#serial_number').html('<option value="">Select serial... (optional)</option>').prop('disabled', false);
@@ -189,26 +243,21 @@
}
// ── Step 2: Lot selected → load active serials ────────────────────────
// ── 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",
url: '<?php echo $server_url?>ics/api/engine/retrieve_active_serial.php',
autoPrepare: true,
checkRequired: 0,
noLoading: true,
action: 'read',
data: {
"product_sku": sku,
"lot_number": $('#lot_number').val()
},
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>`;
option += '<option value="' + serial + '">' + serial + '</option>';
});
$('#serial_number').html(option).prop('disabled', false);
reset_from_location();
@@ -217,199 +266,205 @@
}
// ── Warehouse loaders ─────────────────────────────────────────────────
// ── Warehouse loaders ─────────────────────────────────────────────────────
function retrieve_warehouse(type) {
return ajax_request({
url: "<?php echo $server_url?>ics/api/engine/retrieve_warehouse.php",
url: '<?php echo $server_url?>ics/api/engine/retrieve_warehouse.php',
autoPrepare: true,
checkRequired: 0,
noLoading: true,
data: { "type": type, "lot_number": type === 'from' ? $('#lot_number').val() : '' },
data: { 'type': type, 'lot_number': type === 'from' ? $('#lot_number').val() : '' },
action: 'read',
debugMode: 0,
onSuccess: function(res) {
var option = '<option value="">Select warehouse...</option>';
$.each(res.output, function(key, item) {
option += `<option value='${item.id}'>${item.warehouse_name}</option>`;
option += '<option value="' + item.id + '">' + item.warehouse_name + '</option>';
});
$(`select[name='warehouse'][role='${type}']`).html(option).prop('disabled', false);
$('select[name="warehouse"][role="' + type + '"]').html(option).prop('disabled', false);
}
});
}
// ── Zone / Aisle / Rack — From (type=from, filtered by sku+lot+serial) ─
// ── Zone / Aisle / Rack loaders ───────────────────────────────────────────
function retrieve_zone(type, callback) {
var extra = {};
if (type === 'from') {
extra = {
"product_sku": $('#product_sku').attr('secondary') || $('#product_sku').val(),
"lot_number": $('#lot_number').val(),
"serial_number": $('#serial_number').val()
'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",
url: '<?php echo $server_url?>ics/api/engine/retrieve_zone.php',
autoPrepare: true,
checkRequired: 0,
noLoading: true,
data: Object.assign({ "type": type }, extra),
data: Object.assign({ 'type': type }, extra),
action: 'read',
onSuccess: function(res) {
var option = '<option value="">Please select zone</option>';
var option = '<option value="">Please select ' + label_zone.toLowerCase() + '</option>';
$.each(res.output, function(key, item) {
option += `<option value='${item.zone}'>${item.zone}</option>`;
option += '<option value="' + item.zone + '">' + item.zone + '</option>';
});
$(`select[name='zone'][role='${type}']`).html(option).prop('disabled', false);
$('select[name="zone"][role="' + type + '"]').html(option).prop('disabled', false);
if (callback) callback();
}
});
}
function retrieve_aisle(type, callback) {
var extra = {};
if (type === 'from') {
extra = {
"product_sku": $('#product_sku').attr('secondary') || $('#product_sku').val(),
"lot_number": $('#lot_number').val(),
"serial_number": $('#serial_number').val()
'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",
url: '<?php echo $server_url?>ics/api/engine/retrieve_aisle.php',
autoPrepare: true,
checkRequired: 0,
noLoading: true,
data: Object.assign({ "type": type }, extra),
data: Object.assign({ 'type': type }, extra),
action: 'read',
onSuccess: function(res) {
var option = '<option value="">Please select aisle</option>';
var option = '<option value="">Please select ' + label_aisle.toLowerCase() + '</option>';
$.each(res.output, function(key, item) {
option += `<option value='${item}'>${item}</option>`;
option += '<option value="' + item + '">' + item + '</option>';
});
$(`select[name='aisle'][role='${type}']`).html(option).prop('disabled', false);
$('select[name="aisle"][role="' + type + '"]').html(option).prop('disabled', false);
if (callback) callback();
}
});
}
function retrieve_rack(type, callback) {
var extra = {};
if (type === 'from') {
extra = {
"product_sku": $('#product_sku').attr('secondary') || $('#product_sku').val(),
"lot_number": $('#lot_number').val(),
"serial_number": $('#serial_number').val()
'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",
url: '<?php echo $server_url?>ics/api/engine/retrieve_rack.php',
autoPrepare: true,
checkRequired: 0,
noLoading: true,
data: Object.assign({ "type": type }, extra),
data: Object.assign({ 'type': type }, extra),
action: 'read',
onSuccess: function(res) {
var option = '<option value="">Please select rack</option>';
var option = '<option value="">Please select ' + label_rack.toLowerCase() + '</option>';
$.each(res.output, function(key, item) {
option += `<option value='${item}'>${item}</option>`;
option += '<option value="' + item + '">' + item + '</option>';
});
$(`select[name='rack'][role='${type}']`).html(option).prop('disabled', false);
$('select[name="rack"][role="' + type + '"]').html(option).prop('disabled', false);
if (callback) callback();
}
});
}
// ── Reset from-location dropdowns ─────────────────────────────────────
// ── 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 zone</option>').prop('disabled', true);
$('#aisle_from').html('<option value="">Please select aisle</option>').prop('disabled', true);
$('#rack_from').html('<option value="">Please select rack</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 ─────────────────────────────────────────────────────────
// ── Edit mode ─────────────────────────────────────────────────────────────
function retrieve_for_edit() {
<?php if(empty($_GET["id"])){ ?>
<?php if(empty($_GET['id'])){ ?>
return false;
<?php } ?>
return ajax_request({
url: "<?php echo $server_url?>ics/api/engine/retrieve_stock_transfer.php",
url: '<?php echo $server_url?>ics/api/engine/retrieve_stock_transfer.php',
autoPrepare: true,
checkRequired: 0,
debugMode: 0,
action: 'read',
onSuccess: function(res) {
var data = res.output;
var from_wh = data.ref.ref_warehouse;
var to_wh = data.ref_warehouse;
var data = res.output;
var from_wh = data.ref.ref_warehouse;
var to_wh = data.ref_warehouse;
// Lock lot/serial
$('#lot_number').html(`<option value="${data.lot_number || ''}">${data.lot_number || '—'}</option>`)
$('#lot_number').html('<option value="' + (data.lot_number || '') + '">' + (data.lot_number || '—') + '</option>')
.val(data.lot_number || '').prop('disabled', true);
$('#serial_number').html(`<option value="${data.serial_number || ''}">${data.serial_number || '—'}</option>`)
$('#serial_number').html('<option value="' + (data.serial_number || '') + '">' + (data.serial_number || '—') + '</option>')
.val(data.serial_number || '').prop('disabled', true);
// From side
$("select[name='warehouse'][role='from']").val(from_wh).prop('disabled', true);
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);
// To side
$("select[name='warehouse'][role='to']").val(to_wh).prop('disabled', true);
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);
});
});
if (advanced) {
// Full cascade for From
$('select[name="warehouse"][role="from"]').val(from_wh).prop('disabled', true);
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
$('select[name="warehouse"][role="from"]').val(from_wh).prop('disabled', true);
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);
$('#' + key).val(item);
}
});
$('#contact').val(data.contact_name)
.attr('secondary', data.contact_name)
.attr('data-id', data.contact_id);
$('#product_name').val(data.product_name);
$('#product_sku').attr('secondary', data.product_sku).prop('disabled', true);
$('button[type=submit]').text('Update');
$('button[type=reset]').hide();
}
});
}
function populate_to_side(data, to_wh) {
$('select[name="warehouse"][role="to"]').val(to_wh).prop('disabled', true);
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);
});
});
});
}
// ── Boot ─────────────────────────────────────────────────────────────
function populate_to_side_simple(data, to_wh) {
$('select[name="warehouse"][role="to"]').val(to_wh).prop('disabled', true);
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);
await retrieve_warehouse('to');
await retrieve_for_edit();
} catch(e) {
@@ -418,10 +473,9 @@
});
// ── Change handlers (create mode only) ───────────────────────────────
<?php if(empty($_GET["id"])){ ?>
// ── Change handlers (create mode only) ───────────────────────────────────
<?php if(empty($_GET['id'])){ ?>
// SKU selected → load lots + from-warehouse
$('input#product_sku').on('blur', function() {
var sku = $(this).attr('secondary');
if (!sku) return;
@@ -429,50 +483,47 @@
retrieve_warehouse('from');
});
// Lot selected → reload serials + reset from-location
$('#lot_number').on('change', function() {
retrieve_active_serial();
retrieve_warehouse('from');
});
// Serial selected → reload from-warehouse
$('#serial_number').on('change', function() {
retrieve_warehouse('from');
});
// From warehouse → from zone
$("select[name='warehouse'][role='from']").on('change', function() {
retrieve_zone('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 → from aisle
$("select[name='zone'][role='from']").on('change', function() {
retrieve_aisle('from');
// From zone → aisle (advanced only)
$('select[name="zone"][role="from"]').on('change', function() {
if (advanced) retrieve_aisle('from');
});
// From aisle → from rack
$("select[name='aisle'][role='from']").on('change', function() {
retrieve_rack('from');
// From aisle → rack (advanced only)
$('select[name="aisle"][role="from"]').on('change', function() {
if (advanced) retrieve_rack('from');
});
// To warehouse → to zone
$("select[name='warehouse'][role='to']").on('change', function() {
retrieve_zone('to');
// 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 → to aisle
$("select[name='zone'][role='to']").on('change', function() {
retrieve_aisle('to');
// To zone → aisle (advanced only)
$('select[name="zone"][role="to"]').on('change', function() {
if (advanced) retrieve_aisle('to');
});
// To aisle → to rack
$("select[name='aisle'][role='to']").on('change', function() {
retrieve_rack('to');
// To aisle → rack (advanced only)
$('select[name="aisle"][role="to"]').on('change', function() {
if (advanced) retrieve_rack('to');
});
<?php } ?>
</script>
</body>
</html>
+37 -8
View File
@@ -51,9 +51,9 @@
<th>Lot Number</th>
<th>Serial Number</th>
<th>Quantity</th>
<th>Zone</th>
<th>Aisle</th>
<th>Rack</th>
<th class="th_loc_zone">Zone</th>
<th class="th_loc_aisle">Aisle</th>
<th class="th_loc_rack">Rack</th>
<th>Status</th>
<th>Action</th>
</tr>
@@ -78,6 +78,31 @@
<?php require "../include_ending.php";?>
<script>
var advanced = false;
var label_rack = 'Location';
var label_zone = 'Zone';
var label_aisle = 'Aisle';
function apply_location_labels() {
if (advanced) {
$('.th_loc_zone').text(label_zone).show();
$('.th_loc_aisle').text(label_aisle).show();
$('.th_loc_rack').text(label_rack);
} else {
$('.th_loc_zone, .th_loc_aisle').hide();
$('.th_loc_rack').text(label_rack);
}
}
function location_cells(item) {
if (advanced) {
return `<td class="py-3">${item['zone'] || ''}</td>
<td class="py-3">${item['aisle'] || ''}</td>
<td class="py-3">${item['rack'] || ''}</td>`;
}
return `<td class="py-3">${item['rack'] || ''}</td>`;
}
function retrieve_stock_in() {
return ajax_request({
@@ -129,9 +154,7 @@
<td class="py-3">${item['lot_number'] || '<span class="text-muted">—</span>'}</td>
<td class="py-3">${item['serial_number'] || '<span class="text-muted">—</span>'}</td>
<td class="py-3">${item['quantity']}</td>
<td class="py-3">${item['zone']}</td>
<td class="py-3">${item['aisle']}</td>
<td class="py-3">${item['rack']}</td>
${location_cells(item)}
<td class="py-3">
${item['status'] == 1 ? '<span class="badge bg-success">Approved</span>' : '<span class="badge bg-warning text-dark">Draft</span>'}
</td>
@@ -213,7 +236,13 @@
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
auto_approve = res.output.default_stock_status == 1;
var s = res.output || {};
auto_approve = s.default_stock_status == 1;
advanced = s.advanced_location == 1;
label_rack = s.location_label_rack || (advanced ? 'Rack' : 'Location');
label_zone = s.location_label_zone || 'Zone';
label_aisle = s.location_label_aisle || 'Aisle';
apply_location_labels();
}
});
}
@@ -244,4 +273,4 @@
</body>
</html>
</html>
+37 -8
View File
@@ -51,9 +51,9 @@
<th>Lot Number</th>
<th>Serial Number</th>
<th>Quantity</th>
<th>Zone</th>
<th>Aisle</th>
<th>Rack</th>
<th class="th_loc_zone">Zone</th>
<th class="th_loc_aisle">Aisle</th>
<th class="th_loc_rack">Rack</th>
<th>Status</th>
<th>Action</th>
</tr>
@@ -78,6 +78,31 @@
<?php require "../include_ending.php";?>
<script>
var advanced = false;
var label_rack = 'Location';
var label_zone = 'Zone';
var label_aisle = 'Aisle';
function apply_location_labels() {
if (advanced) {
$('.th_loc_zone').text(label_zone).show();
$('.th_loc_aisle').text(label_aisle).show();
$('.th_loc_rack').text(label_rack);
} else {
$('.th_loc_zone, .th_loc_aisle').hide();
$('.th_loc_rack').text(label_rack);
}
}
function location_cells(item) {
if (advanced) {
return `<td class="py-3">${item['zone'] || ''}</td>
<td class="py-3">${item['aisle'] || ''}</td>
<td class="py-3">${item['rack'] || ''}</td>`;
}
return `<td class="py-3">${item['rack'] || ''}</td>`;
}
function retrieve_stock_out() {
return ajax_request({
@@ -129,9 +154,7 @@
<td class="py-3">${item['lot_number'] || '<span class="text-muted">—</span>'}</td>
<td class="py-3">${item['serial_number'] || '<span class="text-muted">—</span>'}</td>
<td class="py-3">${item['quantity']}</td>
<td class="py-3">${item['zone']}</td>
<td class="py-3">${item['aisle']}</td>
<td class="py-3">${item['rack']}</td>
${location_cells(item)}
<td class="py-3">
${item['status'] == 1 ? '<span class="badge bg-success">Approved</span>' : '<span class="badge bg-warning text-dark">Draft</span>'}
</td>
@@ -213,7 +236,13 @@
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
auto_approve = res.output.default_stock_status == 1;
var s = res.output || {};
auto_approve = s.default_stock_status == 1;
advanced = s.advanced_location == 1;
label_rack = s.location_label_rack || (advanced ? 'Rack' : 'Location');
label_zone = s.location_label_zone || 'Zone';
label_aisle = s.location_label_aisle || 'Aisle';
apply_location_labels();
}
});
}
@@ -244,4 +273,4 @@
</body>
</html>
</html>
+37 -8
View File
@@ -49,9 +49,9 @@
<th>Date</th>
<th>Product</th>
<th>Quantity</th>
<th>Zone</th>
<th>Aisle</th>
<th>Rack</th>
<th class="th_loc_zone">Zone</th>
<th class="th_loc_aisle">Aisle</th>
<th class="th_loc_rack">Rack</th>
<th>Status</th>
<th>Action</th>
</tr>
@@ -76,6 +76,31 @@
<?php require "../include_ending.php";?>
<script>
var advanced = false;
var label_rack = 'Location';
var label_zone = 'Zone';
var label_aisle = 'Aisle';
function apply_location_labels() {
if (advanced) {
$('.th_loc_zone').text(label_zone).show();
$('.th_loc_aisle').text(label_aisle).show();
$('.th_loc_rack').text(label_rack);
} else {
$('.th_loc_zone, .th_loc_aisle').hide();
$('.th_loc_rack').text(label_rack);
}
}
function location_cells(item) {
if (advanced) {
return `<td class="py-3">${item['zone'] || ''}</td>
<td class="py-3">${item['aisle'] || ''}</td>
<td class="py-3">${item['rack'] || ''}</td>`;
}
return `<td class="py-3">${item['rack'] || ''}</td>`;
}
function retrieve_stock_transfer() {
return ajax_request({
@@ -125,9 +150,7 @@
<td class="py-3">${format_date(item["date"])}</td>
<td class="py-3">${item["product_sku"]}: ${item['product_name']}</td>
<td class="py-3">${item['quantity']}</td>
<td class="py-3">${item['zone']}</td>
<td class="py-3">${item['aisle']}</td>
<td class="py-3">${item['rack']}</td>
${location_cells(item)}
<td class="py-3">
${item['status'] == 1 ? '<span class="badge bg-success">Approved</span>' : '<span class="badge bg-warning text-dark">Draft</span>'}
</td>
@@ -211,7 +234,13 @@
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
auto_approve = res.output.default_stock_status == 1;
var s = res.output || {};
auto_approve = s.default_stock_status == 1;
advanced = s.advanced_location == 1;
label_rack = s.location_label_rack || (advanced ? 'Rack' : 'Location');
label_zone = s.location_label_zone || 'Zone';
label_aisle = s.location_label_aisle || 'Aisle';
apply_location_labels();
}
});
}
@@ -242,4 +271,4 @@
</body>
</html>
</html>