747 lines
29 KiB
PHP
747 lines
29 KiB
PHP
<?php
|
|
session_start();
|
|
require '../config.php';
|
|
require '../include_header.php';
|
|
|
|
$order_id = (int)($_GET['id'] ?? 0);
|
|
$is_edit = isset($_GET['edit']) || $order_id === 0;
|
|
?>
|
|
|
|
<body>
|
|
<?php require '../include_topbar.php'; ?>
|
|
<?php require '../include_sidebar.php'; ?>
|
|
|
|
<!-- MAIN CONTENT -->
|
|
<main id="content" class="content py-15">
|
|
<div class="container-fluid">
|
|
|
|
<!-- Page header -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="mb-6 d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h1 class="fs-3 mb-1" id="page_title">
|
|
<?php echo $order_id ? 'Order Detail' : 'New Order'; ?>
|
|
</h1>
|
|
<p class="mb-0 text-muted" id="page_subtitle">
|
|
<?php echo $order_id ? 'View or manage this order' : 'Create a new sales order'; ?>
|
|
</p>
|
|
</div>
|
|
<div class="d-flex gap-2" id="header_actions">
|
|
<a href="<?php echo $server_url?>order/order.php" class="btn btn-light">
|
|
<i class="ti ti-arrow-left me-1"></i>Back
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-5">
|
|
|
|
<!-- Left: Order form -->
|
|
<div class="col-lg-8">
|
|
|
|
<!-- Order header card -->
|
|
<div class="card mb-5">
|
|
<div class="card-body p-5">
|
|
<h2 class="fs-5 mb-4">Order Information</h2>
|
|
<div class="row gx-5">
|
|
|
|
<div class="mb-3 col-lg-6">
|
|
<label class="form-label">Contact</label>
|
|
<input type="text" id="contact" class="form-control contact_search"
|
|
placeholder="Search contact..." autocomplete="off">
|
|
<input type="hidden" id="contact_id">
|
|
</div>
|
|
|
|
<div class="mb-3 col-lg-6">
|
|
<label class="form-label">Order Date</label>
|
|
<input type="text" id="order_date" class="form-control"
|
|
placeholder="DD/MM/YYYY" autocomplete="off">
|
|
</div>
|
|
|
|
<div class="mb-3 col-lg-6">
|
|
<label class="form-label">Shipping Tracking Number</label>
|
|
<input type="text" id="shipping_tracking_number" class="form-control"
|
|
placeholder="Carrier tracking number..." maxlength="100" autocomplete="off">
|
|
</div>
|
|
|
|
<div class="mb-3 col-lg-12">
|
|
<label class="form-label">Notes</label>
|
|
<textarea id="notes" class="form-control" rows="2"
|
|
placeholder="Internal notes..."></textarea>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Order items card -->
|
|
<div class="card mb-5">
|
|
<div class="card-body p-5">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h2 class="fs-5 mb-0">Order Items</h2>
|
|
<button class="btn btn-sm btn-outline-primary" id="btn_add_item"
|
|
onclick="add_item_row()">
|
|
<i class="ti ti-plus me-1"></i>Add Item
|
|
</button>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table mb-0" id="item_table">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Product</th>
|
|
<th>Warehouse</th>
|
|
<th style="width:120px;">Qty</th>
|
|
<th style="width:130px;">Unit Price</th>
|
|
<th style="width:130px;">Total</th>
|
|
<th style="width:40px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="item_tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Right: Summary + actions -->
|
|
<div class="col-lg-4">
|
|
|
|
<!-- Status card (view mode only) -->
|
|
<div class="card mb-5 d-none" id="status_card">
|
|
<div class="card-body p-4">
|
|
<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>
|
|
<span id="badge_fulfillment"></span>
|
|
</div>
|
|
<p class="mb-0 small text-muted" id="order_number_display"></p>
|
|
<p class="mb-0 small text-muted d-none" id="tracking_number_display"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Totals card -->
|
|
<div class="card mb-5">
|
|
<div class="card-body p-4">
|
|
<h2 class="fs-5 mb-3">Summary</h2>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span class="text-muted">Subtotal</span>
|
|
<span id="summary_subtotal">0.00</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<span class="text-muted">Discount</span>
|
|
<input type="number" id="discount" class="form-control form-control-sm text-end"
|
|
style="width:110px;" value="0" min="0" oninput="recalc_totals()">
|
|
</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()">
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-3">
|
|
<span class="text-muted">Shipping</span>
|
|
<input type="number" id="shipping_fee" class="form-control form-control-sm text-end"
|
|
style="width:110px;" value="0" min="0" oninput="recalc_totals()">
|
|
</div>
|
|
<hr>
|
|
<div class="d-flex justify-content-between fw-bold fs-5">
|
|
<span>Grand Total</span>
|
|
<span id="summary_grand_total">0.00</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action buttons -->
|
|
<div class="card">
|
|
<div class="card-body p-4 d-flex flex-column gap-2">
|
|
|
|
<!-- Save draft (edit mode) -->
|
|
<button class="btn btn-primary w-100" id="btn_save"
|
|
onclick="save_order()">
|
|
<i class="ti ti-device-floppy me-1"></i>Save Draft
|
|
</button>
|
|
|
|
<!-- Confirm order (draft only) -->
|
|
<?php if(isset($_GET["id"])){ ?>
|
|
<button class="btn btn-success w-100" id="btn_confirm"
|
|
onclick="confirm_order()">
|
|
<i class="ti ti-check me-1"></i>Confirm Order
|
|
</button>
|
|
<?php } ?>
|
|
|
|
<!-- Proceed to invoice (confirmed, manual mode) -->
|
|
<button class="btn btn-outline-primary w-100" id="btn_invoice"
|
|
onclick="proceed_to_invoice()">
|
|
<i class="ti ti-file-invoice me-1"></i>Proceed to Invoice
|
|
</button>
|
|
|
|
<!-- Update fulfillment status (confirmed and above) -->
|
|
<div id="fulfillment_status_group">
|
|
<label class="form-label small text-muted mb-1">Fulfillment Status</label>
|
|
<div class="d-flex gap-2">
|
|
<select id="fulfillment_status_select" class="form-select form-select-sm">
|
|
<option value="1">Picking</option>
|
|
<option value="2">Packed</option>
|
|
<option value="3">Shipped</option>
|
|
</select>
|
|
<button class="btn btn-sm btn-outline-primary text-nowrap"
|
|
onclick="update_fulfillment_status()">
|
|
<i class="ti ti-check"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div id="payment_status_group">
|
|
<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>
|
|
|
|
<!-- Create Return (confirmed and above) -->
|
|
<button class="btn btn-outline-warning w-100" id="btn_return"
|
|
onclick="create_return()">
|
|
<i class="ti ti-arrow-back-up me-1"></i>Create Return
|
|
</button>
|
|
|
|
<!-- Cancel order -->
|
|
<button class="btn btn-outline-danger w-100" id="btn_cancel"
|
|
onclick="cancel_order()">
|
|
<i class="ti ti-x me-1"></i>Cancel Order
|
|
</button>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<?php require '../include_ending.php'; ?>
|
|
|
|
<script>
|
|
|
|
var order_id = <?php echo $order_id; ?>;
|
|
var order_data = null;
|
|
var warehouses = [];
|
|
var item_index = 0;
|
|
|
|
|
|
// ── Status helpers ────────────────────────────────────────────────────────
|
|
function order_status_badge(status) {
|
|
const map = {
|
|
'-2': '<span class="badge bg-danger">Pending — Awaiting 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">Processing</span>',
|
|
'3': '<span class="badge bg-success">Completed</span>',
|
|
};
|
|
return map[String(status)] ?? '—';
|
|
}
|
|
|
|
function payment_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>',
|
|
};
|
|
return map[String(status)] ?? '—';
|
|
}
|
|
|
|
function fulfillment_status_badge(status) {
|
|
const map = {
|
|
'1': '<span class="badge bg-warning text-dark"><i class="ti ti-list-check me-1"></i>Picking</span>',
|
|
'2': '<span class="badge bg-info text-white"><i class="ti ti-package me-1"></i>Packed</span>',
|
|
'3': '<span class="badge bg-success"><i class="ti ti-truck me-1"></i>Shipped</span>',
|
|
};
|
|
return map[String(status)] ?? '';
|
|
}
|
|
|
|
|
|
// ── Recalculate totals ────────────────────────────────────────────────────
|
|
function recalc_totals() {
|
|
var subtotal = 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;
|
|
$(this).find('.item_total').text(format_number(total, 2));
|
|
subtotal += total;
|
|
});
|
|
|
|
var discount = parseFloat($('#discount').val()) || 0;
|
|
var tax = parseFloat($('#tax').val()) || 0;
|
|
var shipping_fee = parseFloat($('#shipping_fee').val()) || 0;
|
|
var grand_total = subtotal - discount + tax + shipping_fee;
|
|
|
|
$('#summary_subtotal').text(format_number(subtotal, 2));
|
|
$('#summary_grand_total').text(format_number(grand_total, 2));
|
|
}
|
|
|
|
|
|
// ── Add item row ──────────────────────────────────────────────────────────
|
|
function add_item_row(item) {
|
|
item = item || {};
|
|
var idx = item_index++;
|
|
|
|
var wh_options = warehouses.map(w =>
|
|
`<option value="${w.id}" ${item.warehouse_id == w.id ? 'selected' : ''}>${w.warehouse_name}</option>`
|
|
).join('');
|
|
|
|
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}">
|
|
<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>
|
|
</td>
|
|
<td>
|
|
<select class="form-select form-select-sm item_warehouse">
|
|
${wh_options}
|
|
</select>
|
|
</td>
|
|
<td>
|
|
<input type="number" class="form-control form-control-sm item_qty"
|
|
value="${item.quantity || 1}" min="0.0001" step="any" oninput="recalc_totals()">
|
|
</td>
|
|
<td>
|
|
<input type="number" class="form-control form-control-sm item_price"
|
|
value="${item.price || 0}" min="0" step="any" oninput="recalc_totals()">
|
|
</td>
|
|
<td class="item_total text-end">${format_number((item.quantity || 1) * (item.price || 0), 2)}</td>
|
|
<td>
|
|
<a href="javascript:void(0);" class="link-danger" onclick="remove_item(${idx})">
|
|
<i class="ti ti-trash fs-5"></i>
|
|
</a>
|
|
</td>
|
|
</tr>`;
|
|
|
|
$('#item_tbody').append(row);
|
|
recalc_totals();
|
|
}
|
|
|
|
function remove_item(idx) {
|
|
$(`#item_row_${idx}`).remove();
|
|
recalc_totals();
|
|
}
|
|
|
|
|
|
// ── Collect items from DOM ────────────────────────────────────────────────
|
|
function collect_items() {
|
|
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 wh = parseInt($(this).find('.item_warehouse').val()) || 0;
|
|
var qty = parseFloat($(this).find('.item_qty').val()) || 0;
|
|
var price = parseFloat($(this).find('.item_price').val()) || 0;
|
|
if (!sku || !wh || qty <= 0) { idx++; return; }
|
|
items.push({
|
|
item_id: idx,
|
|
product_sku: sku,
|
|
product_name: name,
|
|
warehouse_id: wh,
|
|
zone: '',
|
|
aisle: '',
|
|
rack: '',
|
|
lot_number: '',
|
|
serial_number: '',
|
|
quantity: qty,
|
|
price: price,
|
|
total_price: qty * price,
|
|
stock_out_id: 0,
|
|
});
|
|
idx++;
|
|
});
|
|
return items;
|
|
}
|
|
|
|
|
|
// ── Set view/edit mode ────────────────────────────────────────────────────
|
|
function set_mode(status, has_active_invoice, returnable_count, fulfillment_status) {
|
|
status = parseInt(status);
|
|
has_active_invoice = has_active_invoice || false;
|
|
returnable_count = returnable_count ?? 1;
|
|
fulfillment_status = parseInt(fulfillment_status) || 0;
|
|
var is_pending = status === -2;
|
|
var is_draft = status === 0;
|
|
var is_editable = is_pending || is_draft; // both Pending and Draft are editable
|
|
var can_confirm = status === 0; // only Draft can be confirmed (not Pending)
|
|
var can_cancel = status === -2 || status === 0 || status === 1;
|
|
var is_cancelled = status === -1;
|
|
var is_completed = status === 3;
|
|
var can_fulfill = status >= 1 && !is_cancelled && !is_completed;
|
|
|
|
// Edit controls
|
|
$('#btn_add_item, #contact, #order_date, #notes, #discount, #tax, #shipping_fee')
|
|
.prop('disabled', !is_editable);
|
|
$('#shipping_tracking_number').prop('disabled', !(is_editable || can_fulfill));
|
|
$('.item_sku, .item_qty, .item_price, .item_warehouse').prop('disabled', !is_editable);
|
|
$('a[onclick^="remove_item"]').toggleClass('d-none', !is_editable);
|
|
|
|
// Pending hint
|
|
if (is_pending) {
|
|
$('#page_subtitle').text('Assign a warehouse for each item, then save to move this order to Draft.');
|
|
}
|
|
|
|
// Action buttons
|
|
set_btn_state('#btn_save', is_draft, 'Only draft orders can be edited');
|
|
set_btn_state('#btn_confirm', can_confirm, 'Order must be in draft to confirm');
|
|
set_btn_state('#btn_cancel', can_cancel && !is_cancelled,
|
|
is_cancelled ? 'Order is already cancelled' : 'Only draft or confirmed orders can be cancelled');
|
|
|
|
// Fulfillment status — confirmed and above, not cancelled, not completed
|
|
$('#fulfillment_status_group').toggleClass('d-none', !can_fulfill);
|
|
if (can_fulfill) {
|
|
// Only show options ahead of current fulfillment
|
|
$('#fulfillment_status_select option').each(function() {
|
|
$(this).prop('disabled', parseInt($(this).val()) <= fulfillment_status);
|
|
});
|
|
// Default to next step
|
|
var next = fulfillment_status + 1;
|
|
if (next <= 3) $('#fulfillment_status_select').val(next);
|
|
else $('#fulfillment_status_select').val(fulfillment_status);
|
|
}
|
|
|
|
// Payment status selector
|
|
$('#payment_status_select').prop('disabled', status < 1 || is_cancelled);
|
|
|
|
// Create Return
|
|
var can_return = status >= 1 && !is_cancelled && returnable_count > 0;
|
|
var return_title = is_cancelled ? 'Order is cancelled'
|
|
: status < 1 ? 'Confirm the order before creating a return'
|
|
: returnable_count === 0 ? 'All items have already been returned' : '';
|
|
set_btn_state('#btn_return', can_return, return_title);
|
|
$('#btn_return').html(returnable_count === 0 && status >= 1 && !is_cancelled
|
|
? '<i class="ti ti-arrow-back-up me-1"></i>All Items Returned'
|
|
: '<i class="ti ti-arrow-back-up me-1"></i>Create Return');
|
|
|
|
// Proceed to Invoice
|
|
var can_invoice = status >= 1 && !is_cancelled && !has_active_invoice;
|
|
var invoice_title = is_cancelled ? 'Order is cancelled'
|
|
: status < 1 ? 'Confirm the order before creating an invoice'
|
|
: has_active_invoice ? 'An active invoice already exists for this order' : '';
|
|
set_btn_state('#btn_invoice', can_invoice, invoice_title);
|
|
$('#btn_invoice').html(has_active_invoice
|
|
? '<i class="ti ti-file-invoice me-1"></i>Invoice Already Created'
|
|
: '<i class="ti ti-file-invoice me-1"></i>Proceed to Invoice');
|
|
}
|
|
|
|
|
|
// ── Load existing order ───────────────────────────────────────────────────
|
|
function retrieve_order() {
|
|
if (!order_id) return Promise.resolve();
|
|
return ajax_request({
|
|
url: '<?php echo $server_url?>order/api/engine/retrieve_order.php',
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
action: 'read',
|
|
data: { id: order_id },
|
|
onSuccess: function(res) {
|
|
order_data = res.output;
|
|
var o = order_data;
|
|
|
|
// Header
|
|
$('#page_title').text(o.order_number);
|
|
$('#page_subtitle').text('');
|
|
$('#status_card').removeClass('d-none');
|
|
$('#badge_status').html(order_status_badge(o.status));
|
|
$('#badge_payment').html(payment_status_badge(o.payment_status));
|
|
$('#badge_fulfillment').html(fulfillment_status_badge(o.fulfillment_status));
|
|
$('#order_number_display').text(o.order_number);
|
|
$('#tracking_number_display')
|
|
.toggleClass('d-none', !o.shipping_tracking_number)
|
|
.text(o.shipping_tracking_number ? 'Tracking: ' + o.shipping_tracking_number : '');
|
|
|
|
// Fields
|
|
$('#contact').val(o.contact_name || '');
|
|
$('#contact_id').val(o.contact_id);
|
|
$('#order_date').val(o.order_date ? format_date(o.order_date) : '');
|
|
$('#shipping_tracking_number').val(o.shipping_tracking_number || '');
|
|
$('#notes').val(o.notes || '');
|
|
$('#discount').val(o.discount || 0);
|
|
$('#tax').val(o.tax || 0);
|
|
$('#shipping_fee').val(o.shipping_fee || 0);
|
|
|
|
// Items
|
|
$('#item_tbody').empty();
|
|
(o.items || []).forEach(function(item) { add_item_row(item); });
|
|
|
|
recalc_totals();
|
|
set_mode(o.status, res.has_active_invoice, (res.returnable_items || []).length, o.fulfillment_status);
|
|
$('#payment_status_select').val(o.payment_status ?? 0);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// ── Save draft ────────────────────────────────────────────────────────────
|
|
function save_order() {
|
|
var items = collect_items();
|
|
if (!items.length) {
|
|
bootbox.alert('Please add at least one item.');
|
|
return;
|
|
}
|
|
|
|
const dateStr = $('#order_date').val();
|
|
const [day, month, year] = dateStr.split('/');
|
|
const formatted = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
|
|
|
ajax_request({
|
|
url: '<?php echo $server_url?>order/api/engine/manage_order.php',
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
action: order_id ? 'update' : 'create',
|
|
data: {
|
|
id: order_id,
|
|
contact_id: $('#contact_id').val() || 0,
|
|
order_date: to_iso_date($('#order_date').val()),
|
|
items: JSON.stringify(items),
|
|
discount: $('#discount').val() || 0,
|
|
tax: $('#tax').val() || 0,
|
|
shipping_fee: $('#shipping_fee').val() || 0,
|
|
shipping_tracking_number: $('#shipping_tracking_number').val().trim(),
|
|
notes: $('#notes').val(),
|
|
},
|
|
onSuccess: function(res) {
|
|
if (!order_id && res.new_id) {
|
|
window.location.href = '<?php echo $server_url?>order/manage_order.php?id=' + res.new_id;
|
|
} else {
|
|
retrieve_order();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// ── Confirm order ─────────────────────────────────────────────────────────
|
|
function confirm_order() {
|
|
bootbox.confirm({
|
|
message: 'Confirm this order? Stock-out rows will be created for all items using FIFO rack selection.',
|
|
buttons: {
|
|
confirm: { label: 'Confirm Order', className: 'btn-success' },
|
|
cancel: { label: 'Back', className: 'btn-secondary' }
|
|
},
|
|
callback: function(result) {
|
|
if (!result) return;
|
|
ajax_request({
|
|
url: '<?php echo $server_url?>order/api/engine/confirm_order.php',
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
action: 'update',
|
|
data: { id: order_id },
|
|
onSuccess: function() {
|
|
retrieve_order();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// ── Update fulfillment status ─────────────────────────────────────────────
|
|
function update_fulfillment_status() {
|
|
ajax_request({
|
|
url: '<?php echo $server_url?>order/api/engine/update_order_status.php',
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
action: 'update',
|
|
data: {
|
|
id: order_id,
|
|
fulfillment_status: $('#fulfillment_status_select').val(),
|
|
shipping_tracking_number: $('#shipping_tracking_number').val().trim(),
|
|
},
|
|
onSuccess: function() {
|
|
retrieve_order();
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// ── Update payment status ─────────────────────────────────────────────────
|
|
function update_payment_status() {
|
|
ajax_request({
|
|
url: '<?php echo $server_url?>order/api/engine/update_payment_status.php',
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
action: 'update',
|
|
data: {
|
|
id: order_id,
|
|
payment_status: $('#payment_status_select').val(),
|
|
},
|
|
onSuccess: function() {
|
|
retrieve_order();
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// ── Create return ─────────────────────────────────────────────────────────
|
|
function create_return() {
|
|
window.location.href = '<?php echo $server_url?>order/manage_return.php?order_id=' + order_id;
|
|
}
|
|
|
|
|
|
// ── Proceed to invoice ────────────────────────────────────────────────────
|
|
function proceed_to_invoice() {
|
|
ajax_request({
|
|
url: '<?php echo $server_url?>order/api/engine/proceed_to_invoice.php',
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
action: 'create',
|
|
data: { order_id: order_id },
|
|
onSuccess: function(res) {
|
|
if (res.new_id) {
|
|
window.location.href = '<?php echo $server_url?>order/manage_invoice.php?id=' + res.new_id;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// ── Cancel order ──────────────────────────────────────────────────────────
|
|
function cancel_order() {
|
|
bootbox.confirm({
|
|
message: 'Cancel this order? All pending stock-out rows will be cancelled.',
|
|
buttons: {
|
|
confirm: { label: 'Yes, Cancel Order', className: 'btn-danger' },
|
|
cancel: { label: 'Back', className: 'btn-secondary' }
|
|
},
|
|
callback: function(result) {
|
|
if (!result) return;
|
|
ajax_request({
|
|
url: '<?php echo $server_url?>order/api/engine/cancel_order.php',
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
action: 'update',
|
|
data: { id: order_id },
|
|
onSuccess: function() {
|
|
retrieve_order();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// ── Retrieve warehouses ───────────────────────────────────────────────────
|
|
function retrieve_warehouses() {
|
|
return ajax_request({
|
|
url: '<?php echo $server_url?>inventory/api/engine/warehouse.php',
|
|
autoPrepare: true,
|
|
checkRequired: 0,
|
|
action: 'read',
|
|
onSuccess: function(res) {
|
|
warehouses = res.output || [];
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// ── 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);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// ── Product autocomplete (per row) ────────────────────────────────────────
|
|
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();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
// ── Boot ─────────────────────────────────────────────────────────────────
|
|
$(async function() {
|
|
try {
|
|
await retrieve_warehouses();
|
|
|
|
if (order_id) {
|
|
await retrieve_order();
|
|
} else {
|
|
// New order — set today's date, add one empty row
|
|
var today = flatpickr.formatDate(new Date(), 'd/m/Y');
|
|
$('#order_date').val(today);
|
|
add_item_row();
|
|
set_mode(0);
|
|
}
|
|
|
|
// Init flatpickr on order date
|
|
flatpickr('#order_date', { dateFormat: 'd/m/Y', allowInput: true });
|
|
|
|
init_contact_search();
|
|
init_product_search();
|
|
|
|
} catch(e) {
|
|
console.log(e);
|
|
}
|
|
});
|
|
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|