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
+203 -106
View File
@@ -28,59 +28,84 @@
<div class="col-12">
<div class="card">
<div class="card-body p-6">
<!-- Warehouse — always shown -->
<div class="row">
<div class="col-md-6 mb-3">
<label for="warehouse" class="form-label">Warehouse</label>
<select class="form-select" id="warehouse" required> </select>
</div>
<div class="col-md-6 mb-3">
<label for="zone" class="form-label">Zone</label>
<input type="text" class="form-control" id="zone" placeholder="Enter zone" required>
</div>
</div>
<div class="row">
<div class="col-md-3 mb-3">
<label for="aisle_from" class="form-label">Aisle From</label>
<input type="text" class="form-control" id="aisle_from" placeholder="e.g. 1 or A" required
pattern="^([0-9]+|[A-Za-z])$" title="Enter a number (e.g. 1) or a single letter (e.g. A)">
<div class="form-text">Number (1–99) or letter (A–Z)</div>
</div>
<div class="col-md-3 mb-3">
<label for="aisle_to" class="form-label">Aisle To</label>
<input type="text" class="form-control" id="aisle_to" placeholder="e.g. 5 or E" required
pattern="^([0-9]+|[A-Za-z])$" title="Enter a number (e.g. 5) or a single letter (e.g. E)">
<div class="form-text">Must be same type as Aisle From</div>
</div>
<div class="col-md-3 mb-3">
<label for="rack_from" class="form-label">Rack From</label>
<input type="text" class="form-control" id="rack_from" placeholder="e.g. 1 or A" required
pattern="^([0-9]+|[A-Za-z])$" title="Enter a number (e.g. 1) or a single letter (e.g. A)">
<div class="form-text">Number (1–99) or letter (A–Z)</div>
</div>
<div class="col-md-3 mb-3">
<label for="rack_to" class="form-label">Rack To</label>
<input type="text" class="form-control" id="rack_to" placeholder="e.g. 10 or J" required
pattern="^([0-9]+|[A-Za-z])$" title="Enter a number (e.g. 10) or a single letter (e.g. J)">
<div class="form-text">Must be same type as Rack From</div>
<select class="form-select" id="warehouse" required></select>
</div>
</div>
<!-- Simple mode — from/to location fields -->
<div id="form_simple">
<div class="row">
<div class="col-md-3 mb-3">
<label for="location_from" class="form-label" id="label_location_from">Location From</label>
<input type="text" class="form-control" id="location_from"
placeholder="e.g. Shelf-A or 1">
<div class="form-text">Single value or range start e.g. Shelf-A, Bin-1, 1</div>
</div>
<div class="col-md-3 mb-3">
<label for="location_to" class="form-label" id="label_location_to">Location To</label>
<input type="text" class="form-control" id="location_to"
placeholder="e.g. Shelf-E or 5">
<div class="form-text">Same as From for a single location</div>
</div>
</div>
</div>
<!-- Advanced mode — zone + aisle range + rack range -->
<div id="form_advanced" class="d-none">
<div class="row">
<div class="col-md-6 mb-3">
<label for="zone" class="form-label" id="label_zone">Zone</label>
<input type="text" class="form-control" id="zone" placeholder="e.g. A">
</div>
</div>
<div class="row">
<div class="col-md-3 mb-3">
<label for="aisle_from" class="form-label" id="label_aisle_from">Aisle From</label>
<input type="text" class="form-control" id="aisle_from" placeholder="e.g. 1 or A"
pattern="^([0-9]+|[A-Za-z])$" title="Number or single letter">
<div class="form-text">Number (1–99) or letter (A–Z)</div>
</div>
<div class="col-md-3 mb-3">
<label for="aisle_to" class="form-label" id="label_aisle_to">Aisle To</label>
<input type="text" class="form-control" id="aisle_to" placeholder="e.g. 5 or E"
pattern="^([0-9]+|[A-Za-z])$" title="Number or single letter">
<div class="form-text">Must be same type as Aisle From</div>
</div>
<div class="col-md-3 mb-3">
<label for="rack_from" class="form-label" id="label_rack_from">Rack From</label>
<input type="text" class="form-control" id="rack_from" placeholder="e.g. 1 or A"
pattern="^([0-9]+|[A-Za-z])$" title="Number or single letter">
<div class="form-text">Number (1–99) or letter (A–Z)</div>
</div>
<div class="col-md-3 mb-3">
<label for="rack_to" class="form-label" id="label_rack_to">Rack To</label>
<input type="text" class="form-control" id="rack_to" placeholder="e.g. 10 or J"
pattern="^([0-9]+|[A-Za-z])$" title="Number or single letter">
<div class="form-text">Must be same type as Rack From</div>
</div>
</div>
</div>
<!-- Description — always shown -->
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" rows="4"
placeholder="Enter warehouse description"></textarea>
placeholder="Enter storage description"></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary" onclick="manage_storage();">Add Storage</button>
<button type="reset" class="btn btn-secondary" onclick="reset_input('#storageForm')">Clear</button>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -91,116 +116,188 @@
<?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_company_setting() {
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';
apply_location_labels();
}
});
}
function apply_location_labels() {
if (advanced) {
$('#form_simple').addClass('d-none');
$('#form_advanced').removeClass('d-none');
$('#label_zone').text(label_zone);
$('#label_aisle_from').text(label_aisle + ' From');
$('#label_aisle_to').text(label_aisle + ' To');
$('#label_rack_from').text(label_rack + ' From');
$('#label_rack_to').text(label_rack + ' To');
} else {
$('#form_advanced').addClass('d-none');
$('#form_simple').removeClass('d-none');
$('#label_location_from').text(label_rack + ' From');
$('#label_location_to').text(label_rack + ' To');
$('#location_from').attr('placeholder', 'e.g. Shelf-A or 1');
$('#location_to').attr('placeholder', 'e.g. Shelf-E or 5');
}
}
// ── Save storage ──────────────────────────────────────────────────────────
function manage_storage() {
var zone, aisle_from, aisle_to, rack_from, rack_to;
// Validate that aisle from/to and rack from/to are same type
var af = $('#aisle_from').val().trim().toUpperCase();
var at = $('#aisle_to').val().trim().toUpperCase();
var rf = $('#rack_from').val().trim().toUpperCase();
var rt = $('#rack_to').val().trim().toUpperCase();
if (advanced) {
zone = $('#zone').val().trim();
aisle_from = $('#aisle_from').val().trim().toUpperCase();
aisle_to = $('#aisle_to').val().trim().toUpperCase();
rack_from = $('#rack_from').val().trim().toUpperCase();
rack_to = $('#rack_to').val().trim().toUpperCase();
var isNumeric = function(v) { return /^[0-9]+$/.test(v); };
var isAlpha = function(v) { return /^[A-Z]$/.test(v); };
var isNumeric = function(v) { return /^[0-9]+$/.test(v); };
var isAlpha = function(v) { return /^[A-Z]$/.test(v); };
if (af && at) {
if (isNumeric(af) !== isNumeric(at)) {
alert('Aisle From and Aisle To must both be numbers or both be letters.');
return;
if (aisle_from && aisle_to) {
if (isNumeric(aisle_from) !== isNumeric(aisle_to)) {
alert(label_aisle + ' From and To must both be numbers or both be letters.'); return;
}
if (isAlpha(aisle_from) && aisle_from.charCodeAt(0) > aisle_to.charCodeAt(0)) {
alert(label_aisle + ' From must be less than or equal to To.'); return;
}
if (isNumeric(aisle_from) && parseInt(aisle_from) > parseInt(aisle_to)) {
alert(label_aisle + ' From must be less than or equal to To.'); return;
}
}
if (isAlpha(af) && isAlpha(at) && af.charCodeAt(0) > at.charCodeAt(0)) {
alert('Aisle From must be less than or equal to Aisle To.');
return;
if (rack_from && rack_to) {
if (isNumeric(rack_from) !== isNumeric(rack_to)) {
alert(label_rack + ' From and To must both be numbers or both be letters.'); return;
}
if (isAlpha(rack_from) && rack_from.charCodeAt(0) > rack_to.charCodeAt(0)) {
alert(label_rack + ' From must be less than or equal to To.'); return;
}
if (isNumeric(rack_from) && parseInt(rack_from) > parseInt(rack_to)) {
alert(label_rack + ' From must be less than or equal to To.'); return;
}
}
if (isNumeric(af) && isNumeric(at) && parseInt(af) > parseInt(at)) {
alert('Aisle From must be less than or equal to Aisle To.');
return;
}
}
if (rf && rt) {
if (isNumeric(rf) !== isNumeric(rt)) {
alert('Rack From and Rack To must both be numbers or both be letters.');
return;
} else {
// Simple mode — from/to, same value written to zone/aisle/rack
var loc_from = $('#location_from').val().trim();
var loc_to = $('#location_to').val().trim();
if (!loc_from) {
alert('Please enter a ' + label_rack + ' From value.'); return;
}
if (isAlpha(rf) && isAlpha(rt) && rf.charCodeAt(0) > rt.charCodeAt(0)) {
alert('Rack From must be less than or equal to Rack To.');
return;
}
if (isNumeric(rf) && isNumeric(rt) && parseInt(rf) > parseInt(rt)) {
alert('Rack From must be less than or equal to Rack To.');
return;
// If To is blank, default to same as From (single location)
if (!loc_to) loc_to = loc_from;
// Frontend prefix validation for better UX
var prefix_pattern = /^(.*?)([A-Za-z]|[0-9]+)$/;
var mf = loc_from.match(prefix_pattern);
var mt = loc_to.match(prefix_pattern);
if (mf && mt && mf[1] !== mt[1]) {
alert(label_rack + ' From and To must share the same prefix (e.g. Shelf-A → Shelf-E).'); return;
}
zone = loc_from;
aisle_from = loc_from;
aisle_to = loc_to;
rack_from = loc_from;
rack_to = loc_to;
}
return ajax_request({
url: "<?php echo $server_url?>inventory/api/engine/manage_storage.php",
url: '<?php echo $server_url?>inventory/api/engine/manage_storage.php',
autoPrepare: true,
checkRequired: 0,
debugMode: 0,
action: 'manage',
onSuccess: function(res) {
window.location.href = "<?php echo $server_url?>inventory/warehouse.php";
data: {
warehouse: $('#warehouse').val(),
zone: zone,
aisle_from: aisle_from,
aisle_to: aisle_to,
rack_from: rack_from,
rack_to: rack_to,
description: $('#description').val(),
},
onSuccess: function() {
window.location.href = '<?php echo $server_url?>inventory/warehouse.php';
}
});
}
// ── Retrieve warehouses ───────────────────────────────────────────────────
function retrieve_warehouse() {
return ajax_request({
url: "<?php echo $server_url?>inventory/api/engine/warehouse.php",
url: '<?php echo $server_url?>inventory/api/engine/warehouse.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var option = ``;
var option = '';
$.each(res.output, function(key, item) {
option += `<option value='${item.id}'>${item.warehouse_name}</option>`;
})
$(`select#warehouse`).html(option);
}
});
}
function retrieve_for_edit() {
return ajax_request({
url: "<?php echo $server_url?>inventory/api/engine/retrieve_storage.php",
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
$.each(res.output, function(key, item) {
$(`#${key}`).val(item);
option += '<option value="' + item.id + '">' + item.warehouse_name + '</option>';
});
$(`button[type=submit]`).text('Update Storage');
$(`button[type=reset]`).hide();
$('select#warehouse').html(option);
}
});
}
// ── Load for edit ─────────────────────────────────────────────────────────
function retrieve_for_edit() {
return ajax_request({
url: '<?php echo $server_url?>inventory/api/engine/retrieve_storage.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var d = res.output;
$('#warehouse').val(d.warehouse);
$('#description').val(d.description || '');
if (advanced) {
$('#zone').val(d.zone || '');
$('#aisle_from').val(d.aisle_from || '');
$('#aisle_to').val(d.aisle_to || '');
$('#rack_from').val(d.rack_from || '');
$('#rack_to').val(d.rack_to || '');
} else {
$('#location_from').val(d.rack_from || '');
$('#location_to').val(d.rack_to || '');
}
$('button[type=submit]').text('Update Storage');
$('button[type=reset]').hide();
}
});
}
// ── Boot ──────────────────────────────────────────────────────────────────
$(async function() {
try {
await load_company_setting();
await retrieve_warehouse();
<?php if(!empty($_GET["id"])){ ?>
<?php if (!empty($_GET['id'])) { ?>
await retrieve_for_edit();
<?php } ?>
} catch (e) {
console.log(e);
} catch(e) {
console.error(e);
}
});
</script>