warehouse layers [switchable]
This commit is contained in:
@@ -1,31 +1,22 @@
|
||||
<?php
|
||||
session_start();
|
||||
require '../../../assets/utils/db_auth.php';
|
||||
require '../../../assets/utils/classes/ReturnManager.php';
|
||||
require '../../../assets/utils/classes/WarehouseManager.php';
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
|
||||
if (!$id) {
|
||||
$answer['message'] = 'Invalid ID.';
|
||||
$answer['message'] = 'Invalid return ID.';
|
||||
http_response_code(400);
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
try {
|
||||
dbTransaction($pdo2, function($pdo) use ($id, $company_id) {
|
||||
$sth = $pdo->prepare(
|
||||
"SELECT status FROM td_return
|
||||
WHERE company_id = :company_id AND id = :id"
|
||||
);
|
||||
$sth->execute([':company_id' => $company_id, ':id' => $id]);
|
||||
$row = $sth->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) throw new Exception("Return not found.");
|
||||
if ((int)$row['status'] !== 0) throw new Exception("Only draft returns can be cancelled.");
|
||||
|
||||
$pdo->prepare(
|
||||
"UPDATE td_return SET status = -1
|
||||
WHERE id = :id AND company_id = :company_id"
|
||||
)->execute([':id' => $id, ':company_id' => $company_id]);
|
||||
dbTransaction($pdo2, function($pdo) use ($id, $company_id, $logging) {
|
||||
$ret = new ReturnManager($pdo, $company_id);
|
||||
$whMgmt = new WarehouseManager($pdo, $company_id);
|
||||
$ret->cancelReturn($id, $logging, $whMgmt);
|
||||
});
|
||||
|
||||
$answer['success'] = 1;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
session_start();
|
||||
require '../../../assets/utils/db_auth.php';
|
||||
require '../../../assets/utils/classes/OrderManager.php';
|
||||
require '../../../assets/utils/classes/CompanySettingManager.php';
|
||||
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$fulfillment_status = (int)($data['fulfillment_status'] ?? 0);
|
||||
$tracking_number = trim((string)($data['shipping_tracking_number'] ?? ''));
|
||||
|
||||
if (!$id) {
|
||||
$answer['message'] = 'Invalid order ID.';
|
||||
http_response_code(400);
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
$csm = new CompanySettingManager($pdo1, $company_id);
|
||||
$auto_complete = (int)$csm->get('auto_complete_on_ship') === 1;
|
||||
|
||||
try {
|
||||
dbTransaction($pdo2, function($pdo) use ($id, $fulfillment_status, $tracking_number, $company_id, $logging, $auto_complete) {
|
||||
$order = new OrderManager($pdo, $company_id);
|
||||
$order->updateFulfillmentStatus($id, $fulfillment_status, $tracking_number, $logging, $auto_complete);
|
||||
});
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Order updated.';
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$answer['message'] = 'Database error, please try again.';
|
||||
http_response_code(500);
|
||||
|
||||
} catch (Exception $e) {
|
||||
$answer['message'] = $e->getMessage();
|
||||
http_response_code(400);
|
||||
}
|
||||
|
||||
exit(json_encode($answer));
|
||||
?>
|
||||
@@ -201,7 +201,7 @@
|
||||
var status_hint = !is_invoice ? 'Status cannot be changed on credit/debit notes'
|
||||
: status === 0 ? 'Issue the invoice first before changing status'
|
||||
: status === 4 ? 'Invoice is void' : '';
|
||||
set_btn_state('#invoice_status_group button', can_change_status, status_hint);
|
||||
|
||||
$('#invoice_status_select').prop('disabled', !can_change_status);
|
||||
|
||||
}
|
||||
|
||||
+63
-18
@@ -60,6 +60,12 @@
|
||||
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"
|
||||
@@ -112,8 +118,10 @@
|
||||
<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>
|
||||
|
||||
@@ -172,7 +180,21 @@
|
||||
<i class="ti ti-file-invoice me-1"></i>Proceed to Invoice
|
||||
</button>
|
||||
|
||||
<!-- Update payment status (confirmed and above) -->
|
||||
<!-- 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">
|
||||
@@ -242,6 +264,15 @@
|
||||
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() {
|
||||
@@ -346,37 +377,45 @@
|
||||
|
||||
|
||||
// ── Set view/edit mode ────────────────────────────────────────────────────
|
||||
function set_mode(status, has_active_invoice, returnable_count) {
|
||||
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_draft = status === 0;
|
||||
var can_confirm = status === 0;
|
||||
var can_cancel = 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_draft);
|
||||
$('#shipping_tracking_number').prop('disabled', !(is_draft || can_fulfill));
|
||||
$('.item_sku, .item_qty, .item_price, .item_warehouse').prop('disabled', !is_draft);
|
||||
$('a[onclick^="remove_item"]').toggleClass('d-none', !is_draft);
|
||||
|
||||
// Action buttons — set_btn_state handles class swap + disabled + title
|
||||
// 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');
|
||||
|
||||
// Order status selector — confirmed and above, not cancelled
|
||||
var can_change_status = status >= 1 && !is_cancelled;
|
||||
$('#order_status_group').toggleClass('d-none', !can_change_status);
|
||||
set_btn_state('#order_status_group button', can_change_status,
|
||||
is_cancelled ? 'Order is cancelled' : 'Confirm the order first');
|
||||
$('#order_status_select').prop('disabled', !can_change_status);
|
||||
// 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
|
||||
set_btn_state('#payment_status_group button', status >= 1 && !is_cancelled,
|
||||
'Confirm the order before updating payment status');
|
||||
$('#payment_status_select').prop('disabled', status < 1 || is_cancelled);
|
||||
|
||||
// Create Return
|
||||
@@ -421,12 +460,17 @@
|
||||
$('#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);
|
||||
@@ -437,9 +481,8 @@
|
||||
(o.items || []).forEach(function(item) { add_item_row(item); });
|
||||
|
||||
recalc_totals();
|
||||
set_mode(o.status, res.has_active_invoice, (res.returnable_items || []).length);
|
||||
set_mode(o.status, res.has_active_invoice, (res.returnable_items || []).length, o.fulfillment_status);
|
||||
$('#payment_status_select').val(o.payment_status ?? 0);
|
||||
$('#order_status_select').val(o.status);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -470,6 +513,7 @@
|
||||
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) {
|
||||
@@ -508,16 +552,17 @@
|
||||
}
|
||||
|
||||
|
||||
// ── Update order status ───────────────────────────────────────────────────
|
||||
function update_order_status() {
|
||||
// ── 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,
|
||||
status: $('#order_status_select').val(),
|
||||
id: order_id,
|
||||
fulfillment_status: $('#fulfillment_status_select').val(),
|
||||
shipping_tracking_number: $('#shipping_tracking_number').val().trim(),
|
||||
},
|
||||
onSuccess: function() {
|
||||
retrieve_order();
|
||||
@@ -693,4 +738,4 @@
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -376,8 +376,8 @@
|
||||
status === -1 ? 'Return is cancelled' : 'Return has already been confirmed');
|
||||
set_btn_state('#btn_confirm', is_draft,
|
||||
status === -1 ? 'Return is cancelled' : 'Return is already confirmed');
|
||||
set_btn_state('#btn_cancel', is_draft,
|
||||
status === -1 ? 'Return is already cancelled' : 'Only draft returns can be cancelled');
|
||||
set_btn_state('#btn_cancel', is_draft || status === 1,
|
||||
status === -1 ? 'Return is already cancelled' : 'Return is completed and cannot be cancelled');
|
||||
}
|
||||
|
||||
|
||||
|
||||
+17
-3
@@ -95,7 +95,7 @@
|
||||
<div class="col-lg-5">
|
||||
<div class="position-relative">
|
||||
<input type="text" id="search_input" class="form-control ps-9"
|
||||
placeholder="Search order number or contact...">
|
||||
placeholder="Search order number, contact, or tracking...">
|
||||
<span class="position-absolute top-25 ms-4">
|
||||
<i class="ti ti-search text-muted"></i>
|
||||
</span>
|
||||
@@ -144,6 +144,8 @@
|
||||
<th class="py-3">Items</th>
|
||||
<th class="py-3">Grand Total</th>
|
||||
<th class="py-3">Status</th>
|
||||
<th class="py-3">Fulfillment</th>
|
||||
<th class="py-3">Tracking #</th>
|
||||
<th class="py-3">Payment</th>
|
||||
<th class="py-3">Action</th>
|
||||
</tr>
|
||||
@@ -188,6 +190,15 @@
|
||||
return map[status] ?? '<span class="badge bg-light text-dark">—</span>';
|
||||
}
|
||||
|
||||
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)] ?? '<span class="text-muted">—</span>';
|
||||
}
|
||||
|
||||
|
||||
// ── Retrieve orders ───────────────────────────────────────────────────────
|
||||
function retrieve_orders() {
|
||||
@@ -208,7 +219,8 @@
|
||||
var data = all.filter(function(o) {
|
||||
var match_kw = !keyword ||
|
||||
(o.order_number || '').toLowerCase().includes(keyword) ||
|
||||
(o.contact_name || '').toLowerCase().includes(keyword);
|
||||
(o.contact_name || '').toLowerCase().includes(keyword) ||
|
||||
(o.shipping_tracking_number || '').toLowerCase().includes(keyword);
|
||||
var match_st = fil_status === '' || String(o.status) === fil_status;
|
||||
var match_pm = fil_payment === '' || String(o.payment_status) === fil_payment;
|
||||
return match_kw && match_st && match_pm;
|
||||
@@ -254,6 +266,8 @@
|
||||
<td class="py-3">${item_count} item${item_count !== 1 ? 's' : ''}</td>
|
||||
<td class="py-3 fw-semibold">${format_number(o.grand_total, 2)}</td>
|
||||
<td class="py-3">${order_status_badge(o.status)}</td>
|
||||
<td class="py-3">${fulfillment_status_badge(o.fulfillment_status)}</td>
|
||||
<td class="py-3">${o.shipping_tracking_number || '<span class="text-muted">—</span>'}</td>
|
||||
<td class="py-3">${payment_status_badge(o.payment_status)}</td>
|
||||
<td class="py-3">
|
||||
<a href="<?php echo $server_url?>order/manage_order.php?id=${o.id}" title="View">
|
||||
@@ -309,4 +323,4 @@
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
|
||||
$.each(page_data, function(i, r) {
|
||||
var items = JSON.parse(r.items || '[]');
|
||||
var can_cancel = parseInt(r.status) === 0;
|
||||
var can_cancel = parseInt(r.status) >= 0 && parseInt(r.status) <= 1;
|
||||
|
||||
body += `<tr>
|
||||
<td class="py-3 fw-semibold">${r.return_number}</td>
|
||||
|
||||
Reference in New Issue
Block a user