auto calculate

This commit is contained in:
Abhishek Mali
2025-12-23 14:15:03 +05:30
parent 6ccf2cf84e
commit e872b83ea3
3 changed files with 202 additions and 82 deletions

View File

@@ -68,33 +68,42 @@ class AdminOrderController extends Controller
* ORDER ITEM MANAGEMENT (existing orders) * ORDER ITEM MANAGEMENT (existing orders)
* ---------------------------*/ * ---------------------------*/
public function addItem(Request $request, $orderId) public function addItem(Request $request, $orderId)
{ {
$order = Order::findOrFail($orderId); $order = Order::findOrFail($orderId);
$data = $request->validate([ $data = $request->validate([
'description' => 'required|string', 'description' => 'required|string',
'ctn' => 'nullable|numeric', 'ctn' => 'nullable|numeric',
'qty' => 'nullable|numeric', 'qty' => 'nullable|numeric',
'ttl_qty' => 'nullable|numeric', 'unit' => 'nullable|string',
'unit' => 'nullable|string', 'price' => 'nullable|numeric',
'price' => 'nullable|numeric', 'cbm' => 'nullable|numeric',
'ttl_amount' => 'nullable|numeric', 'kg' => 'nullable|numeric',
'cbm' => 'nullable|numeric', 'shop_no' => 'nullable|string',
'ttl_cbm' => 'nullable|numeric', ]);
'kg' => 'nullable|numeric',
'ttl_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); $data['order_id'] = $order->id;
$this->updateInvoiceFromOrder($order);
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) public function deleteItem($id)
{ {
@@ -500,14 +509,14 @@ class AdminOrderController extends Controller
'items.*.description' => 'required|string', 'items.*.description' => 'required|string',
'items.*.ctn' => 'nullable|numeric', 'items.*.ctn' => 'nullable|numeric',
'items.*.qty' => 'nullable|numeric', 'items.*.qty' => 'nullable|numeric',
'items.*.ttl_qty' => 'nullable|numeric',
'items.*.unit' => 'nullable|string', 'items.*.unit' => 'nullable|string',
'items.*.price' => 'nullable|numeric', 'items.*.price' => 'nullable|numeric',
'items.*.ttl_amount' => 'nullable|numeric',
'items.*.cbm' => 'nullable|numeric', 'items.*.cbm' => 'nullable|numeric',
'items.*.ttl_cbm' => 'nullable|numeric',
'items.*.kg' => 'nullable|numeric', 'items.*.kg' => 'nullable|numeric',
'items.*.ttl_kg' => 'nullable|numeric',
'items.*.shop_no' => 'nullable|string', 'items.*.shop_no' => 'nullable|string',
])['items']; ])['items'];
@@ -520,6 +529,24 @@ class AdminOrderController extends Controller
return back()->with('error', 'Add at least one item.'); 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 // 3) totals
$total_ctn = array_sum(array_column($items, 'ctn')); $total_ctn = array_sum(array_column($items, 'ctn'));
$total_qty = array_sum(array_column($items, 'qty')); $total_qty = array_sum(array_column($items, 'qty'));
@@ -631,30 +658,49 @@ class AdminOrderController extends Controller
* UPDATE ORDER ITEM (existing orders) * UPDATE ORDER ITEM (existing orders)
* ---------------------------*/ * ---------------------------*/
public function updateItem(Request $request, $id) public function updateItem(Request $request, $id)
{ {
$item = OrderItem::findOrFail($id); $item = OrderItem::findOrFail($id);
$order = $item->order; $order = $item->order;
$item->update([ $request->validate([
'description' => $request->description, 'description' => 'required|string',
'ctn' => $request->ctn, 'ctn' => 'nullable|numeric',
'qty' => $request->qty, 'qty' => 'nullable|numeric',
'ttl_qty' => $request->ttl_qty, 'unit' => 'nullable|string',
'unit' => $request->unit, 'price' => 'nullable|numeric',
'price' => $request->price, 'cbm' => 'nullable|numeric',
'ttl_amount' => $request->ttl_amount, 'kg' => 'nullable|numeric',
'cbm' => $request->cbm, 'shop_no' => 'nullable|string',
'ttl_cbm' => $request->ttl_cbm, ]);
'kg' => $request->kg,
'ttl_kg' => $request->ttl_kg,
'shop_no' => $request->shop_no,
]);
$this->recalcTotals($order); // ✅ BACKEND CALCULATION
$this->updateInvoiceFromOrder($order); $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) private function updateInvoiceFromOrder(Order $order)
{ {

Binary file not shown.

View File

@@ -1569,24 +1569,98 @@ document.addEventListener('DOMContentLoaded', function() {
function addRow(index) { function addRow(index) {
const tr = document.createElement('tr'); const tr = document.createElement('tr');
tr.innerHTML = ` tr.innerHTML = `
<td class="align-middle fw-bold">${index + 1}</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>
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][qty]" data-field="qty"></td> <input type="text" class="form-control form-control-sm items-input"name="items[${index}][description]"data-field="description">
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_qty]" data-field="ttl_qty"></td> </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>
<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> <input type="number" class="form-control form-control-sm items-input"name="items[${index}][ctn]"data-field="ctn">
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][cbm]" data-field="cbm" step="0.001"></td> </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>
<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> <input type="number" class="form-control form-control-sm items-input"name="items[${index}][qty]"data-field="qty">
<td><input type="text" class="form-control form-control-sm items-input" name="items[${index}][shop_no]" data-field="shop_no"></td> </td>
<td><button type="button" class="btn btn-sm btn-danger remove-row-btn">&times;</button></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">&times;</button>
</td>
`;
itemsTableBody.appendChild(tr); 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() { function generateDefaultRows() {
itemsTableBody.innerHTML = ''; itemsTableBody.innerHTML = '';
addRow(0); addRow(0);
@@ -1971,29 +2045,29 @@ if (uploadExcelBtn && excelInput) {
} }
function populateItemsTable(items) { function populateItemsTable(items) {
itemsTableBody.innerHTML = ''; itemsTableBody.innerHTML = '';
items.forEach((item, index) => { items.forEach((item, index) => {
addRow(index); addRow(index);
const row = itemsTableBody.children[index]; const row = itemsTableBody.children[index];
row.querySelector('[data-field="description"]').value = item.description ?? ''; row.querySelector('[data-field="description"]').value = item.description ?? '';
row.querySelector('[data-field="ctn"]').value = item.ctn ?? ''; row.querySelector('[data-field="ctn"]').value = item.ctn ?? 0;
row.querySelector('[data-field="qty"]').value = item.qty ?? ''; row.querySelector('[data-field="qty"]').value = item.qty ?? 0;
row.querySelector('[data-field="ttl_qty"]').value = item.ttl_qty ?? ''; row.querySelector('[data-field="unit"]').value = item.unit ?? '';
row.querySelector('[data-field="unit"]').value = item.unit ?? ''; row.querySelector('[data-field="price"]').value= item.price ?? 0;
row.querySelector('[data-field="price"]').value = item.price ?? ''; row.querySelector('[data-field="cbm"]').value = item.cbm ?? 0;
row.querySelector('[data-field="ttl_amount"]').value = item.ttl_amount ?? ''; row.querySelector('[data-field="kg"]').value = item.kg ?? 0;
row.querySelector('[data-field="cbm"]').value = item.cbm ?? ''; row.querySelector('[data-field="shop_no"]').value = item.shop_no ?? '';
row.querySelector('[data-field="ttl_cbm"]').value = item.ttl_cbm ?? '';
row.querySelector('[data-field="kg"]').value = item.kg ?? ''; // 🔥 ALWAYS RECALCULATE
row.querySelector('[data-field="ttl_kg"]').value = item.ttl_kg ?? ''; calculateRow(row);
row.querySelector('[data-field="shop_no"]').value = item.shop_no ?? ''; });
});
reindexRows();
}
reindexRows();
}
}); });
</script> </script>