Fix timestamps, delete requests, invoice dates and GR quantities

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.
This commit is contained in:
Thanakorn
2026-09-17 09:00:15 +07:00
parent f14c850c70
commit f70f226bd1
14 changed files with 464 additions and 60 deletions
+23 -5
View File
@@ -527,9 +527,9 @@
<span class="text-muted ms-2 small">${item.product_name || ''}</span>
</div>
<div class="d-flex gap-3 text-muted small">
<span>Ordered: <strong>${format_number(item.ordered_qty, 0)}</strong></span>
<span>Received: <strong>${format_number(item.received_qty, 0)}</strong></span>
<span>Remaining: <strong class="text-primary">${format_number(item.remaining_qty, 0)}</strong></span>
<span>Ordered: <strong>${format_quantity(item.ordered_qty)}</strong></span>
<span>Received: <strong>${format_quantity(item.received_qty)}</strong></span>
<span>Remaining: <strong class="text-primary">${format_quantity(item.remaining_qty)}</strong></span>
</div>
</div>
@@ -546,7 +546,7 @@
<div class="col-lg-3">
<label class="form-label small text-muted">Receiving Qty <span class="text-danger">*</span></label>
<input type="number" id="recv_qty_${rowId}" class="form-control form-control-sm"
value="${item.remaining_qty}" min="0.0001" max="${item.remaining_qty}" step="any">
value="${item.remaining_qty}" min="0.0001" max="${item.remaining_qty}" step="0.0001">
</div>
<div class="col-lg-3">
@@ -770,6 +770,15 @@
var qty = parseFloat($(`#recv_qty_${rowId}`).val()) || 0;
if (qty <= 0) return;
// Received quantities are stored as decimal(18,4). A value finer than
// that is rounded away on insert, so 0.0000001 was accepted, created a
// stock movement of 0.0000, and still left the PO showing "Partial".
if (round_dp(qty, 4) < 0.0001) {
errors.push(`${$card.data('sku')}: receiving quantity ${qty} is smaller than the minimum the system records (0.0001).`);
return;
}
qty = round_dp(qty, 4);
var wh_id = parseInt($(`#recv_wh_${rowId}`).val()) || 0;
var bin = $(`#recv_bin_${rowId}`).val() || '';
@@ -791,13 +800,22 @@
// flatpickr with altInput: the real (hidden) input holds the ISO value
var expiry_val = $(`#recv_expiry_${rowId}`).val() || '';
var lot_val = $(`#recv_lot_${rowId}`).val().trim();
// Expiry dates are stored on md_lot, keyed by lot number — one entered
// without a lot has nowhere to go and was dropped without warning, so
// the received stock then showed no expiry at all.
if (expiry_val && !lot_val) {
errors.push(`${$card.data('sku')}: enter a lot number — expiry dates are recorded against a lot.`);
return;
}
receive_items.push({
item_id: parseInt($card.data('item-id')),
product_sku: $card.data('sku'),
warehouse_id: wh_id,
quantity: qty,
lot_number: $(`#recv_lot_${rowId}`).val().trim(),
lot_number: lot_val,
expiry_date: expiry_val,
serial_number: $(`#recv_serial_${rowId}`).val().trim(),
zone: zone,
+18 -1
View File
@@ -160,6 +160,8 @@
var invoice_id = <?php echo $invoice_id; ?>;
var invoice_data = null;
var issued_date = ''; // ISO issue date — lower bound for the due date
var due_picker = null; // flatpickr instance on #due_date
function doc_type_badge(doc_type) {
const map = {
@@ -226,6 +228,11 @@
$('#display_contact').text(inv.contact_name || '—');
$('#display_department').text(get_dept_label(inv.department_id));
$('#display_issued').text(format_date(inv.issued_date) || '—');
// A due date before the issue date is not a valid payment term, so
// stop the picker from offering one.
issued_date = inv.issued_date ? String(inv.issued_date).split(' ')[0] : '';
if (due_picker && issued_date) due_picker.set('minDate', issued_date);
$('#due_date').val(inv.due_date ? format_date_input(inv.due_date) : '');
$('#notes').val(inv.notes || '');
@@ -311,6 +318,12 @@
}
function save_invoice() {
var due_val = $('#due_date').val().trim();
if (due_val && issued_date && to_iso_date(due_val) < issued_date) {
bootbox.alert('The due date cannot be earlier than the issue date.');
return;
}
ajax_request({
url: '<?php echo $server_url?>order/api/engine/manage_invoice.php',
autoPrepare: true,
@@ -346,6 +359,10 @@
bootbox.alert('Please enter a due date before issuing this purchase invoice.');
return;
}
if (!is_dn && due_date && issued_date && to_iso_date(due_date) < issued_date) {
bootbox.alert('The due date cannot be earlier than the issue date.');
return;
}
var label = is_dn ? 'Issue Supplier Credit Note' : 'Issue Purchase Invoice';
bootbox.confirm({
message: is_dn ? 'Issue this supplier credit note?' : 'Issue this purchase invoice?',
@@ -419,8 +436,8 @@
$(function() {
load_dept_cache();
due_picker = flatpickr('#due_date', { dateFormat: 'd/m/Y', allowInput: true });
if (invoice_id) retrieve_invoice();
flatpickr('#due_date', { dateFormat: 'd/m/Y', allowInput: true });
});
</script>