Fix item counts, PR/QT conversions, validation and department loss
This commit is contained in:
+11
-1
@@ -581,9 +581,19 @@ function load_formula_options(select_id, document_type, selected_id, onLoaded) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Line tax rate; derived from tax_amount / total_price when only the amount was stored
|
||||||
|
function line_tax_rate(item) {
|
||||||
|
var rate = parseFloat(item.tax_rate) || 0;
|
||||||
|
var amount = parseFloat(item.tax_amount) || 0;
|
||||||
|
var total = parseFloat(item.total_price) || 0;
|
||||||
|
if (rate === 0 && amount > 0 && total > 0) rate = round_dp(amount / total * 100, 2);
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the request promise so callers can await the options before selecting a value
|
||||||
function load_departments(select_id, selected_id) {
|
function load_departments(select_id, selected_id) {
|
||||||
var ctx = document.getElementById('session-context');
|
var ctx = document.getElementById('session-context');
|
||||||
ajax_request({
|
return ajax_request({
|
||||||
url: server_url + 'accounting/api/engine/department.php',
|
url: server_url + 'accounting/api/engine/department.php',
|
||||||
action: 'get',
|
action: 'get',
|
||||||
queueLock: false,
|
queueLock: false,
|
||||||
|
|||||||
@@ -260,7 +260,10 @@ class OrderManager {
|
|||||||
{
|
{
|
||||||
$sth = $this->pdo->prepare(
|
$sth = $this->pdo->prepare(
|
||||||
"SELECT o.*,
|
"SELECT o.*,
|
||||||
COALESCE(c.contact_name, '') AS contact_name
|
COALESCE(c.contact_name, '') AS contact_name,
|
||||||
|
(SELECT COUNT(*) FROM td_order_item i
|
||||||
|
WHERE i.company_id = o.company_id
|
||||||
|
AND i.order_id = o.id) AS item_count
|
||||||
FROM td_order o
|
FROM td_order o
|
||||||
LEFT JOIN md_contact c
|
LEFT JOIN md_contact c
|
||||||
ON c.company_id = o.company_id
|
ON c.company_id = o.company_id
|
||||||
@@ -493,7 +496,7 @@ class OrderManager {
|
|||||||
|
|
||||||
// Fetch existing row to check status and load log
|
// Fetch existing row to check status and load log
|
||||||
$sth = $this->pdo->prepare(
|
$sth = $this->pdo->prepare(
|
||||||
"SELECT status, `log` FROM td_order
|
"SELECT status, department_id, `log` FROM td_order
|
||||||
WHERE company_id = :company_id AND id = :id"
|
WHERE company_id = :company_id AND id = :id"
|
||||||
);
|
);
|
||||||
$sth->execute([':company_id' => $this->company_id, ':id' => $id]);
|
$sth->execute([':company_id' => $this->company_id, ':id' => $id]);
|
||||||
@@ -502,6 +505,11 @@ class OrderManager {
|
|||||||
if (!$row) {
|
if (!$row) {
|
||||||
throw new Exception("Order not found.");
|
throw new Exception("Order not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pages without a department field (Revenue SO) must not wipe the stored one
|
||||||
|
$department_id = array_key_exists('department_id', $data)
|
||||||
|
? (int)$data['department_id']
|
||||||
|
: (int)$row['department_id'];
|
||||||
$cur_status = (int)$row['status'];
|
$cur_status = (int)$row['status'];
|
||||||
if ($cur_status !== 0 && $cur_status !== -2) {
|
if ($cur_status !== 0 && $cur_status !== -2) {
|
||||||
throw new Exception("Only draft or pending orders can be edited.");
|
throw new Exception("Only draft or pending orders can be edited.");
|
||||||
@@ -541,7 +549,7 @@ class OrderManager {
|
|||||||
WHERE id = :id AND company_id = :company_id"
|
WHERE id = :id AND company_id = :company_id"
|
||||||
)->execute([
|
)->execute([
|
||||||
':contact_id' => (int)($data['contact_id'] ?? 0),
|
':contact_id' => (int)($data['contact_id'] ?? 0),
|
||||||
':department_id' => (int)($data['department_id'] ?? 0),
|
':department_id' => $department_id,
|
||||||
':order_date' => $data['order_date'] ?? date('Y-m-d'),
|
':order_date' => $data['order_date'] ?? date('Y-m-d'),
|
||||||
':subtotal' => $subtotal,
|
':subtotal' => $subtotal,
|
||||||
':discount' => $discount,
|
':discount' => $discount,
|
||||||
|
|||||||
@@ -194,7 +194,10 @@ class PurchaseOrderManager {
|
|||||||
{
|
{
|
||||||
$sth = $this->pdo->prepare(
|
$sth = $this->pdo->prepare(
|
||||||
"SELECT p.*,
|
"SELECT p.*,
|
||||||
COALESCE(c.contact_name, '') AS contact_name
|
COALESCE(c.contact_name, '') AS contact_name,
|
||||||
|
(SELECT COUNT(*) FROM td_purchase_order_item i
|
||||||
|
WHERE i.company_id = p.company_id
|
||||||
|
AND i.order_id = p.id) AS item_count
|
||||||
FROM td_purchase_order p
|
FROM td_purchase_order p
|
||||||
LEFT JOIN md_contact c
|
LEFT JOIN md_contact c
|
||||||
ON c.company_id = p.company_id
|
ON c.company_id = p.company_id
|
||||||
|
|||||||
@@ -94,7 +94,10 @@ class PurchaseRequestManager
|
|||||||
WHERE p.company_id = r.company_id
|
WHERE p.company_id = r.company_id
|
||||||
AND p.source = 'purchase_request'
|
AND p.source = 'purchase_request'
|
||||||
AND p.source_id = r.id
|
AND p.source_id = r.id
|
||||||
AND p.status != -1) AS linked_po_count
|
AND p.status != -1) AS linked_po_count,
|
||||||
|
(SELECT COUNT(*) FROM td_purchase_request_item i
|
||||||
|
WHERE i.company_id = r.company_id
|
||||||
|
AND i.request_id = r.id) AS item_count
|
||||||
FROM td_purchase_request r
|
FROM td_purchase_request r
|
||||||
LEFT JOIN md_contact c
|
LEFT JOIN md_contact c
|
||||||
ON c.company_id = r.company_id AND c.id = r.contact_id
|
ON c.company_id = r.company_id AND c.id = r.contact_id
|
||||||
@@ -166,6 +169,12 @@ class PurchaseRequestManager
|
|||||||
|
|
||||||
if (empty($items)) throw new Exception('At least one item is required.');
|
if (empty($items)) throw new Exception('At least one item is required.');
|
||||||
|
|
||||||
|
$request_date = (string)($data['request_date'] ?? '');
|
||||||
|
$required_date = (string)($data['required_date'] ?? '');
|
||||||
|
if ($request_date !== '' && $required_date !== '' && $required_date < $request_date) {
|
||||||
|
throw new Exception('Required date cannot be earlier than the request date.');
|
||||||
|
}
|
||||||
|
|
||||||
[$subtotal, $tax, $grand] = $this->computeTotals($items, $discount, $shipping_fee);
|
[$subtotal, $tax, $grand] = $this->computeTotals($items, $discount, $shipping_fee);
|
||||||
|
|
||||||
if ($id === 0) {
|
if ($id === 0) {
|
||||||
|
|||||||
@@ -89,7 +89,10 @@ class QuotationManager
|
|||||||
public function getList(): array
|
public function getList(): array
|
||||||
{
|
{
|
||||||
$sth = $this->pdo->prepare(
|
$sth = $this->pdo->prepare(
|
||||||
"SELECT q.*, COALESCE(c.contact_name, '') AS contact_name
|
"SELECT q.*, COALESCE(c.contact_name, '') AS contact_name,
|
||||||
|
(SELECT COUNT(*) FROM td_quotation_item i
|
||||||
|
WHERE i.company_id = q.company_id
|
||||||
|
AND i.quotation_id = q.id) AS item_count
|
||||||
FROM td_quotation q
|
FROM td_quotation q
|
||||||
LEFT JOIN md_contact c
|
LEFT JOIN md_contact c
|
||||||
ON c.id = q.contact_id AND c.company_id = q.company_id
|
ON c.id = q.contact_id AND c.company_id = q.company_id
|
||||||
@@ -173,6 +176,24 @@ class QuotationManager
|
|||||||
$items = $data['items'] ?? [];
|
$items = $data['items'] ?? [];
|
||||||
$discount = (float)($data['discount'] ?? 0);
|
$discount = (float)($data['discount'] ?? 0);
|
||||||
|
|
||||||
|
$quotation_date = (string)($data['quotation_date'] ?? '');
|
||||||
|
$valid_until = (string)($data['valid_until'] ?? '');
|
||||||
|
if ((int)($data['contact_id'] ?? 0) <= 0) {
|
||||||
|
throw new Exception('Contact is required.');
|
||||||
|
}
|
||||||
|
if ($quotation_date === '') {
|
||||||
|
throw new Exception('Quotation date is required.');
|
||||||
|
}
|
||||||
|
if ($valid_until !== '' && $valid_until < $quotation_date) {
|
||||||
|
throw new Exception('Valid until cannot be earlier than the quotation date.');
|
||||||
|
}
|
||||||
|
if ((int)($data['department_id'] ?? 0) <= 0) {
|
||||||
|
throw new Exception('Department is required.');
|
||||||
|
}
|
||||||
|
if (empty($items)) {
|
||||||
|
throw new Exception('At least one line item is required.');
|
||||||
|
}
|
||||||
|
|
||||||
[$subtotal, $tax, $grand] = $this->computeTotals($items, $discount);
|
[$subtotal, $tax, $grand] = $this->computeTotals($items, $discount);
|
||||||
|
|
||||||
if ($id === 0) {
|
if ($id === 0) {
|
||||||
|
|||||||
@@ -169,7 +169,10 @@ class ReturnManager {
|
|||||||
$sth = $this->pdo->prepare(
|
$sth = $this->pdo->prepare(
|
||||||
"SELECT r.*,
|
"SELECT r.*,
|
||||||
COALESCE(c.contact_name, '') AS contact_name,
|
COALESCE(c.contact_name, '') AS contact_name,
|
||||||
o.order_number
|
o.order_number,
|
||||||
|
(SELECT COUNT(*) FROM td_return_item i
|
||||||
|
WHERE i.company_id = r.company_id
|
||||||
|
AND i.return_id = r.id) AS item_count
|
||||||
FROM td_return r
|
FROM td_return r
|
||||||
LEFT JOIN md_contact c
|
LEFT JOIN md_contact c
|
||||||
ON c.company_id = r.company_id
|
ON c.company_id = r.company_id
|
||||||
|
|||||||
@@ -154,7 +154,10 @@ class SupplierReturnManager {
|
|||||||
$sth = $this->pdo->prepare(
|
$sth = $this->pdo->prepare(
|
||||||
"SELECT r.*,
|
"SELECT r.*,
|
||||||
COALESCE(c.contact_name, '') AS contact_name,
|
COALESCE(c.contact_name, '') AS contact_name,
|
||||||
p.po_number
|
p.po_number,
|
||||||
|
(SELECT COUNT(*) FROM td_supplier_return_item i
|
||||||
|
WHERE i.company_id = r.company_id
|
||||||
|
AND i.return_id = r.id) AS item_count
|
||||||
FROM td_supplier_return r
|
FROM td_supplier_return r
|
||||||
LEFT JOIN md_contact c
|
LEFT JOIN md_contact c
|
||||||
ON c.company_id = r.company_id
|
ON c.company_id = r.company_id
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ if (!$request_id) {
|
|||||||
$answer['message'] = 'Purchase request ID is required.';
|
$answer['message'] = 'Purchase request ID is required.';
|
||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
}
|
}
|
||||||
if (!$contact_id) {
|
|
||||||
$answer['message'] = 'Supplier (contact_id) is required for the PO.';
|
|
||||||
exit(json_encode($answer));
|
|
||||||
}
|
|
||||||
|
|
||||||
$prm = new PurchaseRequestManager($pdo2, $company_id);
|
$prm = new PurchaseRequestManager($pdo2, $company_id);
|
||||||
|
|
||||||
@@ -29,6 +25,15 @@ if (!$pr || (int)$pr['status'] !== 2) {
|
|||||||
exit(json_encode($answer));
|
exit(json_encode($answer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default to the PR's preferred supplier
|
||||||
|
if (!$contact_id) {
|
||||||
|
$contact_id = (int)($pr['contact_id'] ?? 0);
|
||||||
|
}
|
||||||
|
if (!$contact_id) {
|
||||||
|
$answer['message'] = 'Set a Preferred Supplier on this purchase request before converting it to a PO.';
|
||||||
|
exit(json_encode($answer));
|
||||||
|
}
|
||||||
|
|
||||||
$pr_items = $pr['items'];
|
$pr_items = $pr['items'];
|
||||||
if (empty($pr_items)) {
|
if (empty($pr_items)) {
|
||||||
$answer['message'] = 'Purchase request has no items.';
|
$answer['message'] = 'Purchase request has no items.';
|
||||||
@@ -92,6 +97,7 @@ foreach ($convert_items as $ci) {
|
|||||||
'unit_price' => $unit_price,
|
'unit_price' => $unit_price,
|
||||||
'total_price' => $total_price,
|
'total_price' => $total_price,
|
||||||
'tax_amount' => $tax_amount,
|
'tax_amount' => $tax_amount,
|
||||||
|
'tax_rate' => (float)($pi['tax_rate'] ?? 0),
|
||||||
'received_qty' => 0,
|
'received_qty' => 0,
|
||||||
'stock_in_id' => 0,
|
'stock_in_id' => 0,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -194,7 +194,7 @@
|
|||||||
<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_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.unit_price || item.price || 0}" min="0" step="any" oninput="recalc_totals()"></td>
|
<td><input type="number" class="form-control form-control-sm item_price" value="${item.unit_price || item.price || 0}" min="0" step="any" oninput="recalc_totals()"></td>
|
||||||
<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="number" class="form-control form-control-sm item_tax_rate" value="${line_tax_rate(item).toFixed(2)}" min="0" max="100" step="0.01" oninput="recalc_totals()">
|
||||||
<input type="hidden" class="item_tax_amount" value="${item.tax_amount || 0}">
|
<input type="hidden" class="item_tax_amount" value="${item.tax_amount || 0}">
|
||||||
</td>
|
</td>
|
||||||
<td class="item_total text-end">${format_number((item.quantity || 1) * (item.unit_price || item.price || 0), 2)}</td>
|
<td class="item_total text-end">${format_number((item.quantity || 1) * (item.unit_price || item.price || 0), 2)}</td>
|
||||||
@@ -371,8 +371,8 @@
|
|||||||
recalc_totals();
|
recalc_totals();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(function() {
|
$(async function() {
|
||||||
load_departments('department_id');
|
await Promise.resolve(load_departments('department_id')).catch(function() {});
|
||||||
flatpickr('#po_date', { dateFormat: 'd/m/Y', allowInput: true });
|
flatpickr('#po_date', { dateFormat: 'd/m/Y', allowInput: true });
|
||||||
flatpickr('#expected_date', { dateFormat: 'd/m/Y', allowInput: true });
|
flatpickr('#expected_date', { dateFormat: 'd/m/Y', allowInput: true });
|
||||||
if (po_id) {
|
if (po_id) {
|
||||||
|
|||||||
@@ -370,8 +370,9 @@
|
|||||||
recalc_totals();
|
recalc_totals();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(function() {
|
$(async function() {
|
||||||
load_departments('department_id');
|
// Options must exist before retrieve_request() selects the saved department
|
||||||
|
await Promise.resolve(load_departments('department_id')).catch(function() {});
|
||||||
flatpickr('#request_date', { dateFormat: 'd/m/Y', allowInput: true });
|
flatpickr('#request_date', { dateFormat: 'd/m/Y', allowInput: true });
|
||||||
flatpickr('#required_date', { dateFormat: 'd/m/Y', allowInput: true });
|
flatpickr('#required_date', { dateFormat: 'd/m/Y', allowInput: true });
|
||||||
if (request_id) {
|
if (request_id) {
|
||||||
|
|||||||
@@ -180,7 +180,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$.each(rows, function(i, r) {
|
$.each(rows, function(i, r) {
|
||||||
var items = typeof r.items === 'string' ? JSON.parse(r.items || '[]') : (r.items || []);
|
var item_count = parseInt(r.item_count) || 0;
|
||||||
var po_link = r.po_id > 0
|
var po_link = r.po_id > 0
|
||||||
? `<a href="<?php echo $server_url?>expense/manage_purchase_order.php?id=${r.po_id}">${escape_html(r.po_number || ('PO #' + r.po_id))}</a>`
|
? `<a href="<?php echo $server_url?>expense/manage_purchase_order.php?id=${r.po_id}">${escape_html(r.po_number || ('PO #' + r.po_id))}</a>`
|
||||||
: '<span class="text-muted">—</span>';
|
: '<span class="text-muted">—</span>';
|
||||||
@@ -190,7 +190,7 @@
|
|||||||
<td class="py-3">${r.required_date ? format_date(r.required_date) : '<span class="text-muted">—</span>'}</td>
|
<td class="py-3">${r.required_date ? format_date(r.required_date) : '<span class="text-muted">—</span>'}</td>
|
||||||
<td class="py-3">${escape_html(r.contact_name || '—')}</td>
|
<td class="py-3">${escape_html(r.contact_name || '—')}</td>
|
||||||
<td class="py-3">${get_dept_label(r.department_id)}</td>
|
<td class="py-3">${get_dept_label(r.department_id)}</td>
|
||||||
<td class="py-3">${items.length} item${items.length !== 1 ? 's' : ''}</td>
|
<td class="py-3">${item_count} item${item_count !== 1 ? 's' : ''}</td>
|
||||||
<td class="py-3 fw-semibold">${format_number(r.grand_total, 2)}</td>
|
<td class="py-3 fw-semibold">${format_number(r.grand_total, 2)}</td>
|
||||||
<td class="py-3">${status_badge[String(r.status)] || r.status}</td>
|
<td class="py-3">${status_badge[String(r.status)] || r.status}</td>
|
||||||
<td class="py-3">${po_link}</td>
|
<td class="py-3">${po_link}</td>
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ header('Referrer-Policy: strict-origin-when-cross-origin');
|
|||||||
<link rel="stylesheet" href="<?php echo $server_url?>assets/css/main.css">
|
<link rel="stylesheet" href="<?php echo $server_url?>assets/css/main.css">
|
||||||
<script type="module" src="<?php echo $server_url?>assets/js/main.js"></script>
|
<script type="module" src="<?php echo $server_url?>assets/js/main.js"></script>
|
||||||
<link rel="stylesheet" href="<?php echo $server_url?>assets/css/custom.css">
|
<link rel="stylesheet" href="<?php echo $server_url?>assets/css/custom.css">
|
||||||
<script src="<?php echo $server_url?>assets/js/custom.js"></script>
|
<script src="<?php echo $server_url?>assets/js/custom.js?v=<?php echo @filemtime(__DIR__ . '/assets/js/custom.js'); ?>"></script>
|
||||||
<script src="<?php echo $server_url?>assets/js/batch_overlay.js"></script>
|
<script src="<?php echo $server_url?>assets/js/batch_overlay.js"></script>
|
||||||
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5-qrcode/2.3.8/html5-qrcode.min.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5-qrcode/2.3.8/html5-qrcode.min.js"
|
||||||
|
|||||||
@@ -341,7 +341,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="number" class="form-control form-control-sm item_tax_rate"
|
<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()">
|
value="${line_tax_rate(item).toFixed(2)}" min="0" max="100" step="0.01" oninput="recalc_totals()">
|
||||||
<input type="hidden" class="item_tax_amount" value="${item.tax_amount || 0}">
|
<input type="hidden" class="item_tax_amount" value="${item.tax_amount || 0}">
|
||||||
</td>
|
</td>
|
||||||
<td class="item_total text-end">${format_number((item.quantity || 1) * (item.unit_price || item.price || 0), 2)}</td>
|
<td class="item_total text-end">${format_number((item.quantity || 1) * (item.unit_price || item.price || 0), 2)}</td>
|
||||||
@@ -712,7 +712,7 @@
|
|||||||
// ── Boot ─────────────────────────────────────────────────────────────────
|
// ── Boot ─────────────────────────────────────────────────────────────────
|
||||||
$(async function() {
|
$(async function() {
|
||||||
try {
|
try {
|
||||||
load_departments('department_id');
|
await Promise.resolve(load_departments('department_id')).catch(function() {});
|
||||||
await retrieve_warehouses();
|
await retrieve_warehouses();
|
||||||
|
|
||||||
if (order_id) {
|
if (order_id) {
|
||||||
|
|||||||
+1
-2
@@ -241,8 +241,7 @@
|
|||||||
var body = '';
|
var body = '';
|
||||||
|
|
||||||
$.each(page_data, function(i, o) {
|
$.each(page_data, function(i, o) {
|
||||||
var items = JSON.parse(o.items || '[]');
|
var item_count = parseInt(o.item_count) || 0;
|
||||||
var item_count = items.length;
|
|
||||||
var can_edit = parseInt(o.status) === 0;
|
var can_edit = parseInt(o.status) === 0;
|
||||||
var can_cancel = parseInt(o.status) >= 0 && parseInt(o.status) <= 1;
|
var can_cancel = parseInt(o.status) >= 0 && parseInt(o.status) <= 1;
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@
|
|||||||
var body = '';
|
var body = '';
|
||||||
|
|
||||||
$.each(page_data, function(i, r) {
|
$.each(page_data, function(i, r) {
|
||||||
var items = JSON.parse(r.items || '[]');
|
var item_count = parseInt(r.item_count) || 0;
|
||||||
var can_cancel = parseInt(r.status) >= 0 && parseInt(r.status) <= 1;
|
var can_cancel = parseInt(r.status) >= 0 && parseInt(r.status) <= 1;
|
||||||
|
|
||||||
body += `<tr>
|
body += `<tr>
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
<td class="py-3">${r.contact_name || '<span class="text-muted">—</span>'}</td>
|
<td class="py-3">${r.contact_name || '<span class="text-muted">—</span>'}</td>
|
||||||
<td class="py-3">${get_dept_label(r.department_id)}</td>
|
<td class="py-3">${get_dept_label(r.department_id)}</td>
|
||||||
<td class="py-3">${format_date(r.return_date)}</td>
|
<td class="py-3">${format_date(r.return_date)}</td>
|
||||||
<td class="py-3">${items.length} item${items.length !== 1 ? 's' : ''}</td>
|
<td class="py-3">${item_count} item${item_count !== 1 ? 's' : ''}</td>
|
||||||
<td class="py-3 fw-semibold">${format_number(r.refund_amount, 2)}</td>
|
<td class="py-3 fw-semibold">${format_number(r.refund_amount, 2)}</td>
|
||||||
<td class="py-3">${return_status_badge(r.status)}</td>
|
<td class="py-3">${return_status_badge(r.status)}</td>
|
||||||
<td class="py-3">${receipt_status_badge(r.receipt_status)}</td>
|
<td class="py-3">${receipt_status_badge(r.receipt_status)}</td>
|
||||||
|
|||||||
@@ -326,7 +326,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="number" class="form-control form-control-sm item_tax_rate"
|
<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()">
|
value="${line_tax_rate(item).toFixed(2)}" min="0" max="100" step="0.01" oninput="recalc_totals()">
|
||||||
<input type="hidden" class="item_tax_amount" value="${item.tax_amount || 0}">
|
<input type="hidden" class="item_tax_amount" value="${item.tax_amount || 0}">
|
||||||
</td>
|
</td>
|
||||||
<td class="item_total text-end">${format_number((item.quantity || 1) * (item.unit_price || 0), 2)}</td>
|
<td class="item_total text-end">${format_number((item.quantity || 1) * (item.unit_price || 0), 2)}</td>
|
||||||
@@ -974,7 +974,7 @@
|
|||||||
// ── Boot ─────────────────────────────────────────────────────────────────
|
// ── Boot ─────────────────────────────────────────────────────────────────
|
||||||
$(async function() {
|
$(async function() {
|
||||||
try {
|
try {
|
||||||
load_departments('department_id');
|
await Promise.resolve(load_departments('department_id')).catch(function() {});
|
||||||
await load_location_config();
|
await load_location_config();
|
||||||
await retrieve_warehouses();
|
await retrieve_warehouses();
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -226,8 +226,7 @@
|
|||||||
var body = '';
|
var body = '';
|
||||||
|
|
||||||
$.each(page_data, function(i, o) {
|
$.each(page_data, function(i, o) {
|
||||||
var items = JSON.parse(o.items || '[]');
|
var item_count = parseInt(o.item_count) || 0;
|
||||||
var item_count = items.length;
|
|
||||||
var can_cancel = parseInt(o.status) >= -2 && parseInt(o.status) <= 1;
|
var can_cancel = parseInt(o.status) >= -2 && parseInt(o.status) <= 1;
|
||||||
|
|
||||||
body += `<tr>
|
body += `<tr>
|
||||||
|
|||||||
@@ -218,9 +218,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$.each(rows, function(i, r) {
|
$.each(rows, function(i, r) {
|
||||||
var items = [];
|
var item_count = parseInt(r.item_count) || 0;
|
||||||
try { items = JSON.parse(r.items || '[]'); } catch(e) {}
|
|
||||||
var item_count = items.length;
|
|
||||||
body += `<tr>
|
body += `<tr>
|
||||||
<td class="py-3 fw-semibold">${escape_html(r.return_number || '')}</td>
|
<td class="py-3 fw-semibold">${escape_html(r.return_number || '')}</td>
|
||||||
<td class="py-3">
|
<td class="py-3">
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ foreach ($convert_items as $ci) {
|
|||||||
'unit_price' => $unit_price,
|
'unit_price' => $unit_price,
|
||||||
'total_price' => $total_price,
|
'total_price' => $total_price,
|
||||||
'tax_amount' => $tax_amount,
|
'tax_amount' => $tax_amount,
|
||||||
|
'tax_rate' => (float)($qi['tax_rate'] ?? 0),
|
||||||
'stock_out_id' => 0,
|
'stock_out_id' => 0,
|
||||||
];
|
];
|
||||||
$validated[] = ['item_id' => $item_id, 'quantity' => $qty];
|
$validated[] = ['item_id' => $item_id, 'quantity' => $qty];
|
||||||
|
|||||||
@@ -235,7 +235,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="number" class="form-control form-control-sm item_tax_rate"
|
<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()">
|
value="${line_tax_rate(item).toFixed(2)}" min="0" max="100" step="0.01" oninput="recalc_totals()">
|
||||||
<input type="hidden" class="item_tax_amount" value="${item.tax_amount || 0}">
|
<input type="hidden" class="item_tax_amount" value="${item.tax_amount || 0}">
|
||||||
</td>
|
</td>
|
||||||
<td class="item_total text-end align-middle">
|
<td class="item_total text-end align-middle">
|
||||||
|
|||||||
@@ -515,7 +515,7 @@
|
|||||||
// ── Boot ──────────────────────────────────────────────────────────────────
|
// ── Boot ──────────────────────────────────────────────────────────────────
|
||||||
$(async function() {
|
$(async function() {
|
||||||
try {
|
try {
|
||||||
load_departments('department_id');
|
await Promise.resolve(load_departments('department_id')).catch(function() {});
|
||||||
if (quotation_id) {
|
if (quotation_id) {
|
||||||
await retrieve_quotation();
|
await retrieve_quotation();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -165,12 +165,6 @@
|
|||||||
return map[String(status)] || '<span class="badge bg-light text-dark">—</span>';
|
return map[String(status)] || '<span class="badge bg-light text-dark">—</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
function order_items_count(items) {
|
|
||||||
if (Array.isArray(items)) return items.length;
|
|
||||||
try { return JSON.parse(items || '[]').length; }
|
|
||||||
catch(e) { return 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
function retrieve_orders() {
|
function retrieve_orders() {
|
||||||
return ajax_request({
|
return ajax_request({
|
||||||
url: '<?php echo $server_url?>order/api/engine/retrieve_order.php',
|
url: '<?php echo $server_url?>order/api/engine/retrieve_order.php',
|
||||||
@@ -226,7 +220,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$.each(rows, function(i, o) {
|
$.each(rows, function(i, o) {
|
||||||
var item_count = order_items_count(o.items);
|
var item_count = parseInt(o.item_count) || 0;
|
||||||
var source = String(o.source || '');
|
var source = String(o.source || '');
|
||||||
var source_display = source === 'quotation' && parseInt(o.source_id) > 0
|
var source_display = source === 'quotation' && parseInt(o.source_id) > 0
|
||||||
? `<a href="<?php echo $server_url?>revenue/manage_quotation.php?id=${o.source_id}">Quotation #${o.source_id}</a>`
|
? `<a href="<?php echo $server_url?>revenue/manage_quotation.php?id=${o.source_id}">Quotation #${o.source_id}</a>`
|
||||||
|
|||||||
@@ -212,14 +212,14 @@
|
|||||||
var body = '';
|
var body = '';
|
||||||
|
|
||||||
$.each(page_data, function(i, q) {
|
$.each(page_data, function(i, q) {
|
||||||
var items = typeof q.items === 'string' ? JSON.parse(q.items || '[]') : (q.items || []);
|
var item_count = parseInt(q.item_count) || 0;
|
||||||
body += `<tr>
|
body += `<tr>
|
||||||
<td class="py-3 fw-semibold">${escape_html(q.quotation_number)}</td>
|
<td class="py-3 fw-semibold">${escape_html(q.quotation_number)}</td>
|
||||||
<td class="py-3">${format_date(q.quotation_date)}</td>
|
<td class="py-3">${format_date(q.quotation_date)}</td>
|
||||||
<td class="py-3">${q.valid_until ? format_date(q.valid_until) : '<span class="text-muted">—</span>'}</td>
|
<td class="py-3">${q.valid_until ? format_date(q.valid_until) : '<span class="text-muted">—</span>'}</td>
|
||||||
<td class="py-3">${escape_html(q.contact_name || '—')}</td>
|
<td class="py-3">${escape_html(q.contact_name || '—')}</td>
|
||||||
<td class="py-3">${get_dept_label(q.department_id)}</td>
|
<td class="py-3">${get_dept_label(q.department_id)}</td>
|
||||||
<td class="py-3">${items.length} item${items.length !== 1 ? 's' : ''}</td>
|
<td class="py-3">${item_count} item${item_count !== 1 ? 's' : ''}</td>
|
||||||
<td class="py-3 fw-semibold">${format_number(q.grand_total, 2)}</td>
|
<td class="py-3 fw-semibold">${format_number(q.grand_total, 2)}</td>
|
||||||
<td class="py-3">${status_badge[String(q.status)] || q.status}</td>
|
<td class="py-3">${status_badge[String(q.status)] || q.status}</td>
|
||||||
<td class="py-3">
|
<td class="py-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user