Apply the configured timezone to PHP and both DB connections, wrap unwrapped ajax payloads so delete buttons reach their engines, normalise and validate invoice due dates, reject stock quantities below the stored 4dp scale, and list stock movements across all warehouses.
672 lines
22 KiB
PHP
672 lines
22 KiB
PHP
<?php
|
|
session_start();
|
|
require '../config.php';
|
|
if (!in_array($_SESSION['login_role'] ?? 'viewer', ['owner', 'admin', 'staff'], true)) {
|
|
http_response_code(403);
|
|
exit('Access denied.');
|
|
}
|
|
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>
|
|
<div class="input-group">
|
|
<input type="text" id="product_sku" class="form-control product_search"
|
|
placeholder="Type product sku..." autocomplete="off" required>
|
|
<button class="btn btn-outline-secondary" type="button" onclick="scanner.open_camera()" title="Scan barcode label">
|
|
<i class="ti ti-scan"></i>
|
|
</button>
|
|
</div>
|
|
</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-3">
|
|
<label for="quantity" class="form-label">Quantity</label>
|
|
<input type="number" class="form-control" id="quantity" placeholder="Enter quantity" required>
|
|
</div>
|
|
|
|
<!-- Unit Price / COGS -->
|
|
<div class="mb-3 col-lg-3">
|
|
<label for="price" class="form-label">Unit Price <span class="text-muted fw-normal">(optional)</span></label>
|
|
<input type="number" class="form-control" id="price" placeholder="0.00" step="0.01" min="0">
|
|
</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_bin">
|
|
<label for="bin" class="form-label" id="label_bin">Bin</label>
|
|
<select name="bin" id="bin" class="form-select" required>
|
|
<option value="">Please select bin</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" data-min-role="staff" onclick="manage_stock_in();">Add Stock</button>
|
|
<button type="button" class="btn btn-success ms-2 d-none" id="approve_stock_btn" onclick="approve_current_stock();">Approve</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</main>
|
|
|
|
<?php require "../include_ending.php";?>
|
|
|
|
<script>
|
|
// custom.js globals: set_select_value(), load_location_config(), init_lot_search_inputs()
|
|
|
|
var advanced = false;
|
|
var label_bin = 'Location';
|
|
var label_zone = 'Zone';
|
|
var label_aisle = 'Aisle';
|
|
var scan_sku_ready = false;
|
|
var scan_location_ready = false;
|
|
|
|
|
|
function apply_location_labels() {
|
|
if (advanced) {
|
|
$('#group_zone').show();
|
|
$('#group_aisle').show();
|
|
$('#label_zone').text(label_zone);
|
|
$('#label_aisle').text(label_aisle);
|
|
$('#label_bin').text(label_bin);
|
|
$('#zone').attr('placeholder', 'Please select ' + label_zone.toLowerCase());
|
|
$('#aisle').attr('placeholder', 'Please select ' + label_aisle.toLowerCase());
|
|
$('#bin').attr('placeholder', 'Please select ' + label_bin.toLowerCase());
|
|
} else {
|
|
$('#group_zone').hide();
|
|
$('#group_aisle').hide();
|
|
$('#label_bin').text(label_bin);
|
|
$('#bin').attr('placeholder', 'Please select ' + label_bin.toLowerCase());
|
|
}
|
|
}
|
|
|
|
|
|
function apply_sku_label_scan(data) {
|
|
$('#product_sku').val(data.sku).attr('secondary', data.sku);
|
|
$('#product_name').val(data.product_name || '').attr('secondary', data.product_name || '');
|
|
$('#lot_number').val(data.lot_number || '');
|
|
$('#serial_number').val(data.serial_number || '');
|
|
$('#quantity').val($('#quantity').val() || 1);
|
|
scan_sku_ready = true;
|
|
scan_location_ready = false;
|
|
$('#warehouse').val('');
|
|
$('#zone').val('');
|
|
$('#aisle').val('');
|
|
$('#bin').val('');
|
|
$('#product_sku').trigger('blur');
|
|
}
|
|
|
|
function apply_location_label_scan(data) {
|
|
if (!scan_sku_ready && !($('#product_sku').attr('secondary') || $('#product_sku').val())) {
|
|
bootbox.alert('Scan the SKU label first, then scan the location label.');
|
|
return;
|
|
}
|
|
validate_scanned_location('in', data, function() {
|
|
set_select_value('#warehouse', data.warehouse_id, data.warehouse_id);
|
|
if (advanced) {
|
|
set_select_value('#zone', data.zone, data.zone);
|
|
set_select_value('#aisle', data.aisle, data.aisle);
|
|
}
|
|
set_select_value('#bin', data.bin, data.bin);
|
|
scan_location_ready = true;
|
|
});
|
|
}
|
|
|
|
function validate_scanned_location(mode, data, onSuccess) {
|
|
ajax_request({
|
|
url: '<?php echo $server_url?>ics/api/engine/validate_scan_location.php',
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
action: 'read',
|
|
queueLock: false,
|
|
noLoading: true,
|
|
data: {
|
|
mode: mode,
|
|
warehouse_id: data.warehouse_id,
|
|
zone: data.zone,
|
|
aisle: data.aisle,
|
|
bin: data.bin,
|
|
product_sku: $('#product_sku').attr('secondary') || $('#product_sku').val(),
|
|
lot_number: $('#lot_number').val(),
|
|
serial_number: $('#serial_number').val()
|
|
},
|
|
onSuccess: onSuccess
|
|
});
|
|
}
|
|
|
|
function reset_stock_in_inputs() {
|
|
$('form')[0].reset();
|
|
$('#product_sku').removeAttr('secondary').prop('disabled', false);
|
|
$('#product_name').removeAttr('secondary').val('');
|
|
$('#lot_number, #serial_number, #expiry_date').prop('disabled', false);
|
|
$('#contact').removeAttr('secondary data-id');
|
|
if (expiryPicker) expiryPicker.clear();
|
|
if (!advanced) {
|
|
$('#zone').val('');
|
|
$('#aisle').val('');
|
|
}
|
|
$('#warehouse').val('');
|
|
$('#zone').val('');
|
|
$('#aisle').val('');
|
|
$('#bin').val('');
|
|
scan_sku_ready = false;
|
|
scan_location_ready = false;
|
|
$('#product_sku').focus();
|
|
}
|
|
|
|
function save_scanned_stock_in() {
|
|
if (!scan_sku_ready && !($('#product_sku').attr('secondary') || $('#product_sku').val())) {
|
|
bootbox.alert('Scan the SKU label first.');
|
|
return;
|
|
}
|
|
if (!scan_location_ready && !$('#bin').val()) {
|
|
bootbox.alert('Scan the location label before saving.');
|
|
return;
|
|
}
|
|
manage_stock_in();
|
|
}
|
|
|
|
function manage_stock_in() {
|
|
|
|
var bin_val = $('#bin').val();
|
|
var zone_val = advanced ? $('#zone').val() : bin_val;
|
|
var aisle_val = advanced ? $('#aisle').val() : bin_val;
|
|
var warehouse_val = $('#warehouse').val();
|
|
var sku_val = $('#product_sku').attr('secondary') || $('#product_sku').val();
|
|
var quantity_val = $('#quantity').val();
|
|
|
|
// Quantities are stored as decimal(18,4). Anything finer is rounded away
|
|
// on insert, so 0.0000001 used to become a movement of 0.0000 that still
|
|
// looked like a successful stock-in. Reject it here with a message that
|
|
// names the limit; StockManager enforces the same rule server-side.
|
|
<?php if (empty($_GET["id"])) { ?>
|
|
var quantity_num = parseFloat(quantity_val);
|
|
|
|
if (!(quantity_num > 0)) {
|
|
bootbox.alert('Please enter a quantity greater than zero.');
|
|
return Promise.resolve();
|
|
}
|
|
if (round_dp(quantity_num, 4) < 0.0001) {
|
|
bootbox.alert('Quantity ' + quantity_num + ' is smaller than the minimum the system records (0.0001).');
|
|
return Promise.resolve();
|
|
}
|
|
quantity_val = round_dp(quantity_num, 4);
|
|
|
|
// Expiry dates are stored on the lot, so one entered without a lot number
|
|
// has nowhere to go and would be dropped without warning.
|
|
if ($('#expiry_date').val() && !$('#lot_number').val().trim()) {
|
|
bootbox.alert('Enter a lot number — expiry dates are recorded against a lot.');
|
|
return Promise.resolve();
|
|
}
|
|
<?php } ?>
|
|
|
|
// Mirror to hidden inputs for autoPrepare consistency (simple mode)
|
|
if (!advanced) {
|
|
$('#zone').val(bin_val);
|
|
$('#aisle').val(bin_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,
|
|
'bin': bin_val,
|
|
'product_sku': sku_val,
|
|
'quantity': quantity_val,
|
|
'warehouse': warehouse_val,
|
|
},
|
|
action: 'manage',
|
|
onSuccess: function() {
|
|
<?php if(empty($_GET["id"])){ ?>
|
|
reset_stock_in_inputs();
|
|
<?php } else { ?>
|
|
window.location.href = '<?php echo $server_url?>ics/stock_in.php';
|
|
<?php } ?>
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
function approve_current_stock() {
|
|
bootbox.confirm({
|
|
message: 'Approve this stock-in record?',
|
|
buttons: { confirm: { label: 'Approve', className: 'btn-success' }, cancel: { label: 'Cancel', className: 'btn-secondary' } },
|
|
callback: function(result) {
|
|
if (!result) return;
|
|
ajax_request({
|
|
url: '<?php echo $server_url?>ics/api/engine/approve_stock.php',
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
action: 'update',
|
|
data: {
|
|
id: '<?php echo $_GET["id"] ?? "";?>',
|
|
warehouse: '<?php echo $_GET["wh"] ?? "";?>',
|
|
type: 'in'
|
|
},
|
|
onSuccess: function() {
|
|
window.location.reload();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
|
|
function retrieve_warehouse(type) {
|
|
|
|
return ajax_request({
|
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_warehouse.php",
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
noLoading: true,
|
|
queueLock: false,
|
|
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,
|
|
queueLock: false,
|
|
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,
|
|
queueLock: false,
|
|
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_bin(type, callback) {
|
|
|
|
return ajax_request({
|
|
url: "<?php echo $server_url?>ics/api/engine/retrieve_bin.php",
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
noLoading: true,
|
|
queueLock: false,
|
|
data: {
|
|
"type": type
|
|
},
|
|
action: 'read',
|
|
onSuccess: function(res) {
|
|
var option = '<option value="">Please select ' + label_bin.toLowerCase() + '</option>';
|
|
$.each(res.output, function(key, item) {
|
|
option += '<option value="' + item + '">' + item + '</option>';
|
|
});
|
|
$('select#bin').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', 'bin'].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 {
|
|
// No lot number, so there is nothing for an expiry date to hang
|
|
// off: expiry lives on md_lot, keyed by lot. Leaving the field
|
|
// empty but editable invited entering one that would be silently
|
|
// discarded on update, so say why it is unavailable instead.
|
|
if (expiryPicker) { expiryPicker.clear(); }
|
|
$('#expiry_date').prop('disabled', true)
|
|
.attr('title', 'Expiry dates are recorded against a lot — this movement has no lot number')
|
|
.attr('placeholder', 'Not tracked — no lot number');
|
|
if (expiryPicker && expiryPicker.altInput) {
|
|
expiryPicker.altInput.disabled = true;
|
|
expiryPicker.altInput.placeholder = 'Not tracked — no lot number';
|
|
}
|
|
}
|
|
$('#product_sku').attr('secondary', data.product_sku);
|
|
$('#product_sku, #quantity, #price').prop('disabled', true);
|
|
$('button[type=submit]').text('Update');
|
|
$('button[type=reset]').hide();
|
|
if (data.status == 0) {
|
|
$('#approve_stock_btn').removeClass('d-none');
|
|
}
|
|
}
|
|
|
|
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_bin('to', function() {
|
|
$('#bin').val(data.bin).attr('disabled', true);
|
|
populate_fields();
|
|
});
|
|
});
|
|
});
|
|
} else {
|
|
// Simple mode — skip zone/aisle cascade
|
|
retrieve_bin('to', function() {
|
|
$('#bin').val(data.bin).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_bin('to');
|
|
});
|
|
});
|
|
} else {
|
|
retrieve_bin('to');
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
$(async function() {
|
|
try {
|
|
await load_location_config(apply_location_labels);
|
|
await retrieve_warehouse('to');
|
|
await retrieve_for_edit();
|
|
scanner.init({
|
|
allowRaw: false,
|
|
allowedTypes: ['sku', 'loc'],
|
|
onScan: function(sku, product_name, uom, data) {
|
|
if (data.type === 'sku') apply_sku_label_scan(data);
|
|
if (data.type === 'loc') apply_location_label_scan(data);
|
|
}
|
|
});
|
|
|
|
<?php if(!empty($_GET["sku"])){ ?>
|
|
prefill_from_restock();
|
|
<?php } ?>
|
|
} catch (e) {
|
|
console.log(e);
|
|
}
|
|
});
|
|
|
|
$(document).on('lot:selected', '#lot_number', function(e, lot) {
|
|
if (expiryPicker) {
|
|
expiryPicker.setDate(lot.expiry_date || '', true, 'Y-m-d');
|
|
if (expiryPicker.altInput) expiryPicker.altInput.disabled = true;
|
|
}
|
|
$('#expiry_date').prop('disabled', true)
|
|
.attr('title', 'Expiry date is locked for existing lots');
|
|
});
|
|
|
|
$(document).on('lot:cleared', '#lot_number', function() {
|
|
$('#expiry_date').prop('disabled', false).removeAttr('title');
|
|
if (expiryPicker) {
|
|
expiryPicker.clear();
|
|
if (expiryPicker.altInput) expiryPicker.altInput.disabled = false;
|
|
}
|
|
});
|
|
|
|
// ── 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"])){ ?>
|
|
$(document).on('keydown', function(e) {
|
|
if (e.key === 'F2') {
|
|
e.preventDefault();
|
|
save_scanned_stock_in();
|
|
}
|
|
});
|
|
|
|
$("select#warehouse").change(function() {
|
|
if (advanced) {
|
|
retrieve_zone('to');
|
|
} else {
|
|
retrieve_bin('to');
|
|
}
|
|
});
|
|
|
|
$("select#zone").change(function() {
|
|
if (advanced) retrieve_aisle('to');
|
|
});
|
|
|
|
$("select#aisle").change(function() {
|
|
if (advanced) retrieve_bin('to');
|
|
});
|
|
|
|
<?php } ?>
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|