accounting workflows
This commit is contained in:
+181
-218
@@ -71,6 +71,13 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 col-lg-6">
|
||||
<label class="form-label">Department</label>
|
||||
<select id="department_id" class="form-select">
|
||||
<option value="0">— No Department —</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 col-lg-12">
|
||||
<label class="form-label">Notes</label>
|
||||
<textarea id="notes" class="form-control" rows="2"
|
||||
@@ -97,8 +104,9 @@
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th style="width:120px;">Qty</th>
|
||||
<th style="width:130px;">Unit Price</th>
|
||||
<th style="width:90px;">Qty</th>
|
||||
<th style="width:120px;">Unit Price</th>
|
||||
<th style="width:80px;">Tax%</th>
|
||||
<th style="width:130px;">Total</th>
|
||||
<th style="width:40px;"></th>
|
||||
</tr>
|
||||
@@ -137,7 +145,6 @@
|
||||
<h2 class="fs-5 mb-3">Status</h2>
|
||||
<div class="d-flex gap-2 mb-2">
|
||||
<span id="badge_status"></span>
|
||||
<span id="badge_payment"></span>
|
||||
</div>
|
||||
<p class="mb-0 small text-muted" id="po_number_display"></p>
|
||||
<p class="mb-0 small text-muted d-none" id="expected_date_display"></p>
|
||||
@@ -159,8 +166,12 @@
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">Tax</span>
|
||||
<input type="number" id="tax" class="form-control form-control-sm text-end"
|
||||
style="width:110px;" value="0" min="0" oninput="recalc_totals()">
|
||||
<span id="summary_tax" class="fw-semibold">0.00</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="text-muted small">Tax Adj.</span>
|
||||
<input type="number" id="tax_adjustment" class="form-control form-control-sm text-end"
|
||||
style="width:90px;" value="0" min="-0.3" max="0.3" step="0.01" oninput="recalc_totals()">
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<span class="text-muted">Shipping</span>
|
||||
@@ -189,20 +200,27 @@
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="payment_status_group" class="d-none">
|
||||
<label class="form-label small text-muted mb-1">Payment Status</label>
|
||||
<div class="d-flex gap-2">
|
||||
<select id="payment_status_select" class="form-select form-select-sm">
|
||||
<option value="0">Unpaid</option>
|
||||
<option value="1">Paid</option>
|
||||
<option value="2">Partial</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline-primary text-nowrap"
|
||||
onclick="update_payment_status()">
|
||||
<i class="ti ti-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-1">
|
||||
|
||||
<button class="btn btn-outline-primary w-100 d-none" id="btn_proceed_pi"
|
||||
onclick="proceed_to_purchase_invoice()">
|
||||
<i class="ti ti-file-invoice me-1"></i>Proceed to Purchase Invoice
|
||||
</button>
|
||||
|
||||
<a href="#" class="btn btn-outline-info w-100 d-none" id="btn_view_pi">
|
||||
<i class="ti ti-eye me-1"></i>View Purchase Invoice
|
||||
</a>
|
||||
|
||||
<a href="<?php echo $server_url?>po/manage_supplier_return.php?po_id=<?php echo $po_id; ?>"
|
||||
class="btn btn-outline-secondary w-100 d-none" id="btn_new_return">
|
||||
<i class="ti ti-arrow-back-up me-1"></i>New Supplier Return
|
||||
</a>
|
||||
|
||||
<hr class="my-1">
|
||||
|
||||
<button class="btn btn-outline-warning w-100 d-none" id="btn_close_po" onclick="close_po()">
|
||||
<i class="ti ti-lock me-1"></i>Close PO
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-danger w-100" id="btn_cancel" onclick="cancel_po()">
|
||||
<i class="ti ti-x me-1"></i>Cancel PO
|
||||
@@ -220,6 +238,7 @@
|
||||
<?php require '../include_ending.php'; ?>
|
||||
|
||||
<script>
|
||||
// custom.js globals: remove_item(), load_location_config(), init_product_search_inputs(), init_lot_search_inputs(), escape_html(), format_number()
|
||||
|
||||
var po_id = <?php echo $po_id; ?>;
|
||||
var po_data = null;
|
||||
@@ -238,40 +257,44 @@
|
||||
// ── Status helpers ────────────────────────────────────────────────────────
|
||||
function po_status_badge(status) {
|
||||
const map = {
|
||||
'-2': '<span class="badge bg-danger">Pending Warehouse</span>',
|
||||
'-1': '<span class="badge bg-secondary">Cancelled</span>',
|
||||
'0': '<span class="badge bg-warning text-dark">Draft</span>',
|
||||
'1': '<span class="badge bg-info text-white">Confirmed</span>',
|
||||
'2': '<span class="badge bg-primary">Partial</span>',
|
||||
'3': '<span class="badge bg-success">Completed</span>',
|
||||
};
|
||||
return map[String(status)] ?? '—';
|
||||
}
|
||||
|
||||
function po_payment_badge(status) {
|
||||
function receipt_status_badge(status) {
|
||||
const map = {
|
||||
'0': '<span class="badge bg-warning text-dark">Unpaid</span>',
|
||||
'1': '<span class="badge bg-success">Paid</span>',
|
||||
'2': '<span class="badge bg-info text-white">Partial</span>',
|
||||
'2': '<span class="badge bg-primary">Partial</span>',
|
||||
'3': '<span class="badge bg-success">Received</span>',
|
||||
};
|
||||
return map[String(status)] ?? '—';
|
||||
return map[String(status)] ?? '';
|
||||
}
|
||||
|
||||
|
||||
// ── Recalculate totals ────────────────────────────────────────────────────
|
||||
function recalc_totals() {
|
||||
var subtotal = 0;
|
||||
var tax_sum = 0;
|
||||
$('#item_tbody tr').each(function() {
|
||||
var qty = parseFloat($(this).find('.item_qty').val()) || 0;
|
||||
var price = parseFloat($(this).find('.item_price').val()) || 0;
|
||||
var total = qty * price;
|
||||
var qty = parseFloat($(this).find('.item_qty').val()) || 0;
|
||||
var price = parseFloat($(this).find('.item_price').val()) || 0;
|
||||
var tax_rate = parseFloat($(this).find('.item_tax_rate').val()) || 0;
|
||||
var total = qty * price;
|
||||
var tax_amt = round_dp(total * tax_rate / 100, 4);
|
||||
$(this).find('.item_total').text(format_number(total, 2));
|
||||
$(this).find('.item_tax_amount').val(tax_amt);
|
||||
subtotal += total;
|
||||
tax_sum += tax_amt;
|
||||
});
|
||||
var discount = parseFloat($('#discount').val()) || 0;
|
||||
var tax = parseFloat($('#tax').val()) || 0;
|
||||
var shipping_fee = parseFloat($('#shipping_fee').val()) || 0;
|
||||
var discount = parseFloat($('#discount').val()) || 0;
|
||||
var tax_adjustment = parseFloat($('#tax_adjustment').val()) || 0;
|
||||
var shipping_fee = parseFloat($('#shipping_fee').val()) || 0;
|
||||
var derived_tax = round_dp(tax_sum, 2);
|
||||
$('#summary_subtotal').text(format_number(subtotal, 2));
|
||||
$('#summary_grand_total').text(format_number(subtotal - discount + tax + shipping_fee, 2));
|
||||
$('#summary_tax').text(format_number(derived_tax, 2));
|
||||
$('#summary_grand_total').text(format_number(subtotal - discount + derived_tax + tax_adjustment + shipping_fee, 2));
|
||||
}
|
||||
|
||||
|
||||
@@ -282,7 +305,7 @@
|
||||
var row = `<tr id="item_row_${idx}">
|
||||
<td>
|
||||
<input type="text" class="form-control form-control-sm item_sku product_search"
|
||||
placeholder="SKU..." value="${item.product_sku || ''}" autocomplete="off" data-idx="${idx}">
|
||||
placeholder="SKU..." value="${item.product_sku || ''}" autocomplete="off" data-idx="${idx}" data-product-name-target=".item_name">
|
||||
<input type="hidden" class="item_name" value="${item.product_name || ''}">
|
||||
<input type="hidden" class="item_id" value="${item.item_id ?? idx}">
|
||||
<small class="text-muted item_name_display">${item.product_name || ''}</small>
|
||||
@@ -295,6 +318,11 @@
|
||||
<input type="number" class="form-control form-control-sm item_price"
|
||||
value="${item.unit_price || 0}" min="0" step="any" oninput="recalc_totals()">
|
||||
</td>
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm item_tax_rate"
|
||||
value="${parseFloat(item.tax_rate || 0).toFixed(2)}" min="0" max="100" step="0.01" oninput="recalc_totals()">
|
||||
<input type="hidden" class="item_tax_amount" value="${item.tax_amount || 0}">
|
||||
</td>
|
||||
<td class="item_total text-end">${format_number((item.quantity || 1) * (item.unit_price || 0), 2)}</td>
|
||||
<td>
|
||||
<a href="javascript:void(0);" class="link-danger remove_item_btn" onclick="remove_item(${idx})">
|
||||
@@ -303,13 +331,12 @@
|
||||
</td>
|
||||
</tr>`;
|
||||
$('#item_tbody').append(row);
|
||||
if (window.init_product_search_inputs) {
|
||||
window.init_product_search_inputs(`#item_row_${idx}`);
|
||||
}
|
||||
recalc_totals();
|
||||
}
|
||||
|
||||
function remove_item(idx) {
|
||||
$(`#item_row_${idx}`).remove();
|
||||
recalc_totals();
|
||||
}
|
||||
|
||||
|
||||
// ── Collect items from DOM ────────────────────────────────────────────────
|
||||
@@ -317,10 +344,12 @@
|
||||
var items = [];
|
||||
var idx = 0;
|
||||
$('#item_tbody tr').each(function() {
|
||||
var sku = $(this).find('.item_sku').val().trim();
|
||||
var name = $(this).find('.item_name').val().trim();
|
||||
var qty = parseFloat($(this).find('.item_qty').val()) || 0;
|
||||
var price = parseFloat($(this).find('.item_price').val()) || 0;
|
||||
var sku = $(this).find('.item_sku').val().trim();
|
||||
var name = $(this).find('.item_name').val().trim();
|
||||
var qty = parseFloat($(this).find('.item_qty').val()) || 0;
|
||||
var price = parseFloat($(this).find('.item_price').val()) || 0;
|
||||
var tax_rate = parseFloat($(this).find('.item_tax_rate').val()) || 0;
|
||||
var tax_amt = parseFloat($(this).find('.item_tax_amount').val()) || 0;
|
||||
if (!sku || qty <= 0) { idx++; return; }
|
||||
items.push({
|
||||
item_id: idx,
|
||||
@@ -328,7 +357,9 @@
|
||||
product_name: name,
|
||||
quantity: qty,
|
||||
unit_price: price,
|
||||
total_price: qty * price,
|
||||
total_price: round_dp(qty * price, 4),
|
||||
tax_rate: tax_rate,
|
||||
tax_amount: tax_amt,
|
||||
received_qty: 0,
|
||||
stock_in_id: 0,
|
||||
});
|
||||
@@ -338,21 +369,6 @@
|
||||
}
|
||||
|
||||
|
||||
// ── Location config loader ────────────────────────────────────────────────
|
||||
function load_location_config() {
|
||||
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_rack = s.location_label_rack || (advanced ? 'Rack' : 'Location');
|
||||
label_zone = s.location_label_zone || 'Zone';
|
||||
label_aisle = s.location_label_aisle || 'Aisle';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ── Cascade helpers (scoped per receive row by rowId) ─────────────────────
|
||||
@@ -439,83 +455,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
function init_receive_lot_search(rowId) {
|
||||
var $lot = $(`#recv_lot_${rowId}`);
|
||||
$(document).on('lot:selected', '.lot_search', function(e, lot) {
|
||||
var rowId = $(this).closest('[data-row]').data('row');
|
||||
if (rowId) lock_recv_expiry(rowId, lot.expiry_date || '');
|
||||
});
|
||||
|
||||
$lot.autocomplete({
|
||||
source: function(request, response) {
|
||||
if ($lot.data('xhr')) {
|
||||
$lot.data('xhr').abort();
|
||||
}
|
||||
|
||||
var $card = $lot.closest('[data-row]');
|
||||
var q = prepare_form_data(0, true);
|
||||
q.keyword = request.term;
|
||||
q.product_sku = $card.data('sku') || '';
|
||||
|
||||
var xhr = $.ajax({
|
||||
url: server_url + 'ics/api/engine/retrieve_lot.php',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: { json: JSON.stringify(q) },
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
|
||||
},
|
||||
success: function(res) {
|
||||
response((res.result || []).map(function(item) {
|
||||
var label = `
|
||||
<div class="lot-result-item" style="font-family:'Poppins',sans-serif;padding:4px 0;">
|
||||
<div style="font-size:10px;text-transform:uppercase;color:#E66239;font-weight:600;line-height:1.2;">
|
||||
Lot: ${item.lot_number}
|
||||
</div>
|
||||
<div style="font-size:13px;font-weight:500;color:#171717;margin-top:2px;">
|
||||
Expiry: ${item.expiry_date || 'N/A'}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
return {
|
||||
label: label,
|
||||
value: item.lot_number,
|
||||
data: item
|
||||
};
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
$lot.data('xhr', xhr);
|
||||
},
|
||||
minLength: 0,
|
||||
delay: 300,
|
||||
select: function(e, ui) {
|
||||
if (!ui.item || !ui.item.data) return;
|
||||
|
||||
var lot = ui.item.data;
|
||||
$lot
|
||||
.val(lot.lot_number)
|
||||
.attr('secondary', lot.lot_number)
|
||||
.attr('data-existing', '1');
|
||||
|
||||
lock_recv_expiry(rowId, lot.expiry_date || '');
|
||||
return false;
|
||||
}
|
||||
}).data('ui-autocomplete')._renderItem = function(ul, item) {
|
||||
return $('<li>').append($('<div>').html(item.label)).appendTo(ul);
|
||||
};
|
||||
|
||||
$lot.on('blur', function() {
|
||||
var secondary = $lot.attr('secondary');
|
||||
if (secondary) $lot.val(secondary);
|
||||
});
|
||||
|
||||
$lot.on('focus', function() {
|
||||
$lot.autocomplete('search', $lot.val());
|
||||
});
|
||||
|
||||
$lot.on('input', function() {
|
||||
$lot.removeAttr('data-existing secondary');
|
||||
unlock_recv_expiry(rowId);
|
||||
});
|
||||
}
|
||||
$(document).on('lot:cleared', '.lot_search', function() {
|
||||
var rowId = $(this).closest('[data-row]').data('row');
|
||||
if (rowId) unlock_recv_expiry(rowId);
|
||||
});
|
||||
|
||||
// Called when warehouse select changes on a receive row
|
||||
function on_recv_wh_change(rowId) {
|
||||
@@ -597,7 +545,7 @@
|
||||
|
||||
<div class="col-lg-3">
|
||||
<label class="form-label small text-muted">Lot Number</label>
|
||||
<input type="text" id="recv_lot_${rowId}" class="form-control form-control-sm recv_lot_search"
|
||||
<input type="text" id="recv_lot_${rowId}" class="form-control form-control-sm lot_search"
|
||||
placeholder="Type to search or enter new lot..." autocomplete="off">
|
||||
</div>
|
||||
|
||||
@@ -640,7 +588,9 @@
|
||||
fp.altInput.classList.add('form-control', 'form-control-sm');
|
||||
}
|
||||
});
|
||||
init_receive_lot_search(rowId);
|
||||
if (window.init_lot_search_inputs) {
|
||||
window.init_lot_search_inputs(`#recv_lot_${rowId}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Pre-select default warehouse and trigger cascade for each row
|
||||
@@ -655,27 +605,50 @@
|
||||
|
||||
|
||||
// ── Set view/edit mode ────────────────────────────────────────────────────
|
||||
function set_mode(status, receivable_count) {
|
||||
function set_mode(status, receivable_count, po_res, closed_at) {
|
||||
status = parseInt(status);
|
||||
receivable_count = receivable_count ?? 0;
|
||||
po_res = po_res || {};
|
||||
var is_closed = !!closed_at;
|
||||
var is_draft = status === 0;
|
||||
var is_pending = status === -2;
|
||||
var is_editable = is_pending || is_draft;
|
||||
var can_confirm = status === 0;
|
||||
var can_cancel = status >= 0 && status <= 1;
|
||||
var can_receive = (status === 1 || status === 2) && receivable_count > 0;
|
||||
var can_cancel = status >= -2 && status <= 1;
|
||||
var can_receive = status === 1 && receivable_count > 0 && !is_closed;
|
||||
var can_close = status === 1 && !is_closed;
|
||||
var is_cancelled = status === -1;
|
||||
var has_pi = !!po_res.has_active_pi;
|
||||
var pi_id = po_res.active_pi_id || 0;
|
||||
var has_received = (po_res.returnable_items || []).length > 0;
|
||||
|
||||
$('#btn_add_item, #contact, #po_date, #expected_date, #warehouse_id, #notes, #discount, #tax, #shipping_fee')
|
||||
.prop('disabled', !is_draft);
|
||||
$('.item_sku, .item_qty, .item_price').prop('disabled', !is_draft);
|
||||
$('.remove_item_btn').toggleClass('d-none', !is_draft);
|
||||
$('#btn_add_item, #contact, #po_date, #expected_date, #warehouse_id, #department_id, #notes, #discount, #tax_adjustment, #shipping_fee')
|
||||
.prop('disabled', !is_editable);
|
||||
$('.item_sku, .item_qty, .item_price, .item_tax_rate').prop('disabled', !is_editable);
|
||||
$('.remove_item_btn').toggleClass('d-none', !is_editable);
|
||||
|
||||
set_btn_state('#btn_save', is_draft, 'Only draft POs can be edited');
|
||||
set_btn_state('#btn_confirm', can_confirm, 'PO must be in draft to confirm');
|
||||
set_btn_state('#btn_save', is_editable, 'Only pending or draft POs can be edited');
|
||||
$('#btn_save').html(is_pending
|
||||
? '<i class="ti ti-device-floppy me-1"></i>Save Warehouse'
|
||||
: '<i class="ti ti-device-floppy me-1"></i>Save Draft');
|
||||
set_btn_state('#btn_confirm', can_confirm,
|
||||
is_pending ? 'Assign a default warehouse and save first. The PO will move to Draft.' : 'PO must be in draft to confirm');
|
||||
set_btn_state('#btn_cancel', can_cancel && !is_cancelled,
|
||||
is_cancelled ? 'PO is already cancelled' : 'Only draft or confirmed POs can be cancelled');
|
||||
|
||||
$('#payment_status_group').toggleClass('d-none', status < 1 || is_cancelled);
|
||||
$('#receive_card').toggleClass('d-none', !can_receive);
|
||||
$('#btn_close_po').toggleClass('d-none', !can_close);
|
||||
|
||||
// Purchase Invoice button
|
||||
$('#btn_proceed_pi').toggleClass('d-none', has_pi || status < 1 || is_cancelled);
|
||||
if (has_pi && pi_id) {
|
||||
$('#btn_view_pi')
|
||||
.attr('href', '<?php echo $server_url?>po/manage_purchase_invoice.php?id=' + pi_id);
|
||||
}
|
||||
$('#btn_view_pi').toggleClass('d-none', !has_pi);
|
||||
|
||||
// New Supplier Return — only when goods have been received (status 2 or 3)
|
||||
$('#btn_new_return').toggleClass('d-none', !has_received || is_cancelled);
|
||||
}
|
||||
|
||||
|
||||
@@ -695,7 +668,6 @@
|
||||
$('#page_subtitle').text('');
|
||||
$('#status_card').removeClass('d-none');
|
||||
$('#badge_status').html(po_status_badge(o.status));
|
||||
$('#badge_payment').html(po_payment_badge(o.payment_status));
|
||||
$('#po_number_display').text(o.po_number);
|
||||
$('#expected_date_display')
|
||||
.toggleClass('d-none', !o.expected_date)
|
||||
@@ -706,18 +678,17 @@
|
||||
$('#po_date').val(o.po_date ? format_date(o.po_date) : '');
|
||||
$('#expected_date').val(o.expected_date ? format_date(o.expected_date) : '');
|
||||
$('#warehouse_id').val(o.warehouse_id || '');
|
||||
$('#department_id').val(o.department_id || 0);
|
||||
$('#notes').val(o.notes || '');
|
||||
$('#discount').val(o.discount || 0);
|
||||
$('#tax').val(o.tax || 0);
|
||||
$('#tax_adjustment').val(o.tax_adjustment || 0);
|
||||
$('#shipping_fee').val(o.shipping_fee || 0);
|
||||
$('#payment_status_select').val(o.payment_status ?? 0);
|
||||
|
||||
$('#item_tbody').empty();
|
||||
(o.items || []).forEach(function(item) { add_item_row(item); });
|
||||
recalc_totals();
|
||||
|
||||
var receivable = res.receivable_items || [];
|
||||
set_mode(o.status, receivable.length);
|
||||
set_mode(o.status, receivable.length, res, o.closed_at);
|
||||
if (receivable.length > 0) {
|
||||
build_receive_rows(receivable);
|
||||
}
|
||||
@@ -737,14 +708,15 @@
|
||||
action: po_id ? 'update' : 'create',
|
||||
data: {
|
||||
id: po_id,
|
||||
contact_id: $('#contact_id').val() || 0,
|
||||
contact_id: $('#contact_id').val() || 0,
|
||||
department_id: $('#department_id').val() || 0,
|
||||
po_date: to_iso_date($('#po_date').val()),
|
||||
expected_date: to_iso_date($('#expected_date').val()) || '',
|
||||
warehouse_id: $('#warehouse_id').val() || 0,
|
||||
warehouse_id: $('#warehouse_id').val() || 0,
|
||||
items: JSON.stringify(items),
|
||||
discount: $('#discount').val() || 0,
|
||||
tax: $('#tax').val() || 0,
|
||||
shipping_fee: $('#shipping_fee').val() || 0,
|
||||
discount: $('#discount').val() || 0,
|
||||
tax_adjustment: $('#tax_adjustment').val() || 0,
|
||||
shipping_fee: $('#shipping_fee').val() || 0,
|
||||
notes: $('#notes').val(),
|
||||
},
|
||||
onSuccess: function(res) {
|
||||
@@ -853,10 +825,7 @@
|
||||
receive_items: JSON.stringify(receive_items),
|
||||
},
|
||||
onSuccess: function(res) {
|
||||
var msg = res.auto_approved
|
||||
? 'Goods received and stock approved automatically.'
|
||||
: 'Goods received. Stock-in rows are pending warehouse approval.';
|
||||
bootbox.alert(msg, function() { retrieve_po(); });
|
||||
bootbox.alert('Goods received successfully.', function() { retrieve_po(); });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -864,19 +833,51 @@
|
||||
}
|
||||
|
||||
|
||||
// ── Update payment status ─────────────────────────────────────────────────
|
||||
function update_payment_status() {
|
||||
ajax_request({
|
||||
url: '<?php echo $server_url?>po/api/engine/update_po_payment.php',
|
||||
autoPrepare: true, checkRequired: 0,
|
||||
action: 'update',
|
||||
data: { id: po_id, payment_status: $('#payment_status_select').val() },
|
||||
onSuccess: function() { retrieve_po(); }
|
||||
// ── Proceed to Purchase Invoice ───────────────────────────────────────────
|
||||
function proceed_to_purchase_invoice() {
|
||||
bootbox.confirm({
|
||||
message: 'Create a purchase invoice from this PO?',
|
||||
buttons: {
|
||||
confirm: { label: 'Create Purchase Invoice', className: 'btn-primary' },
|
||||
cancel: { label: 'Back', className: 'btn-secondary' }
|
||||
},
|
||||
callback: function(result) {
|
||||
if (!result) return;
|
||||
ajax_request({
|
||||
url: '<?php echo $server_url?>po/api/engine/proceed_to_purchase_invoice.php',
|
||||
autoPrepare: true, checkRequired: 0,
|
||||
action: 'create',
|
||||
data: { po_id: po_id },
|
||||
onSuccess: function(res) {
|
||||
window.location.href = '<?php echo $server_url?>po/manage_purchase_invoice.php?id=' + res.new_id;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ── Cancel PO ────────────────────────────────────────────────────────────
|
||||
function close_po() {
|
||||
bootbox.confirm({
|
||||
message: 'Close this PO? No further goods receipts will be accepted. This cannot be undone.',
|
||||
buttons: {
|
||||
confirm: { label: 'Yes, Close PO', className: 'btn-warning' },
|
||||
cancel: { label: 'Back', className: 'btn-secondary' }
|
||||
},
|
||||
callback: function(result) {
|
||||
if (!result) return;
|
||||
ajax_request({
|
||||
url: '<?php echo $server_url?>po/api/engine/close_po.php',
|
||||
autoPrepare: true, checkRequired: 0,
|
||||
action: 'update',
|
||||
data: { id: po_id },
|
||||
onSuccess: function() { retrieve_po(); }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cancel_po() {
|
||||
bootbox.confirm({
|
||||
message: 'Cancel this purchase order? Any pending (unapproved) stock-in rows will be cancelled.',
|
||||
@@ -930,59 +931,24 @@
|
||||
}
|
||||
|
||||
|
||||
// ── Contact autocomplete ──────────────────────────────────────────────────
|
||||
function init_contact_search() {
|
||||
$('#contact').autocomplete({
|
||||
source: function(req, res) {
|
||||
ajax_request({
|
||||
url: '<?php echo $server_url?>contact/api/engine/contact.php',
|
||||
autoPrepare: true, checkRequired: 0,
|
||||
action: 'read',
|
||||
data: { keyword: req.term },
|
||||
onSuccess: function(data) {
|
||||
res(data.output.map(c => ({ label: c.contact_name, value: c.contact_name, id: c.id })));
|
||||
}
|
||||
});
|
||||
},
|
||||
select: function(e, ui) { $('#contact_id').val(ui.item.id); }
|
||||
});
|
||||
}
|
||||
$(document).on('contact:selected', '#contact', function(e, contact) {
|
||||
$('#contact_id').val(contact.id);
|
||||
});
|
||||
|
||||
|
||||
// ── Product autocomplete ──────────────────────────────────────────────────
|
||||
function init_product_search() {
|
||||
$(document).on('input', '.item_sku', function() {
|
||||
var $input = $(this);
|
||||
var $row = $input.closest('tr');
|
||||
$input.autocomplete({
|
||||
source: function(req, res) {
|
||||
ajax_request({
|
||||
url: '<?php echo $server_url?>inventory/api/engine/product.php',
|
||||
autoPrepare: true, checkRequired: 0,
|
||||
action: 'read',
|
||||
data: { keyword: req.term },
|
||||
onSuccess: function(data) {
|
||||
res((data.output || []).map(p => ({
|
||||
label: p.sku + ' — ' + p.product_name,
|
||||
value: p.sku,
|
||||
name: p.product_name,
|
||||
})));
|
||||
}
|
||||
});
|
||||
},
|
||||
select: function(e, ui) {
|
||||
$row.find('.item_name').val(ui.item.name);
|
||||
$row.find('.item_name_display').text(ui.item.name);
|
||||
recalc_totals();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
$(document).on('product:selected', '.item_sku', function(e, product) {
|
||||
var $row = $(this).closest('tr');
|
||||
$row.find('.item_name_display').text(product.product_name);
|
||||
$row.find('.item_name').val(product.product_name || '');
|
||||
$row.find('.item_tax_rate').val(parseFloat(product.tax_rate || 0).toFixed(2));
|
||||
recalc_totals();
|
||||
});
|
||||
|
||||
|
||||
// ── Boot ─────────────────────────────────────────────────────────────────
|
||||
$(async function() {
|
||||
try {
|
||||
load_departments('department_id');
|
||||
await load_location_config();
|
||||
await retrieve_warehouses();
|
||||
|
||||
@@ -997,9 +963,6 @@
|
||||
flatpickr('#po_date', { dateFormat: 'd/m/Y', allowInput: true });
|
||||
flatpickr('#expected_date', { dateFormat: 'd/m/Y', allowInput: true });
|
||||
|
||||
init_contact_search();
|
||||
init_product_search();
|
||||
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user