auto calculate
This commit is contained in:
@@ -68,33 +68,42 @@ class AdminOrderController extends Controller
|
||||
* ORDER ITEM MANAGEMENT (existing orders)
|
||||
* ---------------------------*/
|
||||
public function addItem(Request $request, $orderId)
|
||||
{
|
||||
$order = Order::findOrFail($orderId);
|
||||
{
|
||||
$order = Order::findOrFail($orderId);
|
||||
|
||||
$data = $request->validate([
|
||||
'description' => 'required|string',
|
||||
'ctn' => 'nullable|numeric',
|
||||
'qty' => 'nullable|numeric',
|
||||
'ttl_qty' => 'nullable|numeric',
|
||||
'unit' => 'nullable|string',
|
||||
'price' => 'nullable|numeric',
|
||||
'ttl_amount' => 'nullable|numeric',
|
||||
'cbm' => 'nullable|numeric',
|
||||
'ttl_cbm' => 'nullable|numeric',
|
||||
'kg' => 'nullable|numeric',
|
||||
'ttl_kg' => 'nullable|numeric',
|
||||
'shop_no' => 'nullable|string',
|
||||
]);
|
||||
$data = $request->validate([
|
||||
'description' => 'required|string',
|
||||
'ctn' => 'nullable|numeric',
|
||||
'qty' => 'nullable|numeric',
|
||||
'unit' => 'nullable|string',
|
||||
'price' => 'nullable|numeric',
|
||||
'cbm' => 'nullable|numeric',
|
||||
'kg' => 'nullable|numeric',
|
||||
'shop_no' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$data['order_id'] = $order->id;
|
||||
// ✅ BACKEND CALCULATION
|
||||
$ctn = (float) ($data['ctn'] ?? 0);
|
||||
$qty = (float) ($data['qty'] ?? 0);
|
||||
$price = (float) ($data['price'] ?? 0);
|
||||
$cbm = (float) ($data['cbm'] ?? 0);
|
||||
$kg = (float) ($data['kg'] ?? 0);
|
||||
|
||||
OrderItem::create($data);
|
||||
$data['ttl_qty'] = $ctn * $qty;
|
||||
$data['ttl_amount'] = $data['ttl_qty'] * $price;
|
||||
$data['ttl_cbm'] = $cbm * $ctn;
|
||||
$data['ttl_kg'] = $ctn * $kg;
|
||||
|
||||
$this->recalcTotals($order);
|
||||
$this->updateInvoiceFromOrder($order);
|
||||
$data['order_id'] = $order->id;
|
||||
|
||||
OrderItem::create($data);
|
||||
|
||||
$this->recalcTotals($order);
|
||||
$this->updateInvoiceFromOrder($order);
|
||||
|
||||
return redirect()->back()->with('success', 'Item added and totals updated.');
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', 'Item added and totals updated.');
|
||||
}
|
||||
|
||||
public function deleteItem($id)
|
||||
{
|
||||
@@ -500,14 +509,14 @@ class AdminOrderController extends Controller
|
||||
'items.*.description' => 'required|string',
|
||||
'items.*.ctn' => 'nullable|numeric',
|
||||
'items.*.qty' => 'nullable|numeric',
|
||||
'items.*.ttl_qty' => 'nullable|numeric',
|
||||
|
||||
'items.*.unit' => 'nullable|string',
|
||||
'items.*.price' => 'nullable|numeric',
|
||||
'items.*.ttl_amount' => 'nullable|numeric',
|
||||
|
||||
'items.*.cbm' => 'nullable|numeric',
|
||||
'items.*.ttl_cbm' => 'nullable|numeric',
|
||||
|
||||
'items.*.kg' => 'nullable|numeric',
|
||||
'items.*.ttl_kg' => 'nullable|numeric',
|
||||
|
||||
'items.*.shop_no' => 'nullable|string',
|
||||
])['items'];
|
||||
|
||||
@@ -520,6 +529,24 @@ class AdminOrderController extends Controller
|
||||
return back()->with('error', 'Add at least one item.');
|
||||
}
|
||||
|
||||
// ✅ BACKEND CALCULATION (DO NOT TRUST FRONTEND)
|
||||
foreach ($items as &$item) {
|
||||
|
||||
$ctn = (float) ($item['ctn'] ?? 0);
|
||||
$qty = (float) ($item['qty'] ?? 0);
|
||||
$price = (float) ($item['price'] ?? 0);
|
||||
$cbm = (float) ($item['cbm'] ?? 0);
|
||||
$kg = (float) ($item['kg'] ?? 0);
|
||||
|
||||
// Calculated fields
|
||||
$item['ttl_qty'] = $ctn * $qty;
|
||||
$item['ttl_amount'] = $item['ttl_qty'] * $price;
|
||||
$item['ttl_cbm'] = $cbm * $ctn;
|
||||
$item['ttl_kg'] = $ctn * $kg;
|
||||
}
|
||||
unset($item); // VERY IMPORTANT
|
||||
|
||||
|
||||
// 3) totals
|
||||
$total_ctn = array_sum(array_column($items, 'ctn'));
|
||||
$total_qty = array_sum(array_column($items, 'qty'));
|
||||
@@ -631,30 +658,49 @@ class AdminOrderController extends Controller
|
||||
* UPDATE ORDER ITEM (existing orders)
|
||||
* ---------------------------*/
|
||||
public function updateItem(Request $request, $id)
|
||||
{
|
||||
$item = OrderItem::findOrFail($id);
|
||||
$order = $item->order;
|
||||
{
|
||||
$item = OrderItem::findOrFail($id);
|
||||
$order = $item->order;
|
||||
|
||||
$item->update([
|
||||
'description' => $request->description,
|
||||
'ctn' => $request->ctn,
|
||||
'qty' => $request->qty,
|
||||
'ttl_qty' => $request->ttl_qty,
|
||||
'unit' => $request->unit,
|
||||
'price' => $request->price,
|
||||
'ttl_amount' => $request->ttl_amount,
|
||||
'cbm' => $request->cbm,
|
||||
'ttl_cbm' => $request->ttl_cbm,
|
||||
'kg' => $request->kg,
|
||||
'ttl_kg' => $request->ttl_kg,
|
||||
'shop_no' => $request->shop_no,
|
||||
]);
|
||||
$request->validate([
|
||||
'description' => 'required|string',
|
||||
'ctn' => 'nullable|numeric',
|
||||
'qty' => 'nullable|numeric',
|
||||
'unit' => 'nullable|string',
|
||||
'price' => 'nullable|numeric',
|
||||
'cbm' => 'nullable|numeric',
|
||||
'kg' => 'nullable|numeric',
|
||||
'shop_no' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$this->recalcTotals($order);
|
||||
$this->updateInvoiceFromOrder($order);
|
||||
// ✅ BACKEND CALCULATION
|
||||
$ctn = (float) ($request->ctn ?? 0);
|
||||
$qty = (float) ($request->qty ?? 0);
|
||||
$price = (float) ($request->price ?? 0);
|
||||
$cbm = (float) ($request->cbm ?? 0);
|
||||
$kg = (float) ($request->kg ?? 0);
|
||||
|
||||
$item->update([
|
||||
'description' => $request->description,
|
||||
'ctn' => $ctn,
|
||||
'qty' => $qty,
|
||||
'ttl_qty' => $ctn * $qty,
|
||||
'unit' => $request->unit,
|
||||
'price' => $price,
|
||||
'ttl_amount' => ($ctn * $qty) * $price,
|
||||
'cbm' => $cbm,
|
||||
'ttl_cbm' => $cbm * $ctn,
|
||||
'kg' => $kg,
|
||||
'ttl_kg' => $ctn * $kg,
|
||||
'shop_no' => $request->shop_no,
|
||||
]);
|
||||
|
||||
$this->recalcTotals($order);
|
||||
$this->updateInvoiceFromOrder($order);
|
||||
|
||||
return back()->with('success', 'Item updated successfully');
|
||||
}
|
||||
|
||||
return back()->with('success', 'Item updated successfully');
|
||||
}
|
||||
|
||||
private function updateInvoiceFromOrder(Order $order)
|
||||
{
|
||||
|
||||
BIN
public/invoices/invoice-INV-2025-000048.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000048.pdf
Normal file
Binary file not shown.
@@ -1569,24 +1569,98 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
function addRow(index) {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td class="align-middle fw-bold">${index + 1}</td>
|
||||
<td><input type="text" class="form-control form-control-sm items-input" name="items[${index}][description]" data-field="description"></td>
|
||||
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ctn]" data-field="ctn"></td>
|
||||
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][qty]" data-field="qty"></td>
|
||||
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_qty]" data-field="ttl_qty"></td>
|
||||
<td><input type="text" class="form-control form-control-sm items-input" name="items[${index}][unit]" data-field="unit"></td>
|
||||
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][price]" data-field="price" step="0.01"></td>
|
||||
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_amount]" data-field="ttl_amount" step="0.01"></td>
|
||||
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][cbm]" data-field="cbm" step="0.001"></td>
|
||||
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_cbm]" data-field="ttl_cbm" step="0.001"></td>
|
||||
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][kg]" data-field="kg" step="0.001"></td>
|
||||
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_kg]" data-field="ttl_kg" step="0.001"></td>
|
||||
<td><input type="text" class="form-control form-control-sm items-input" name="items[${index}][shop_no]" data-field="shop_no"></td>
|
||||
<td><button type="button" class="btn btn-sm btn-danger remove-row-btn">×</button></td>
|
||||
`;
|
||||
<td class="align-middle fw-bold">${index + 1}</td>
|
||||
|
||||
<td>
|
||||
<input type="text" class="form-control form-control-sm items-input"name="items[${index}][description]"data-field="description">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm items-input"name="items[${index}][ctn]"data-field="ctn">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm items-input"name="items[${index}][qty]"data-field="qty">
|
||||
</td>
|
||||
|
||||
<!-- 🔒 AUTO: TTL/QTY = CTN * QTY -->
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm bg-light"name="items[${index}][ttl_qty]"data-field="ttl_qty"readonly>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="text" class="form-control form-control-sm items-input"name="items[${index}][unit]"data-field="unit">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm items-input"name="items[${index}][price]"data-field="price"step="0.01">
|
||||
</td>
|
||||
|
||||
<!-- 🔒 AUTO: TTL AMOUNT = TTL/QTY * PRICE -->
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm bg-light"name="items[${index}][ttl_amount]"data-field="ttl_amount"step="0.001"readonly>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm items-input"name="items[${index}][cbm]"data-field="cbm"step="0.0001">
|
||||
</td>
|
||||
|
||||
<!-- 🔒 AUTO: TTL CBM = CBM * QTY -->
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm bg-light"name="items[${index}][ttl_cbm]"data-field="ttl_cbm"step="0.0001"readonly>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm items-input"name="items[${index}][kg]"data-field="kg"step="0.0001">
|
||||
</td>
|
||||
|
||||
<!-- 🔒 AUTO: TTL KG = CTN * KG -->
|
||||
<td>
|
||||
<input type="number" class="form-control form-control-sm bg-light"name="items[${index}][ttl_kg]"data-field="ttl_kg"step="0.0001"readonly>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<input type="text" class="form-control form-control-sm items-input"name="items[${index}][shop_no]"data-field="shop_no">
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-danger remove-row-btn">×</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
itemsTableBody.appendChild(tr);
|
||||
}
|
||||
|
||||
function calculateRow(row) {
|
||||
const ctn = parseFloat(row.querySelector('[data-field="ctn"]')?.value) || 0;
|
||||
const qty = parseFloat(row.querySelector('[data-field="qty"]')?.value) || 0;
|
||||
const price = parseFloat(row.querySelector('[data-field="price"]')?.value) || 0;
|
||||
const cbm = parseFloat(row.querySelector('[data-field="cbm"]')?.value) || 0;
|
||||
const kg = parseFloat(row.querySelector('[data-field="kg"]')?.value) || 0;
|
||||
|
||||
const ttlQty = ctn * qty;
|
||||
const ttlAmount = ttlQty * price;
|
||||
const ttlCbm = cbm * ctn;
|
||||
const ttlKg = ctn * kg;
|
||||
|
||||
row.querySelector('[data-field="ttl_qty"]').value = ttlQty.toFixed(2);
|
||||
row.querySelector('[data-field="ttl_amount"]').value = ttlAmount.toFixed(2);
|
||||
row.querySelector('[data-field="ttl_cbm"]').value = ttlCbm.toFixed(3);
|
||||
row.querySelector('[data-field="ttl_kg"]').value = ttlKg.toFixed(3);
|
||||
}
|
||||
|
||||
itemsTableBody.addEventListener('input', function (e) {
|
||||
const row = e.target.closest('tr');
|
||||
if (!row) return;
|
||||
|
||||
const calcFields = ['ctn', 'qty', 'price', 'cbm', 'kg'];
|
||||
if (calcFields.includes(e.target.dataset.field)) {
|
||||
calculateRow(row);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
function generateDefaultRows() {
|
||||
itemsTableBody.innerHTML = '';
|
||||
addRow(0);
|
||||
@@ -1971,29 +2045,29 @@ if (uploadExcelBtn && excelInput) {
|
||||
|
||||
}
|
||||
|
||||
function populateItemsTable(items) {
|
||||
itemsTableBody.innerHTML = '';
|
||||
function populateItemsTable(items) {
|
||||
itemsTableBody.innerHTML = '';
|
||||
|
||||
items.forEach((item, index) => {
|
||||
addRow(index);
|
||||
const row = itemsTableBody.children[index];
|
||||
items.forEach((item, index) => {
|
||||
addRow(index);
|
||||
const row = itemsTableBody.children[index];
|
||||
|
||||
row.querySelector('[data-field="description"]').value = item.description ?? '';
|
||||
row.querySelector('[data-field="ctn"]').value = item.ctn ?? '';
|
||||
row.querySelector('[data-field="qty"]').value = item.qty ?? '';
|
||||
row.querySelector('[data-field="ttl_qty"]').value = item.ttl_qty ?? '';
|
||||
row.querySelector('[data-field="unit"]').value = item.unit ?? '';
|
||||
row.querySelector('[data-field="price"]').value = item.price ?? '';
|
||||
row.querySelector('[data-field="ttl_amount"]').value = item.ttl_amount ?? '';
|
||||
row.querySelector('[data-field="cbm"]').value = item.cbm ?? '';
|
||||
row.querySelector('[data-field="ttl_cbm"]').value = item.ttl_cbm ?? '';
|
||||
row.querySelector('[data-field="kg"]').value = item.kg ?? '';
|
||||
row.querySelector('[data-field="ttl_kg"]').value = item.ttl_kg ?? '';
|
||||
row.querySelector('[data-field="shop_no"]').value = item.shop_no ?? '';
|
||||
});
|
||||
row.querySelector('[data-field="description"]').value = item.description ?? '';
|
||||
row.querySelector('[data-field="ctn"]').value = item.ctn ?? 0;
|
||||
row.querySelector('[data-field="qty"]').value = item.qty ?? 0;
|
||||
row.querySelector('[data-field="unit"]').value = item.unit ?? '';
|
||||
row.querySelector('[data-field="price"]').value= item.price ?? 0;
|
||||
row.querySelector('[data-field="cbm"]').value = item.cbm ?? 0;
|
||||
row.querySelector('[data-field="kg"]').value = item.kg ?? 0;
|
||||
row.querySelector('[data-field="shop_no"]').value = item.shop_no ?? '';
|
||||
|
||||
// 🔥 ALWAYS RECALCULATE
|
||||
calculateRow(row);
|
||||
});
|
||||
|
||||
reindexRows();
|
||||
}
|
||||
|
||||
reindexRows();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user