468 lines
15 KiB
PHP
468 lines
15 KiB
PHP
<?php
|
|
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="col-12">
|
|
<div class="mb-6">
|
|
<h1 class="fs-3 mb-0">Stock In</h1>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row g">
|
|
<div class="col-lg-8 col-12">
|
|
<div class="card">
|
|
<div class="card-body p-5">
|
|
<h2 class="fs-5 mb-4">Add Stock</h2>
|
|
<form class="row gx-5">
|
|
<!-- Product Name -->
|
|
<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"
|
|
placeholder="Type product sku..." autocomplete="off" required>
|
|
</div>
|
|
<div class="mb-3 col-lg-3">
|
|
<label for="product_name" class="form-label">Name</label>
|
|
<input type="text" id="product_name" class="form-control" autocomplete="off" disabled>
|
|
</div>
|
|
|
|
<!-- SKU or Product Code -->
|
|
<div class="mb-3 col-lg-6">
|
|
<label for="quantity" class="form-label">Quantity</label>
|
|
<input type="number" class="form-control" id="quantity" placeholder="Enter quantity" required>
|
|
</div>
|
|
|
|
<!-- Lot Number -->
|
|
<div class="mb-3 col-lg-6">
|
|
<label for="lot_number" class="form-label">Lot Number <span class="text-muted fw-normal">(optional)</span></label>
|
|
<input type="text" class="form-control lot_search" id="lot_number" placeholder="Type to search or enter new lot..." autocomplete="off">
|
|
</div>
|
|
|
|
<!-- Expiry Date (only relevant when lot_number is set) -->
|
|
<div class="mb-3 col-lg-6">
|
|
<label for="expiry_date" class="form-label">Expiry Date <span class="text-muted fw-normal">(optional)</span></label>
|
|
<input type="text" class="form-control" id="expiry_date" placeholder="DD/MM/YYYY" autocomplete="off">
|
|
</div>
|
|
|
|
<!-- Serial Number -->
|
|
<div class="mb-3 col-lg-6">
|
|
<label for="serial_number" class="form-label">Serial Number <span class="text-muted fw-normal">(optional)</span></label>
|
|
<input type="text" class="form-control" id="serial_number" placeholder="Enter serial number">
|
|
</div>
|
|
|
|
<!-- Warehouse -->
|
|
<div class="mb-3 col-lg-6">
|
|
<label for="warehouse" class="form-label">Warehouse</label>
|
|
<select name="warehouse" id="warehouse" class="form-select" required></select>
|
|
</div>
|
|
|
|
<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" 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" 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>
|
|
</div>
|
|
|
|
<!-- Contact -->
|
|
<div class="mb-3 col-lg-6">
|
|
<label for="contact" class="form-label">Contact</label>
|
|
<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 -->
|
|
<div class="mb-3 col-lg-12">
|
|
<button type="submit" class="btn btn-primary" onclick="manage_stock_in();">Add Stock</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</main>
|
|
|
|
<?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',
|
|
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(),
|
|
'zone': $('#zone').val(),
|
|
'aisle': $('#aisle').val(),
|
|
},
|
|
action: 'manage',
|
|
onSuccess: function() {
|
|
window.location.href = '<?php echo $server_url?>ics/stock_in.php';
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
},
|
|
action: 'read',
|
|
onSuccess: function(res) {
|
|
var option = `<option value="">Please select warehouse</option>`;
|
|
$.each(res.output, function(key, item) {
|
|
option += `<option value='${item.id}'>${item.warehouse_name}</option>`;
|
|
})
|
|
$(`select#warehouse`).html(option);
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function retrieve_zone(type, callback) {
|
|
|
|
return ajax_request({
|
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_zone.php",
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
noLoading: true,
|
|
data : {
|
|
"type": type
|
|
},
|
|
debugMode: 0,
|
|
action: 'read',
|
|
onSuccess: function(res) {
|
|
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);
|
|
|
|
if (callback) callback(); // ← add this
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function retrieve_aisle(type, callback) {
|
|
|
|
return ajax_request({
|
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_aisle.php",
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
noLoading: true,
|
|
data: {
|
|
"type": type
|
|
},
|
|
action: 'read',
|
|
onSuccess: function(res) {
|
|
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);
|
|
|
|
if (callback) callback(); // ← add this
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function retrieve_rack(type, callback) {
|
|
|
|
return ajax_request({
|
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_rack.php",
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
noLoading: true,
|
|
data: {
|
|
"type": type
|
|
},
|
|
action: 'read',
|
|
onSuccess: function(res) {
|
|
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);
|
|
|
|
if (callback) callback(); // ← add this
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function retrieve_for_edit() {
|
|
|
|
<?php if(empty($_GET["id"])){ ?>
|
|
return false;
|
|
<?php } ?>
|
|
|
|
return ajax_request({
|
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_stock_in.php",
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
debugMode: 0,
|
|
action: 'read',
|
|
onSuccess: function(res) {
|
|
|
|
var data = res.output;
|
|
|
|
$('#warehouse').val('<?php echo $_GET["wh"];?>').attr('disabled', true);
|
|
|
|
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();
|
|
}
|
|
|
|
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();
|
|
});
|
|
});
|
|
});
|
|
} else {
|
|
// Simple mode — skip zone/aisle cascade
|
|
retrieve_rack('to', function() {
|
|
$('#rack').val(data.rack).attr('disabled', true);
|
|
populate_fields();
|
|
});
|
|
}
|
|
|
|
|
|
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
|
|
// ── Prefill for restock flow (coming from low-stock-products.php) ────
|
|
function prefill_from_restock() {
|
|
|
|
var params = new URLSearchParams(window.location.search);
|
|
var sku = params.get('sku');
|
|
var name = params.get('name');
|
|
var qty = params.get('qty');
|
|
var wh = params.get('wh');
|
|
|
|
// Not a restock flow — nothing to prefill
|
|
if (!sku) return false;
|
|
|
|
// Product fields (locked — the point of restock is this specific SKU)
|
|
$('#product_sku')
|
|
.val(sku)
|
|
.attr('secondary', sku)
|
|
.prop('disabled', true);
|
|
|
|
$('#product_name')
|
|
.val(name || '')
|
|
.attr('secondary', name || '');
|
|
// already disabled in markup
|
|
|
|
// Suggested quantity (prefilled value only; user can still edit)
|
|
if (qty) {
|
|
$('#quantity').val(qty);
|
|
}
|
|
|
|
// Warehouse: lock it (can't restock to a different warehouse than the one that's low)
|
|
if (wh) {
|
|
$('#warehouse').val(wh).prop('disabled', true);
|
|
|
|
// 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;
|
|
}
|
|
|
|
|
|
$(async function() {
|
|
try {
|
|
await load_location_config(apply_location_labels);
|
|
await retrieve_warehouse('to');
|
|
await retrieve_for_edit();
|
|
|
|
<?php if(!empty($_GET["sku"])){ ?>
|
|
prefill_from_restock();
|
|
<?php } ?>
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
});
|
|
|
|
// ── Flatpickr: display dd/mm/yyyy, send yyyy-mm-dd to API ──────────
|
|
var expiryPicker = flatpickr("#expiry_date", {
|
|
dateFormat: "Y-m-d", // actual value stored in input (sent to API)
|
|
altInput: true, // show a separate visible input
|
|
altFormat: "d/m/Y", // visible format: 28/04/2026
|
|
allowInput: true,
|
|
onReady: function(_, __, fp) {
|
|
// Apply Bootstrap form-control class to the alt input
|
|
fp.altInput.classList.add("form-control");
|
|
},
|
|
onChange: function(_, dateStr) {
|
|
// If lot is new (not existing), keep expiry_date unlocked
|
|
}
|
|
});
|
|
|
|
|
|
<?php if(empty($_GET["id"])){ ?>
|
|
$("select#warehouse").change(function() {
|
|
if (advanced) {
|
|
retrieve_zone('to');
|
|
} else {
|
|
retrieve_rack('to');
|
|
}
|
|
});
|
|
|
|
$("select#zone").change(function() {
|
|
if (advanced) retrieve_aisle('to');
|
|
});
|
|
|
|
$("select#aisle").change(function() {
|
|
if (advanced) retrieve_rack('to');
|
|
});
|
|
|
|
<?php } ?>
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|