Lot, Serial, ExpiryDate

This commit is contained in:
Thanakorn S
2026-04-25 14:37:00 +07:00
parent 92d116fd21
commit 3cc5baba3f
35 changed files with 3518 additions and 1890 deletions
+239 -163
View File
@@ -2,22 +2,19 @@
session_start();
require '../config.php';
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="row">
<div class="col-12">
<div class="mb-6">
<h1 class="fs-3 mb-0">Stock Transfer</h1>
</div>
</div>
</div>
@@ -27,7 +24,8 @@
<div class="card-body p-5">
<h2 class="fs-5 mb-4">Move Stock</h2>
<form class="row gx-5">
<!-- Product Name -->
<!-- Step 1: Product -->
<div class="mb-3 col-lg-3">
<label for="product_sku" class="form-label">SKU</label>
<input type="text" id="product_sku" class="form-control product_search"
@@ -38,67 +36,81 @@
<input type="text" id="product_name" class="form-control" autocomplete="off" disabled>
</div>
<!-- SKU or Product Code -->
<div class="mb-3 col-lg-6">
</div>
<!-- Warehouse -->
<!-- Step 2: Lot Number (active lots for selected SKU) -->
<div class="mb-3 col-lg-3">
<label for="warehouse" class="form-label">From Warehouse</label>
<select name="warehouse" id="warehouse_from" role="from" class="form-select" required>
<option value="">Please select warehouse</option>
<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="zone" class="form-label">From Zone</label>
<select name="zone" id="zone_from" role="from" class="form-select" required>
<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">
<label class="form-label">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 for="aisle" class="form-label">From Aisle</label>
<select name="aisle" id="aisle_from" role="from" class="form-select" required>
<label class="form-label">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 for="rack" class="form-label">From Rack</label>
<select name="rack" id="rack_from" role="from" class="form-select" required>
<label class="form-label">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>
<!-- Warehouse -->
<div class="mb-3 col-lg-3">
<label for="warehouse" class="form-label">To Warehouse</label>
<select name="warehouse" id="warehouse_to" role="to" class="form-select" required>
<option value="">Please select warehouse</option>
</select>
<!-- 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 for="zone" class="form-label">To Zone</label>
<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">
<label class="form-label">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 for="aisle" class="form-label">To Aisle</label>
<label class="form-label">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 for="rack" class="form-label">To Rack</label>
<label class="form-label">To Rack</label>
<select name="rack" id="rack_to" role="to" class="form-select" required>
<option value="">Please select rack</option>
</select>
@@ -110,24 +122,21 @@
<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 Button -->
<!-- Submit -->
<div class="mb-3 col-lg-12">
<button type="submit" class="btn btn-primary" onclick="manage_stock_transfer();">Add Stock</button>
<button type="submit" class="btn btn-primary" onclick="manage_stock_transfer();">Transfer Stock</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</main>
@@ -146,135 +155,189 @@
},
action: 'manage',
onSuccess: function(res) {
window.location.href = "<?php echo $server_url?>ics/stock_transfer.php";
}
});
}
// ── Step 1: Product selected → load active lots + to-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,
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);
$('#serial_number').html('<option value="">Select serial... (optional)</option>').prop('disabled', false);
reset_from_location();
}
});
}
// ── 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,
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);
reset_from_location();
}
});
}
// ── Warehouse loaders ─────────────────────────────────────────────────
function retrieve_warehouse(type) {
return ajax_request({
url: "<?php echo $server_url?>ics/api/engine/retrieve_warehouse.php",
autoPrepare: true,
checkRequired: 0,
data: {
"type": type
},
noLoading: true,
data: { "type": type },
action: 'read',
debugMode: 0,
onSuccess: function(res) {
var option = `<option value="">Please select warehouse</option>`;
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);
if(!type) {
$(`select[name='warehouse']`).html(option);
}
});
$(`select[name='warehouse'][role='${type}']`).html(option).prop('disabled', false);
}
});
}
// ── Zone / Aisle / Rack — From (type=from, filtered by sku+lot+serial) ─
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()
};
}
return ajax_request({
url: "<?php echo $server_url?>ics/api/engine/retrieve_zone.php",
autoPrepare: true,
checkRequired: 0,
data: {
"type": type
},
noLoading: true,
debugMode: 0,
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 zone</option>';
$.each(res.output, function(key, item) {
option += `<option value='${item.zone}'>${item.zone}</option>`;
})
$(`select[name='zone'][role='${type}']`).html(option);
if(!type) {
$(`select[name='zone']`).html(option);
}
if (callback) callback(); // ← add this
});
$(`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()
};
}
return ajax_request({
url: "<?php echo $server_url?>ics/api/engine/retrieve_aisle.php",
autoPrepare: true,
checkRequired: 0,
data: {
"type": type
},
noLoading: true,
data: Object.assign({ "type": type }, extra),
action: 'read',
debugMode: 0,
onSuccess: function(res) {
var option = `<option value="">Please select aisle</option>`;
var option = '<option value="">Please select aisle</option>';
$.each(res.output, function(key, item) {
option += `<option value='${item}'>${item}</option>`;
})
$(`select[name='aisle'][role='${type}']`).html(option);
if(!type) {
$(`select[name='aisle']`).html(option);
}
if (callback) callback(); // ← add this
});
$(`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()
};
}
return ajax_request({
url: "<?php echo $server_url?>ics/api/engine/retrieve_rack.php",
autoPrepare: true,
checkRequired: 0,
data: {
"type": type
},
noLoading: true,
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 rack</option>';
$.each(res.output, function(key, item) {
option += `<option value='${item}'>${item}</option>`;
})
$(`select[name='rack'][role='${type}']`).html(option);
if(!type) {
$(`select[name='rack']`).html(option);
}
if (callback) callback(); // ← add this
});
$(`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 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);
}
// ── Edit mode ─────────────────────────────────────────────────────────
function retrieve_for_edit() {
<?php if(empty($_GET["id"])){ ?>
@@ -289,54 +352,43 @@
action: 'read',
onSuccess: function(res) {
var type = 'from';
var warehouse = res.output.ref.ref_warehouse;
var data = res.output;
var data = res.output;
var from_wh = data.ref.ref_warehouse;
var to_wh = data.ref_warehouse;
/**
* From warehouse
*/
$(`select[name='warehouse'][role=${type}]`).val(warehouse).attr('disabled', true);
// Lock lot/serial
$('#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>`)
.val(data.serial_number || '').prop('disabled', true);
retrieve_zone(type, function() {
$(`select[name='zone'][role=${type}]`).val(data.zone).attr('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);
retrieve_aisle(type, function() {
$(`select[name='aisle'][role=${type}]`).val(data.aisle).attr('disabled', true);
retrieve_rack(type, function() {
$(`select[name='rack'][role=${type}]`).val(data.rack).attr('disabled', true);
type = 'to';
warehouse = res.output.ref_warehouse;
data = res.output.ref;
/**
* To warehouse
*/
$(`select[name='warehouse'][role=${type}]`).val(warehouse).attr('disabled', true);
retrieve_zone(type, function() {
$(`select[name='zone'][role=${type}]`).val(data.zone).attr('disabled', true);
retrieve_aisle(type, function() {
$(`select[name='aisle'][role=${type}]`).val(data.aisle).attr('disabled',true);
retrieve_rack(type, function() {
$(`select[name='rack'][role=${type}]`).val(data.rack).attr('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);
});
});
});
});
});
});
var data = res.output;
// retrieve for edit
// Common fields
$.each(data, function(key, item) {
if (!['warehouse', 'zone', 'aisle', 'rack'].includes(key)) {
if (!['warehouse', 'zone', 'aisle', 'rack', 'lot_number', 'serial_number', 'ref'].includes(key)) {
$(`#${key}`).val(item);
}
});
@@ -346,52 +398,76 @@
.attr('data-id', data.contact_id);
$('#product_name').val(data.product_name);
$('#product_sku').attr('secondary', data.product_sku);
$('#product_sku,#quantity').prop('disabled', true);
$(`button[type=submit]`).text('Update');
$(`button[type=reset]`).hide();
$('#product_sku').attr('secondary', data.product_sku).prop('disabled', true);
$('button[type=submit]').text('Update');
$('button[type=reset]').hide();
}
});
}
// ── Boot ─────────────────────────────────────────────────────────────
$(async function() {
try {
await retrieve_warehouse('from');
await retrieve_warehouse('to');
await retrieve_for_edit();
} catch (e) {
} catch(e) {
console.log(e);
}
});
$(`#product_sku`).on('blur', function() {
$(async function() {
try {
await retrieve_warehouse('from');
await retrieve_warehouse('to');
} catch (e) {
console.log(e);
}
})
});
// ── Change handlers (create mode only) ───────────────────────────────
<?php if(empty($_GET["id"])){ ?>
$("select[name='warehouse']").change(function() {
retrieve_zone($(this).attr('role'));
// SKU selected → load lots + from-warehouse
$('input#product_sku').on('blur', function() {
var sku = $(this).attr('secondary');
if (!sku) return;
retrieve_active_lot();
retrieve_warehouse('from');
});
$("select[name='zone']").change(function() {
retrieve_aisle($(this).attr('role'));
// Lot selected → reload serials + reset from-location
$('#lot_number').on('change', function() {
retrieve_active_serial();
retrieve_warehouse('from');
});
$("select[name='aisle']").change(function() {
retrieve_rack($(this).attr('role'));
// 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 zone → from aisle
$("select[name='zone'][role='from']").on('change', function() {
retrieve_aisle('from');
});
// From aisle → from rack
$("select[name='aisle'][role='from']").on('change', function() {
retrieve_rack('from');
});
// To warehouse → to zone
$("select[name='warehouse'][role='to']").on('change', function() {
retrieve_zone('to');
});
// To zone → to aisle
$("select[name='zone'][role='to']").on('change', function() {
retrieve_aisle('to');
});
// To aisle → to rack
$("select[name='aisle'][role='to']").on('change', function() {
retrieve_rack('to');
});
<?php } ?>