Files
wms-app/app/inventory/manage_storage.php

321 lines
13 KiB
PHP
Raw Permalink 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
require_once __DIR__ . '/../session.php';
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 + bin 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="bin_from" class="form-label" id="label_bin_from">Bin From</label>
<input type="text" class="form-control" id="bin_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="bin_to" class="form-label" id="label_bin_to">Bin To</label>
<input type="text" class="form-control" id="bin_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 Bin 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_bin = '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_bin = s.location_label_bin || (advanced ? 'Bin' : '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_bin_from').text(label_bin + ' From');
$('#label_bin_to').text(label_bin + ' To');
} else {
$('#form_advanced').addClass('d-none');
$('#form_simple').removeClass('d-none');
$('#label_location_from').text(label_bin + ' From');
$('#label_location_to').text(label_bin + ' 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, bin_from, bin_to;
if (advanced) {
zone = $('#zone').val().trim();
aisle_from = $('#aisle_from').val().trim().toUpperCase();
aisle_to = $('#aisle_to').val().trim().toUpperCase();
bin_from = $('#bin_from').val().trim().toUpperCase();
bin_to = $('#bin_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 (!bin_from || !bin_to) {
alert('Please enter the full ' + label_bin + ' 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 (bin_from && bin_to) {
if (isNumeric(bin_from) !== isNumeric(bin_to)) {
alert(label_bin + ' From and To must both be numbers or both be letters.'); return;
}
if (isAlpha(bin_from) && bin_from.charCodeAt(0) > bin_to.charCodeAt(0)) {
alert(label_bin + ' From must be less than or equal to To.'); return;
}
if (isNumeric(bin_from) && parseInt(bin_from) > parseInt(bin_to)) {
alert(label_bin + ' From must be less than or equal to To.'); return;
}
}
} else {
// Simple mode — from/to, same value written to zone/aisle/bin
var loc_from = $('#location_from').val().trim();
var loc_to = $('#location_to').val().trim();
if (!loc_from) {
alert('Please enter a ' + label_bin + ' 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_bin + ' 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;
bin_from = loc_from;
bin_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,
bin_from: bin_from,
bin_to: bin_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 || '');
$('#bin_from').val(d.bin_from || '');
$('#bin_to').val(d.bin_to || '');
} else {
$('#location_from').val(d.bin_from || '');
$('#location_to').val(d.bin_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>