Files
wms-app/app/inventory/manage_storage.php
T
2026-05-06 16:37:20 +07:00

321 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
session_start();
require '../config.php';
if (!in_array($_SESSION['login_role'] ?? 'viewer', ['owner', 'admin'], true)) {
http_response_code(403);
exit('Access denied.');
}
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php';?>
<?php require '../include_master_sidebar.php';?>
<!-- MAIN CONTENT -->
<main id="content" class="content py-15">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 gap-3">
<div class="">
<h1 class="fs-3 mb-1">Manage Storage</h1>
<p class="mb-0">Manage your Storage items</p>
</div>
</div>
</div>
</div>
<div class="row" id="storageForm">
<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>
<!-- 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 storage description"></textarea>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary" data-min-role="admin" 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>
</div>
</main>
<?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;
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); };
if (!zone) {
alert('Please enter a ' + label_zone + ' value.'); return;
}
if (!aisle_from || !aisle_to) {
alert('Please enter the full ' + label_aisle + ' range.'); return;
}
if (!rack_from || !rack_to) {
alert('Please enter the full ' + label_rack + ' range.'); 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 (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;
}
}
} 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 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',
autoPrepare: true,
checkRequired: 0,
action: 'manage',
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',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var option = '';
$.each(res.output, function(key, item) {
option += '<option value="' + item.id + '">' + item.warehouse_name + '</option>';
});
$('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'])) { ?>
await retrieve_for_edit();
<?php } ?>
} catch(e) {
console.error(e);
}
});
</script>
</body>
</html>