Compare commits

...

8 Commits

Author SHA256 Message Date
Abhishek Mali
9a6ca49ad7 user order controller update for show view data 2026-03-13 20:19:34 +05:30
Abhishek Mali
bf2689e62d container and invoice api done small api like view invoice traking in order section is remaning 2026-03-13 17:25:58 +05:30
Utkarsh Khedkar
c25b468c77 Status Updated paying 2026-03-12 18:20:09 +05:30
Utkarsh Khedkar
5d8a746876 Status Updated 2026-03-12 18:11:43 +05:30
Utkarsh Khedkar
bb2a361a97 Merge branch 'dev' of http://103.248.30.24:3000/kent-logistics/Kent-logistics-Laravel into dev 2026-03-12 12:34:49 +05:30
Utkarsh Khedkar
6b5876e08f Fetch Data For Customer Details 2026-03-12 12:34:27 +05:30
Abhishek Mali
43b1a64911 ajax update 2026-03-12 11:48:42 +05:30
Utkarsh Khedkar
ff4c006ca4 Gst Updates 2026-03-11 20:02:43 +05:30
32 changed files with 1547 additions and 1173 deletions

View File

@@ -1,6 +1,6 @@
APP_NAME=Laravel APP_NAME=Laravel
APP_ENV=local APP_ENV=local
APP_KEY= APP_KEY=base64:/ulhRgiCOFjZV6xUDkXLfiR9X8iFRZ4QIiX3UJbdwY4=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost

View File

@@ -8,10 +8,8 @@ use App\Models\InvoiceItem;
use App\Models\InvoiceInstallment; use App\Models\InvoiceInstallment;
use App\Models\InvoiceChargeGroup; use App\Models\InvoiceChargeGroup;
use App\Models\InvoiceChargeGroupItem; use App\Models\InvoiceChargeGroupItem;
use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Mpdf\Mpdf; use Mpdf\Mpdf;
class AdminInvoiceController extends Controller class AdminInvoiceController extends Controller
@@ -21,7 +19,6 @@ class AdminInvoiceController extends Controller
// ------------------------------------------------------------- // -------------------------------------------------------------
public function index(Request $request) public function index(Request $request)
{ {
// Container relation सह invoices load करतो
$query = Invoice::with(['items', 'customer', 'container']); $query = Invoice::with(['items', 'customer', 'container']);
if ($request->filled('search')) { if ($request->filled('search')) {
@@ -50,7 +47,7 @@ class AdminInvoiceController extends Controller
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// POPUP VIEW + CUSTOMER DATA SYNC // POPUP VIEW
// ------------------------------------------------------------- // -------------------------------------------------------------
public function popup($id) public function popup($id)
{ {
@@ -59,16 +56,8 @@ class AdminInvoiceController extends Controller
'chargeGroups.items', 'chargeGroups.items',
])->findOrFail($id); ])->findOrFail($id);
// demo update असेल तर ठेव/काढ
$invoice->update([
'customer_email' => 'test@demo.com',
'customer_address' => 'TEST ADDRESS',
'pincode' => '999999',
]);
$shipment = null; $shipment = null;
// आधीच group मध्ये असलेले item ids
$groupedItemIds = $invoice->chargeGroups $groupedItemIds = $invoice->chargeGroups
->flatMap(fn($group) => $group->items->pluck('invoice_item_id')) ->flatMap(fn($group) => $group->items->pluck('invoice_item_id'))
->unique() ->unique()
@@ -88,8 +77,31 @@ class AdminInvoiceController extends Controller
'customer', 'customer',
'container', 'container',
'chargeGroups.items', 'chargeGroups.items',
'installments',
])->findOrFail($id); ])->findOrFail($id);
// ✅ Customer details sync — जर test data आला असेल तर fix होईल
if ($invoice->customer) {
$needsUpdate = [];
if (empty($invoice->customer_email) || $invoice->customer_email === 'test@demo.com') {
$needsUpdate['customer_email'] = $invoice->customer->email;
}
if (empty($invoice->customer_address) || $invoice->customer_address === 'TEST ADDRESS') {
$needsUpdate['customer_address'] = $invoice->customer->address;
}
if (empty($invoice->pincode) || $invoice->pincode === '999999') {
$needsUpdate['pincode'] = $invoice->customer->pincode;
}
if (!empty($needsUpdate)) {
$invoice->update($needsUpdate);
$invoice->refresh();
}
}
$shipment = null; $shipment = null;
$groupedItemIds = $invoice->chargeGroups $groupedItemIds = $invoice->chargeGroups
@@ -104,7 +116,7 @@ class AdminInvoiceController extends Controller
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// UPDATE INVOICE (HEADER LEVEL) // UPDATE INVOICE (HEADER ONLY)
// ------------------------------------------------------------- // -------------------------------------------------------------
public function update(Request $request, $id) public function update(Request $request, $id)
{ {
@@ -118,76 +130,31 @@ class AdminInvoiceController extends Controller
$data = $request->validate([ $data = $request->validate([
'invoice_date' => 'required|date', 'invoice_date' => 'required|date',
'due_date' => 'required|date|after_or_equal:invoice_date', 'due_date' => 'required|date|after_or_equal:invoice_date',
'final_amount' => 'required|numeric|min:0', 'status' => 'required|in:pending,paying,paid,overdue',
'tax_type' => 'required|in:gst,igst',
'tax_percent' => 'required|numeric|min:0|max:28',
'status' => 'required|in:pending,paid,overdue',
'notes' => 'nullable|string', 'notes' => 'nullable|string',
]); ]);
Log::info('✅ Validated Invoice Update Data', $data); Log::info('✅ Validated Invoice Header Update Data', $data);
$finalAmount = (float) $data['final_amount'];
$taxPercent = (float) $data['tax_percent'];
if ($data['tax_type'] === 'gst') {
Log::info('🟢 GST Selected', compact('taxPercent'));
$data['cgst_percent'] = $taxPercent / 2;
$data['sgst_percent'] = $taxPercent / 2;
$data['igst_percent'] = 0;
} else {
Log::info('🔵 IGST Selected', compact('taxPercent'));
$data['cgst_percent'] = 0;
$data['sgst_percent'] = 0;
$data['igst_percent'] = $taxPercent;
}
$gstAmount = ($finalAmount * $taxPercent) / 100;
$data['gst_amount'] = $gstAmount;
$data['final_amount_with_gst'] = $finalAmount + $gstAmount;
$data['gst_percent'] = $taxPercent;
Log::info('📌 Final Calculated Invoice Values', [
'invoice_id' => $invoice->id,
'final_amount' => $finalAmount,
'gst_amount' => $data['gst_amount'],
'final_amount_with_gst' => $data['final_amount_with_gst'],
'tax_type' => $data['tax_type'],
'cgst_percent' => $data['cgst_percent'],
'sgst_percent' => $data['sgst_percent'],
'igst_percent' => $data['igst_percent'],
]);
$invoice->update($data); $invoice->update($data);
Log::info('✅ Invoice Updated Successfully', [
'invoice_id' => $invoice->id,
]);
$invoice->refresh(); $invoice->refresh();
Log::info('🔍 Invoice AFTER UPDATE (DB values)', [
Log::info('🔍 Invoice AFTER HEADER UPDATE', [
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'final_amount' => $invoice->final_amount, 'charge_groups_total' => $invoice->charge_groups_total,
'gst_percent' => $invoice->gst_percent,
'gst_amount' => $invoice->gst_amount, 'gst_amount' => $invoice->gst_amount,
'final_amount_with_gst' => $invoice->final_amount_with_gst, 'grand_total_with_charges'=> $invoice->grand_total_with_charges,
'tax_type' => $invoice->tax_type,
'cgst_percent' => $invoice->cgst_percent,
'sgst_percent' => $invoice->sgst_percent,
'igst_percent' => $invoice->igst_percent,
]); ]);
$this->generateInvoicePDF($invoice); $this->generateInvoicePDF($invoice);
return redirect() return redirect()
->route('admin.invoices.index') ->route('admin.invoices.edit', $invoice->id)
->with('success', 'Invoice updated & PDF generated successfully.'); ->with('success', 'Invoice updated & PDF generated successfully.');
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// UPDATE INVOICE ITEMS // UPDATE INVOICE ITEMS (फक्त items save)
// ------------------------------------------------------------- // -------------------------------------------------------------
public function updateItems(Request $request, Invoice $invoice) public function updateItems(Request $request, Invoice $invoice)
{ {
@@ -202,9 +169,7 @@ class AdminInvoiceController extends Controller
'items.*.ttl_amount' => ['required', 'numeric', 'min:0'], 'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
]); ]);
$itemsInput = $data['items']; foreach ($data['items'] as $itemId => $itemData) {
foreach ($itemsInput as $itemId => $itemData) {
$item = InvoiceItem::where('id', $itemId) $item = InvoiceItem::where('id', $itemId)
->where('invoice_id', $invoice->id) ->where('invoice_id', $invoice->id)
->first(); ->first();
@@ -222,47 +187,8 @@ class AdminInvoiceController extends Controller
$item->save(); $item->save();
} }
$newBaseAmount = InvoiceItem::where('invoice_id', $invoice->id) Log::info('✅ Invoice items updated (no totals recalculation)', [
->sum('ttl_amount');
$taxType = $invoice->tax_type;
$cgstPercent = (float) ($invoice->cgst_percent ?? 0);
$sgstPercent = (float) ($invoice->sgst_percent ?? 0);
$igstPercent = (float) ($invoice->igst_percent ?? 0);
$gstPercent = 0;
if ($taxType === 'gst') {
$gstPercent = $cgstPercent + $sgstPercent;
} elseif ($taxType === 'igst') {
$gstPercent = $igstPercent;
}
$gstAmount = $newBaseAmount * $gstPercent / 100;
$finalWithGst = $newBaseAmount + $gstAmount;
$invoice->final_amount = $newBaseAmount;
$invoice->gst_amount = $gstAmount;
$invoice->final_amount_with_gst = $finalWithGst;
$invoice->gst_percent = $gstPercent;
$invoice->save();
// ⭐ Total Charges (groups समावेत) पुन्हा कॅलक्युलेट
$chargeGroupsTotal = $invoice->chargeGroups()->sum('total_charge');
$invoice->charge_groups_total = $chargeGroupsTotal;
$invoice->grand_total_with_charges = $invoice->final_amount_with_gst + $chargeGroupsTotal;
$invoice->save();
Log::info('✅ Invoice items updated & totals recalculated', [
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'final_amount' => $invoice->final_amount,
'gst_amount' => $invoice->gst_amount,
'final_amount_with_gst' => $invoice->final_amount_with_gst,
'tax_type' => $invoice->tax_type,
'cgst_percent' => $invoice->cgst_percent,
'sgst_percent' => $invoice->sgst_percent,
'igst_percent' => $invoice->igst_percent,
'charge_groups_total' => $invoice->charge_groups_total,
'grand_total_with_charges' => $invoice->grand_total_with_charges,
]); ]);
return back()->with('success', 'Invoice items updated successfully.'); return back()->with('success', 'Invoice items updated successfully.');
@@ -319,10 +245,10 @@ class AdminInvoiceController extends Controller
]); ]);
$invoice = Invoice::findOrFail($invoice_id); $invoice = Invoice::findOrFail($invoice_id);
$paidTotal = $invoice->installments()->sum('amount');
// 👇 Total Charges (grand_total_with_charges) वरून remaining $grandTotal = $invoice->grand_total_with_charges ?? 0;
$grandTotal = $invoice->grand_total_with_charges;
$paidTotal = $invoice->installments()->sum('amount');
$remaining = $grandTotal - $paidTotal; $remaining = $grandTotal - $paidTotal;
if ($request->amount > $remaining) { if ($request->amount > $remaining) {
@@ -341,8 +267,9 @@ class AdminInvoiceController extends Controller
]); ]);
$newPaid = $paidTotal + $request->amount; $newPaid = $paidTotal + $request->amount;
$remaining = max(0, $grandTotal - $newPaid);
if ($newPaid >= $invoice->final_amount_with_gst) { if ($newPaid >= $grandTotal && $grandTotal > 0) {
$invoice->update(['status' => 'paid']); $invoice->update(['status' => 'paid']);
} }
@@ -350,14 +277,14 @@ class AdminInvoiceController extends Controller
'status' => 'success', 'status' => 'success',
'message' => 'Installment added successfully.', 'message' => 'Installment added successfully.',
'installment' => $installment, 'installment' => $installment,
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal,
'totalPaid' => $newPaid, 'totalPaid' => $newPaid,
'gstAmount' => $invoice->gst_amount, 'remaining' => $remaining,
'finalAmountWithGst' => $grandTotal, // इथे grand total पाठव 'isCompleted' => $remaining <= 0,
'baseAmount' => $invoice->final_amount, 'isZero' => $newPaid == 0,
'remaining' => max(0, $grandTotal - $newPaid),
'isCompleted' => $newPaid >= $grandTotal,
]); ]);
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
@@ -369,87 +296,158 @@ class AdminInvoiceController extends Controller
$invoice = $installment->invoice; $invoice = $installment->invoice;
$installment->delete(); $installment->delete();
$invoice->refresh();
$grandTotal = $invoice->grand_total_with_charges ?? 0;
$paidTotal = $invoice->installments()->sum('amount'); $paidTotal = $invoice->installments()->sum('amount');
$grandTotal = $invoice->grand_total_with_charges; $remaining = max(0, $grandTotal - $paidTotal);
$remaining = $grandTotal - $paidTotal;
if ($paidTotal <= 0 && $grandTotal > 0) {
$invoice->update(['status' => 'pending']);
} elseif ($paidTotal > 0 && $paidTotal < $grandTotal) {
$invoice->update(['status' => 'pending']);
}
return response()->json([ return response()->json([
'status' => 'success', 'status' => 'success',
'message' => 'Installment deleted.', 'message' => 'Installment deleted.',
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal,
'totalPaid' => $paidTotal, 'totalPaid' => $paidTotal,
'gstAmount' => $invoice->gst_amount,
'finalAmountWithGst' => $grandTotal, // इथेही
'baseAmount' => $invoice->final_amount,
'remaining' => $remaining, 'remaining' => $remaining,
'isZero' => $paidTotal == 0, 'isZero' => $paidTotal == 0,
]); ]);
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// CHARGE GROUP SAVE (no AJAX branch) // CHARGE GROUP SAVE
// ------------------------------------------------------------- // -------------------------------------------------------------
public function storeChargeGroup(Request $request, $invoiceId) public function storeChargeGroup(Request $request, $invoiceId)
{ {
$invoice = Invoice::with('items')->findOrFail($invoiceId); Log::info('🟡 storeChargeGroup HIT', [
'invoice_id' => $invoiceId,
$data = $request->validate([ 'payload' => $request->all(),
'group_name' => 'required|string|max:255',
'basis_type' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
'basis_value' => 'required|numeric',
'rate' => 'required|numeric|min:0.0001',
'auto_total' => 'required|numeric|min:0.01',
'item_ids' => 'required|array',
'item_ids.*' => 'integer|exists:invoice_items,id',
]); ]);
$invoice = Invoice::with('items', 'chargeGroups')->findOrFail($invoiceId);
$data = $request->validate([
'groupname' => 'required|string|max:255',
'basistype' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
'basisvalue' => 'required|numeric',
'rate' => 'required|numeric|min:0.0001',
'autototal' => 'required|numeric|min:0.01',
'itemids' => 'required|array',
'itemids.*' => 'integer|exists:invoice_items,id',
'tax_type' => 'nullable|in:none,gst,igst',
'gst_percent' => 'nullable|numeric|min:0|max:28',
'total_with_gst' => 'nullable|numeric|min:0',
]);
Log::info('✅ storeChargeGroup VALIDATED', $data);
// duplicate name check
$exists = InvoiceChargeGroup::where('invoice_id', $invoice->id) $exists = InvoiceChargeGroup::where('invoice_id', $invoice->id)
->where('group_name', $data['group_name']) ->where('group_name', $data['groupname'])
->exists(); ->exists();
if ($exists) { if ($exists) {
return back() return back()
->withErrors(['group_name' => 'This group name is already used for this invoice.']) ->withErrors(['groupname' => 'This group name is already used for this invoice.'])
->withInput(); ->withInput();
} }
$taxType = $data['tax_type'] ?? 'gst';
$gstPercent = $data['gst_percent'] ?? 0;
$baseTotal = $data['autototal'];
$totalWithGst = $data['total_with_gst'] ?? $baseTotal;
if ($totalWithGst == 0 && $gstPercent > 0) {
$gstAmount = ($baseTotal * $gstPercent) / 100;
$totalWithGst = $baseTotal + $gstAmount;
}
// 1) Group create
$group = InvoiceChargeGroup::create([ $group = InvoiceChargeGroup::create([
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'group_name' => $data['group_name'], 'group_name' => $data['groupname'],
'basis_type' => $data['basis_type'], 'basis_type' => $data['basistype'],
'basis_value' => $data['basis_value'], 'basis_value' => $data['basisvalue'],
'rate' => $data['rate'], 'rate' => $data['rate'],
'total_charge' => $data['auto_total'], 'total_charge' => $baseTotal,
'tax_type' => $taxType,
'gst_percent' => $gstPercent,
'total_with_gst' => $totalWithGst,
]); ]);
foreach ($data['item_ids'] as $itemId) { // 2) Items link
foreach ($data['itemids'] as $itemId) {
InvoiceChargeGroupItem::create([ InvoiceChargeGroupItem::create([
'group_id' => $group->id, 'group_id' => $group->id,
'invoice_item_id' => $itemId, 'invoice_item_id' => $itemId,
]); ]);
} }
// ⭐ Charge groups नुसार Total Charges सेट करा // 3) सर्व groups वरून invoice level totals
$chargeGroupsTotal = $invoice->chargeGroups()->sum('total_charge'); $invoice->load('chargeGroups');
$grandTotal = $invoice->final_amount_with_gst + $chargeGroupsTotal;
$chargeGroupsBase = $invoice->chargeGroups->sum('total_charge'); // base
$chargeGroupsWithG = $invoice->chargeGroups->sum('total_with_gst'); // base + gst
$chargeGroupsGst = $chargeGroupsWithG - $chargeGroupsBase; // gst only
$invoiceGstPercent = $group->gst_percent ?? 0;
$invoiceTaxType = $group->tax_type ?? 'gst';
$cgstPercent = 0;
$sgstPercent = 0;
$igstPercent = 0;
if ($invoiceTaxType === 'gst') {
$cgstPercent = $invoiceGstPercent / 2;
$sgstPercent = $invoiceGstPercent / 2;
} elseif ($invoiceTaxType === 'igst') {
$igstPercent = $invoiceGstPercent;
}
// 🔴 इथे main fix:
// final_amount = base (total_charge sum)
// final_amount_with_gst = base + gst (total_with_gst sum)
// grand_total_with_charges = final_amount_with_gst (same)
$invoice->update([ $invoice->update([
'charge_groups_total' => $chargeGroupsTotal, 'charge_groups_total' => $chargeGroupsBase,
'grand_total_with_charges' => $grandTotal, 'gst_amount' => $chargeGroupsGst,
'gst_percent' => $invoiceGstPercent,
'tax_type' => $invoiceTaxType,
'cgst_percent' => $cgstPercent,
'sgst_percent' => $sgstPercent,
'igst_percent' => $igstPercent,
'final_amount' => $chargeGroupsBase,
'final_amount_with_gst' => $chargeGroupsWithG,
'grand_total_with_charges' => $chargeGroupsWithG,
]); ]);
return redirect() Log::info('✅ Invoice updated from Charge Group (CG total + GST)', [
->back() 'invoice_id' => $invoice->id,
->with('success', 'Charge group saved successfully.'); 'charge_groups_total' => $chargeGroupsBase,
} 'gst_amount' => $chargeGroupsGst,
'gst_percent' => $invoiceGstPercent,
'tax_type' => $invoiceTaxType,
'cgst_percent' => $cgstPercent,
'sgst_percent' => $sgstPercent,
'igst_percent' => $igstPercent,
'final_amount' => $invoice->final_amount,
'final_amount_with_gst' => $invoice->final_amount_with_gst,
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
]);
public function download(Invoice $invoice) return response()->json([
{ 'success' => true,
if (!$invoice->pdf_path || !Storage::exists($invoice->pdf_path)) { 'message' => 'Charge group saved successfully.',
return back()->with('error', 'PDF not found.'); 'group_id' => $group->id,
} ]);
return Storage::download($invoice->pdf_path, $invoice->invoice_number . '.pdf');
} }
} }

View File

@@ -11,7 +11,7 @@ use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
use Carbon\Carbon; use Carbon\Carbon;
use Barryvdh\DomPDF\Facade\Pdf; use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\Storage; // <-- added for Excel download use Illuminate\Support\Facades\Storage;
class ContainerController extends Controller class ContainerController extends Controller
{ {
@@ -518,9 +518,12 @@ class ContainerController extends Controller
$firstMark = $rowsForCustomer[0]['mark']; $firstMark = $rowsForCustomer[0]['mark'];
$snap = $markToSnapshot[$firstMark] ?? null; $snap = $markToSnapshot[$firstMark] ?? null;
// ✅ Customer User model वरून fetch करा (customer_id string आहे जसे CID-2025-000001)
$customerUser = \App\Models\User::where('customer_id', $customerId)->first();
$invoice = new Invoice(); $invoice = new Invoice();
$invoice->container_id = $container->id; $invoice->container_id = $container->id;
// $invoice->customer_id = $customerId; $invoice->customer_id = $customerUser->id ?? null; // ✅ integer id store करतोय
$invoice->mark_no = $firstMark; $invoice->mark_no = $firstMark;
$invoice->invoice_number = $this->generateInvoiceNumber(); $invoice->invoice_number = $this->generateInvoiceNumber();
@@ -533,21 +536,25 @@ class ContainerController extends Controller
->addDays(10) ->addDays(10)
->format('Y-m-d'); ->format('Y-m-d');
// ✅ Snapshot data from MarkList (backward compatibility)
if ($snap) { if ($snap) {
$invoice->customer_name = $snap['customer_name'] ?? null; $invoice->customer_name = $snap['customer_name'] ?? null;
$invoice->company_name = $snap['company_name'] ?? null; $invoice->company_name = $snap['company_name'] ?? null;
$invoice->customer_mobile = $snap['mobile_no'] ?? null; $invoice->customer_mobile = $snap['mobile_no'] ?? null;
} }
// ✅ User model वरून email, address, pincode घ्या
if ($customerUser) {
$invoice->customer_email = $customerUser->email ?? null;
$invoice->customer_address = $customerUser->address ?? null;
$invoice->pincode = $customerUser->pincode ?? null;
}
$invoice->final_amount = 0; $invoice->final_amount = 0;
$invoice->gst_percent = 0; $invoice->gst_percent = 0;
$invoice->gst_amount = 0; $invoice->gst_amount = 0;
$invoice->final_amount_with_gst = 0; $invoice->final_amount_with_gst = 0;
$invoice->customer_email = null;
$invoice->customer_address = null;
$invoice->pincode = null;
$uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark')); $uniqueMarks = array_unique(array_column($rowsForCustomer, 'mark'));
$invoice->notes = 'Auto-created from Container ' . $container->container_number $invoice->notes = 'Auto-created from Container ' . $container->container_number
. ' for Mark(s): ' . implode(', ', $uniqueMarks); . ' for Mark(s): ' . implode(', ', $uniqueMarks);
@@ -613,7 +620,18 @@ class ContainerController extends Controller
public function show(Container $container) public function show(Container $container)
{ {
$container->load('rows'); $container->load('rows');
return view('admin.container_show', compact('container'));
// paid / paying invoices च्या row indexes collect करा
$lockedRowIndexes = \App\Models\Invoice::whereIn('invoices.status', ['paid', 'paying'])
->where('invoices.container_id', $container->id)
->join('invoice_items', 'invoices.id', '=', 'invoice_items.invoice_id')
->pluck('invoice_items.container_row_index')
->filter()
->unique()
->values()
->toArray();
return view('admin.container_show', compact('container', 'lockedRowIndexes'));
} }
public function updateRows(Request $request, Container $container) public function updateRows(Request $request, Container $container)
@@ -871,7 +889,6 @@ class ContainerController extends Controller
return Storage::download($path, $fileName); return Storage::download($path, $fileName);
} }
public function popupPopup(Container $container) public function popupPopup(Container $container)
{ {
// existing show सारखाच data वापरू // existing show सारखाच data वापरू
@@ -939,5 +956,4 @@ class ContainerController extends Controller
'summary' => $summary, 'summary' => $summary,
]); ]);
} }
} }

View File

@@ -21,23 +21,33 @@ class UserOrderController extends Controller
} }
// ------------------------------------- // -------------------------------------
// Get all orders // Get customer invoices with containers
// ------------------------------------- // -------------------------------------
$orders = $user->orders()->with('invoice')->get(); $invoices = $user->invoices()->with('container')->get();
// Unique containers for this customer
$containers = $invoices->pluck('container')->filter()->unique('id');
// ------------------------------------- // -------------------------------------
// Counts // Counts based on container status
// ------------------------------------- // -------------------------------------
$totalOrders = $orders->count(); $totalOrders = $containers->count();
$delivered = $orders->where('status', 'delivered')->count();
$inTransit = $orders->where('status', '!=', 'delivered')->count(); $delivered = $containers->where('status', 'delivered')->count();
$inTransit = $containers->whereNotIn('status', [
'delivered',
'warehouse',
'domestic-distribution'
])->count();
$active = $totalOrders; $active = $totalOrders;
// ------------------------------------- // -------------------------------------
// Total Amount = Invoice.total_with_gst // Total Amount = sum of invoice totals
// ------------------------------------- // -------------------------------------
$totalAmount = $orders->sum(function ($o) { $totalAmount = $invoices->sum(function ($invoice) {
return $o->invoice->final_amount_with_gst ?? 0; return $invoice->final_amount_with_gst ?? 0;
}); });
// Format total amount in K, L, Cr // Format total amount in K, L, Cr
@@ -45,13 +55,12 @@ class UserOrderController extends Controller
return response()->json([ return response()->json([
'status' => true, 'status' => true,
'summary' => [ 'summary' => [
'active_orders' => $active, 'active_orders' => $active,
'in_transit_orders' => $inTransit, 'in_transit_orders' => $inTransit,
'delivered_orders' => $delivered, 'delivered_orders' => $delivered,
'total_value' => $formattedAmount, // formatted value 'total_value' => $formattedAmount,
'total_raw' => $totalAmount // original value 'total_raw' => $totalAmount
] ]
]); ]);
} }
@@ -90,18 +99,26 @@ class UserOrderController extends Controller
], 401); ], 401);
} }
// Fetch orders for this user // Get invoices with containers for this customer
$orders = $user->orders() $invoices = $user->invoices()
->with(['invoice', 'shipments']) ->with('container')
->orderBy('id', 'desc') ->orderBy('id', 'desc')
->get() ->get();
->map(function ($o) {
// Extract unique containers
$containers = $invoices->pluck('container')
->filter()
->unique('id')
->values();
$orders = $containers->map(function ($container) {
return [ return [
'order_id' => $o->order_id, 'order_id' => $container->id,
'status' => $o->status, 'container_number' => $container->container_number,
'amount' => $o->ttl_amount, 'status' => $container->status,
'description'=> "Order from {$o->origin} to {$o->destination}", 'container_date' => $container->container_date,
'created_at' => $o->created_at, 'created_at' => $container->created_at,
]; ];
}); });
@@ -115,45 +132,73 @@ public function orderDetails($order_id)
{ {
$user = JWTAuth::parseToken()->authenticate(); $user = JWTAuth::parseToken()->authenticate();
$order = $user->orders() if (!$user) {
return response()->json([
'success' => false,
'message' => 'Unauthorized'
], 401);
}
// Find container first
$container = \App\Models\Container::find($order_id);
if (!$container) {
return response()->json([
'success' => false,
'message' => 'Container not found'
], 404);
}
// Find invoice belonging to this user for this container
$invoice = \App\Models\Invoice::where('customer_id', $user->id)
->where('container_id', $container->id)
->with(['items']) ->with(['items'])
->where('order_id', $order_id)
->first(); ->first();
if (!$order) { if (!$invoice) {
return response()->json(['success' => false, 'message' => 'Order not found'], 404); return response()->json([
'success' => false,
'message' => 'Order not found for this user'
], 404);
} }
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'order' => $order 'order' => [
'container_id' => $container->id,
'container_number' => $container->container_number,
'container_date' => $container->container_date,
'status' => $container->status,
'invoice_id' => $invoice->id,
'items' => $invoice->items
]
]); ]);
} }
public function orderShipment($order_id) // public function orderShipment($order_id)
{ // {
$user = JWTAuth::parseToken()->authenticate(); // $user = JWTAuth::parseToken()->authenticate();
// Get order // // Get order
$order = $user->orders()->where('order_id', $order_id)->first(); // $order = $user->orders()->where('order_id', $order_id)->first();
if (!$order) { // if (!$order) {
return response()->json(['success' => false, 'message' => 'Order not found'], 404); // return response()->json(['success' => false, 'message' => 'Order not found'], 404);
} // }
// Find shipment only for this order // // Find shipment only for this order
$shipment = $order->shipments() // $shipment = $order->shipments()
->with(['items' => function ($q) use ($order) { // ->with(['items' => function ($q) use ($order) {
$q->where('order_id', $order->id); // $q->where('order_id', $order->id);
}]) // }])
->first(); // ->first();
return response()->json([ // return response()->json([
'success' => true, // 'success' => true,
'shipment' => $shipment // 'shipment' => $shipment
]); // ]);
} // }
public function orderInvoice($order_id) public function orderInvoice($order_id)
@@ -179,23 +224,35 @@ public function trackOrder($order_id)
{ {
$user = JWTAuth::parseToken()->authenticate(); $user = JWTAuth::parseToken()->authenticate();
$order = $user->orders() if (!$user) {
->with('shipments') return response()->json([
->where('order_id', $order_id) 'success' => false,
->first(); 'message' => 'Unauthorized'
], 401);
if (!$order) {
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
} }
$shipment = $order->shipments()->first(); // Ensure the container belongs to this customer via invoice
$invoice = \App\Models\Invoice::where('customer_id', $user->id)
->where('container_id', $order_id)
->with('container')
->first();
if (!$invoice || !$invoice->container) {
return response()->json([
'success' => false,
'message' => 'Order not found'
], 404);
}
$container = $invoice->container;
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'track' => [ 'track' => [
'order_id' => $order->order_id, 'order_id' => $container->id,
'shipment_status' => $shipment->status ?? 'pending', 'container_number' => $container->container_number,
'shipment_date' => $shipment->shipment_date ?? null, 'status' => $container->status,
'container_date' => $container->container_date,
] ]
]); ]);
} }
@@ -289,44 +346,44 @@ public function invoiceDetails($invoice_id)
]); ]);
} }
public function confirmOrder($order_id) // public function confirmOrder($order_id)
{ // {
$user = JWTAuth::parseToken()->authenticate(); // $user = JWTAuth::parseToken()->authenticate();
if (! $user) { // if (! $user) {
return response()->json([ // return response()->json([
'success' => false, // 'success' => false,
'message' => 'Unauthorized' // 'message' => 'Unauthorized'
], 401); // ], 401);
} // }
$order = $user->orders() // $order = $user->orders()
->where('order_id', $order_id) // ->where('order_id', $order_id)
->first(); // ->first();
if (! $order) { // if (! $order) {
return response()->json([ // return response()->json([
'success' => false, // 'success' => false,
'message' => 'Order not found' // 'message' => 'Order not found'
], 404); // ], 404);
} // }
// 🚫 Only allow confirm from order_placed // // 🚫 Only allow confirm from order_placed
if ($order->status !== 'order_placed') { // if ($order->status !== 'order_placed') {
return response()->json([ // return response()->json([
'success' => false, // 'success' => false,
'message' => 'Order cannot be confirmed' // 'message' => 'Order cannot be confirmed'
], 422); // ], 422);
} // }
$order->status = 'order_confirmed'; // $order->status = 'order_confirmed';
$order->save(); // $order->save();
return response()->json([ // return response()->json([
'success' => true, // 'success' => true,
'message' => 'Order confirmed successfully' // 'message' => 'Order confirmed successfully'
]); // ]);
} // }

View File

@@ -31,6 +31,13 @@ class Invoice extends Model
'pincode', 'pincode',
'pdf_path', 'pdf_path',
'notes', 'notes',
// totals from charge groups
'charge_groups_total',
'grand_total_with_charges',
'tax_type',
'cgst_percent',
'sgst_percent',
'igst_percent',
]; ];
/**************************** /****************************
@@ -42,10 +49,10 @@ class Invoice extends Model
return $this->hasMany(InvoiceItem::class)->orderBy('id', 'ASC'); return $this->hasMany(InvoiceItem::class)->orderBy('id', 'ASC');
} }
public function container() // public function container()
{ // {
return $this->belongsTo(Container::class); // return $this->belongsTo(Container::class);
} // }
public function customer() public function customer()
{ {
@@ -57,16 +64,16 @@ class Invoice extends Model
return $this->hasMany(InvoiceInstallment::class); return $this->hasMany(InvoiceInstallment::class);
} }
// ✅ SINGLE, correct relation
public function chargeGroups() public function chargeGroups()
{ {
return $this->hasMany(\App\Models\InvoiceChargeGroup::class, 'invoice_id'); return $this->hasMany(InvoiceChargeGroup::class, 'invoice_id');
} }
/**************************** /****************************
* Helper Functions * Helper Functions
****************************/ ****************************/
// (Items based calculateTotals वापरणार नाहीस तरी ठेवू शकतोस)
public function calculateTotals() public function calculateTotals()
{ {
$gst = ($this->final_amount * $this->gst_percent) / 100; $gst = ($this->final_amount * $this->gst_percent) / 100;
@@ -84,29 +91,43 @@ class Invoice extends Model
return null; return null;
} }
// ✅ Charge groups total accessor // ✅ Charge groups base total (WITHOUT GST)
public function getChargeGroupsTotalAttribute() public function getChargeGroupsTotalAttribute()
{ {
// relation already loaded असेल तर collection वरून sum होईल // base = total_charge sum
return (float) $this->chargeGroups->sum('total_charge'); return (float) $this->chargeGroups->sum('total_charge');
} }
// ✅ Grand total accessor (items + GST + charge groups) // ✅ Grand total: Charge groups base + GST (items ignore)
public function getGrandTotalWithChargesAttribute() public function getGrandTotalWithChargesAttribute()
{ {
return (float) ($this->final_amount_with_gst ?? 0) + $this->charge_groups_total; $base = (float) ($this->charge_groups_total ?? 0);
$gst = (float) ($this->gst_amount ?? 0);
return $base + $gst;
} }
public function totalPaid() public function totalPaid(): float
{ {
return $this->installments->sum('amount'); return (float) $this->installments()->sum('amount');
} }
public function remainingAmount(): float
public function remainingAmount()
{ {
return $this->grand_total_with_charges - $this->totalPaid(); $grand = (float) $this->grand_total_with_charges;
$paid = (float) $this->totalPaid();
return max(0, $grand - $paid);
} }
public function isLockedForEdit(): bool
{
return $this->status === 'paid';
}
public function container()
{
return $this->belongsTo(\App\Models\Container::class, 'container_id');
}
} }

View File

@@ -13,6 +13,10 @@ class InvoiceChargeGroup extends Model
'basis_value', 'basis_value',
'rate', 'rate',
'total_charge', 'total_charge',
'tax_type',
'gst_percent',
'total_with_gst',
]; ];
public function invoice() public function invoice()

View File

@@ -89,10 +89,7 @@ class User extends Authenticatable implements JWTSubject
{ {
return []; return [];
} }
public function invoices()
{
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'customer_id');
}
// App\Models\User.php // App\Models\User.php
@@ -108,6 +105,10 @@ public function invoiceInstallments()
); );
} }
public function invoices()
{
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
}
} }

View File

@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('invoice_charge_groups', function (Blueprint $table) {
$table->string('tax_type')->nullable()->after('total_charge');
$table->decimal('gst_percent', 5, 2)->default(0)->after('tax_type');
$table->decimal('total_with_gst', 15, 2)->default(0)->after('gst_percent');
});
}
public function down(): void
{
Schema::table('invoice_charge_groups', function (Blueprint $table) {
$table->dropColumn(['tax_type', 'gst_percent', 'total_with_gst']);
});
}
};

View File

@@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddChargeColumnsToInvoicesTable extends Migration
{
public function up()
{
Schema::table('invoices', function (Blueprint $table) {
if (!Schema::hasColumn('invoices', 'charge_groups_total')) {
$table->decimal('charge_groups_total', 15, 2)
->nullable()
->after('final_amount_with_gst');
}
if (!Schema::hasColumn('invoices', 'grand_total_with_charges')) {
$table->decimal('grand_total_with_charges', 15, 2)
->nullable()
->after('charge_groups_total');
}
});
}
public function down()
{
Schema::table('invoices', function (Blueprint $table) {
if (Schema::hasColumn('invoices', 'charge_groups_total')) {
$table->dropColumn('charge_groups_total');
}
if (Schema::hasColumn('invoices', 'grand_total_with_charges')) {
$table->dropColumn('grand_total_with_charges');
}
});
}
}

View File

@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration {
public function up(): void
{
DB::statement("
ALTER TABLE `invoices`
MODIFY `status` ENUM('pending','paying','paid','overdue')
NOT NULL DEFAULT 'pending'
");
}
public function down(): void
{
DB::statement("
ALTER TABLE `invoices`
MODIFY `status` ENUM('pending','paid','overdue')
NOT NULL DEFAULT 'pending'
");
}
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -672,11 +672,19 @@
); );
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount; $isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
// row index = headerRowIndex + 1 + offset — ContainerRow मध्ये row_index save आहे
$isLockedByInvoice = in_array($row->row_index, $lockedRowIndexes ?? []);
$isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice;
@endphp @endphp
@if($loop->first && $isLockedByInvoice)
{{-- पहिल्या cell मध्ये lock icon --}}
@endif
<td> <td>
<input <input
type="text" type="text"
class="cm-cell-input {{ $isTotalColumn ? 'cm-cell-readonly' : '' }}" class="cm-cell-input {{ $isReadOnly ? 'cm-cell-readonly' : '' }}"
name="rows[{{ $row->id }}][{{ $heading }}]" name="rows[{{ $row->id }}][{{ $heading }}]"
value="{{ $value }}" value="{{ $value }}"
data-row-id="{{ $row->id }}" data-row-id="{{ $row->id }}"
@@ -690,10 +698,11 @@
data-ttlkg="{{ $isTotalKg ? '1' : '0' }}" data-ttlkg="{{ $isTotalKg ? '1' : '0' }}"
data-price="{{ $isPrice ? '1' : '0' }}" data-price="{{ $isPrice ? '1' : '0' }}"
data-amount="{{ $isAmount ? '1' : '0' }}" data-amount="{{ $isAmount ? '1' : '0' }}"
@if($isTotalColumn) readonly @endif {{ $isReadOnly ? 'readonly' : '' }}
> >
</td> </td>
@endforeach @endforeach
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
@@ -706,14 +715,16 @@
</div> </div>
@if(!$container->rows->isEmpty()) @if(!$container->rows->isEmpty())
<button id="cmSaveBtnFloating" class="cm-save-btn-floating"> <button
id="cmSaveBtnFloating"
class="cm-save-btn-floating {{ $container->status !== 'pending' ? 'cm-disabled' : '' }}"
{{ $container->status !== 'pending' ? 'disabled' : '' }}
>
Save Changes Save Changes
</button> </button>
<div id="cmToast" class="cm-toast">
Changes saved successfully.
</div>
@endif @endif
<script> <script>
function cmFilterRows() { function cmFilterRows() {
const input = document.getElementById('cmRowSearch'); const input = document.getElementById('cmRowSearch');
@@ -843,6 +854,7 @@
const target = e.target; const target = e.target;
if (!target.classList.contains('cm-cell-input')) return; if (!target.classList.contains('cm-cell-input')) return;
// readonly / non-pending cells साठी block
if (target.classList.contains('cm-cell-readonly') || target.hasAttribute('readonly')) { if (target.classList.contains('cm-cell-readonly') || target.hasAttribute('readonly')) {
target.blur(); target.blur();
return; return;
@@ -857,6 +869,11 @@
if (form && btn) { if (form && btn) {
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
// जर बटण आधीच disabled असेल (non-pending status किंवा processing)
if (btn.classList.contains('cm-disabled') || btn.hasAttribute('disabled')) {
return;
}
btn.classList.add('cm-disabled'); btn.classList.add('cm-disabled');
const formData = new FormData(form); const formData = new FormData(form);
@@ -888,4 +905,5 @@
} }
}); });
</script> </script>
@endsection @endsection

View File

@@ -170,6 +170,13 @@
font-weight: 500; font-weight: 500;
} }
.filter-select option[value="paying"] {
background-color: #e0e7ff;
color: #4338ca;
font-weight: 500;
}
.filter-select option[value="all"] { .filter-select option[value="all"] {
background-color: white; background-color: white;
color: #1f2937; color: #1f2937;
@@ -534,6 +541,10 @@
background: url('/images/status-bg-overdue.png') !important; background: url('/images/status-bg-overdue.png') !important;
} }
.badge-paying {
background: url('/images/status-bg-paying.png') !important;
}
/* Fallback colors if images don't load - ALL WITH SAME SIZE */ /* Fallback colors if images don't load - ALL WITH SAME SIZE */
.badge.badge-paid { .badge.badge-paid {
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important; background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
@@ -554,6 +565,13 @@
border-color: #8b5cf6 !important; border-color: #8b5cf6 !important;
} }
.badge.badge-paying {
background: linear-gradient(135deg, #e0e7ff, #c7d2fe) !important;
color: #4338ca !important;
border-color: #6366f1 !important;
}
/* Entry Button - Centered */ /* Entry Button - Centered */
.btn-entry { .btn-entry {
background: linear-gradient(135deg, #3492f8 55%, #1256cc 110%); background: linear-gradient(135deg, #3492f8 55%, #1256cc 110%);
@@ -1282,11 +1300,14 @@
<i class="bi bi-check-circle-fill status-icon"></i> <i class="bi bi-check-circle-fill status-icon"></i>
@elseif($invoice->status == 'pending') @elseif($invoice->status == 'pending')
<i class="bi bi-clock-fill status-icon"></i> <i class="bi bi-clock-fill status-icon"></i>
@elseif($invoice->status == 'paying')
<i class="bi bi-arrow-repeat status-icon"></i> {{-- processing icon --}}
@elseif($invoice->status == 'overdue') @elseif($invoice->status == 'overdue')
<i class="bi bi-exclamation-triangle-fill status-icon"></i> <i class="bi bi-exclamation-triangle-fill status-icon"></i>
@endif @endif
{{ ucfirst($invoice->status) }} {{ ucfirst($invoice->status) }}
</span> </span>
</td> </td>
<td class="date-cell"> <td class="date-cell">
@@ -1338,11 +1359,14 @@
<i class="bi bi-check-circle-fill status-icon"></i> <i class="bi bi-check-circle-fill status-icon"></i>
@elseif($invoice->status == 'pending') @elseif($invoice->status == 'pending')
<i class="bi bi-clock-fill status-icon"></i> <i class="bi bi-clock-fill status-icon"></i>
@elseif($invoice->status == 'paying')
<i class="bi bi-arrow-repeat status-icon"></i>
@elseif($invoice->status == 'overdue') @elseif($invoice->status == 'overdue')
<i class="bi bi-exclamation-triangle-fill status-icon"></i> <i class="bi bi-exclamation-triangle-fill status-icon"></i>
@endif @endif
{{ ucfirst($invoice->status) }} {{ ucfirst($invoice->status) }}
</span> </span>
</div> </div>
<div class="mobile-invoice-details"> <div class="mobile-invoice-details">
@@ -1677,6 +1701,7 @@ document.addEventListener('DOMContentLoaded', function() {
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''} ${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''} ${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''} ${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
${invoice.status === 'paying' ? '<i class="bi bi-arrow-repeat status-icon"></i>' : ''}
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)} ${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
</span> </span>
</td> </td>
@@ -1710,6 +1735,7 @@ document.addEventListener('DOMContentLoaded', function() {
${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''} ${invoice.status === 'paid' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''} ${invoice.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''} ${invoice.status === 'overdue' ? '<i class="bi bi-exclamation-triangle-fill status-icon"></i>' : ''}
${invoice.status === 'paying' ? '<i class="bi bi-arrow-repeat status-icon"></i>' : ''}
${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)} ${invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
</span> </span>
</div> </div>

View File

@@ -1,9 +1,7 @@
@extends('admin.layouts.app') @extends('admin.layouts.app')
@section('page-title', 'Edit Invoice') @section('page-title', 'Edit Invoice')
@section('content') @section('content')
<style> <style>
:root { :root {
@@ -314,7 +312,7 @@
</style> </style>
<div class="container-fluid py-3"> <div class="container-fluid py-3">
{{-- Invoice Preview / Overview --}} {{-- Invoice Overview --}}
<div class="glass-card"> <div class="glass-card">
<div class="card-header-compact"> <div class="card-header-compact">
<h4> <h4>
@@ -323,12 +321,11 @@
</h4> </h4>
</div> </div>
<div class="card-body-compact"> <div class="card-body-compact">
{{-- Read-only popup: items price/total cannot be edited here --}}
@include('admin.popup_invoice', ['invoice' => $invoice, 'shipment' => $shipment]) @include('admin.popup_invoice', ['invoice' => $invoice, 'shipment' => $shipment])
</div> </div>
</div> </div>
{{-- Edit Invoice Header Details (POST) --}} {{-- Edit Invoice Header --}}
<div class="glass-card"> <div class="glass-card">
<div class="card-header-compact"> <div class="card-header-compact">
<h4> <h4>
@@ -365,61 +362,16 @@
required> required>
</div> </div>
{{-- Final Amount (Base) --}} {{-- Final Amount (With GST) Charge Groups Total --}}
<div class="form-group-compact"> <div class="form-group-compact">
<label class="form-label-compact"> <label class="form-label-compact">
<i class="fas fa-money-bill-wave"></i> Final Amount (Before GST) <i class="fas fa-money-bill-wave"></i> Final Amount (With GST)
</label> </label>
<input type="number" <input type="number"
step="0.01" step="0.01"
name="final_amount"
class="form-control-compact" class="form-control-compact"
value="{{ old('final_amount', $invoice->final_amount) }}" value="{{ $invoice->grand_total_with_charges }}"
required> readonly>
</div>
{{-- Tax Type --}}
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-receipt"></i> Tax Type
</label>
<div class="d-flex gap-3 mt-1">
<div class="form-check">
<input class="form-check-input"
type="radio"
name="tax_type"
value="gst"
{{ old('tax_type', $invoice->tax_type) === 'gst' ? 'checked' : '' }}>
<label class="form-check-label fw-semibold">GST (CGST + SGST)</label>
</div>
<div class="form-check">
<input class="form-check-input"
type="radio"
name="tax_type"
value="igst"
{{ old('tax_type', $invoice->tax_type) === 'igst' ? 'checked' : '' }}>
<label class="form-check-label fw-semibold">IGST</label>
</div>
</div>
</div>
{{-- Tax Percentage --}}
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-percentage"></i> Tax Percentage
</label>
<input type="number"
step="0.01"
min="0"
max="28"
name="tax_percent"
class="form-control-compact"
value="{{ old('tax_percent',
$invoice->tax_type === 'gst'
? ($invoice->cgst_percent + $invoice->sgst_percent)
: $invoice->igst_percent
) }}"
required>
</div> </div>
{{-- Status --}} {{-- Status --}}
@@ -428,16 +380,12 @@
<i class="fas fa-tasks"></i> Status <i class="fas fa-tasks"></i> Status
</label> </label>
<select name="status" class="form-select-compact" required> <select name="status" class="form-select-compact" required>
<option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}> <option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}>Pending</option>
Pending <option value="paying" {{ old('status', $invoice->status) === 'paying' ? 'selected' : '' }}>Paying</option>
</option> <option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}>Paid</option>
<option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}> <option value="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>Overdue</option>
Paid
</option>
<option value="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>
Overdue
</option>
</select> </select>
</div> </div>
{{-- Notes --}} {{-- Notes --}}
@@ -462,43 +410,46 @@
</div> </div>
@php @php
// आता helpers वापरू: totalPaid() आणि remainingAmount()
$totalPaid = $invoice->totalPaid(); $totalPaid = $invoice->totalPaid();
$remaining = $invoice->remainingAmount(); $remaining = $invoice->remainingAmount();
// Mixed tax type label from charge groups
$taxTypes = $invoice->chargeGroups
? $invoice->chargeGroups->pluck('tax_type')->filter()->unique()->values()
: collect([]);
$taxTypeLabel = 'None';
if ($taxTypes->count() === 1) {
if ($taxTypes[0] === 'gst') {
$taxTypeLabel = 'GST (CGST + SGST)';
} elseif ($taxTypes[0] === 'igst') {
$taxTypeLabel = 'IGST';
} else {
$taxTypeLabel = strtoupper($taxTypes[0]);
}
} elseif ($taxTypes->count() > 1) {
$parts = [];
if ($taxTypes->contains('gst')) {
$parts[] = 'GST (CGST + SGST)';
}
if ($taxTypes->contains('igst')) {
$parts[] = 'IGST';
}
$taxTypeLabel = implode(' + ', $parts);
}
@endphp @endphp
{{-- Amount Breakdown (items + GST + groups) --}} {{-- Amount Breakdown --}}
<div class="amount-breakdown-compact"> <div class="amount-breakdown-compact">
<h6 class="fw-bold mb-3 text-dark"> <h6 class="fw-bold mb-3 text-dark">
<i class="fas fa-calculator me-2"></i>Amount Breakdown <i class="fas fa-calculator me-2"></i>Amount Breakdown
</h6> </h6>
<div class="breakdown-row">
<span class="breakdown-label">Total Amount Before Tax</span>
<span class="breakdown-value" id="baseAmount">
{{ number_format($invoice->final_amount, 2) }}
</span>
</div>
<div class="breakdown-row"> <div class="breakdown-row">
<span class="breakdown-label">Tax Type</span> <span class="breakdown-label">Tax Type</span>
<span class="breakdown-value text-primary"> <span class="breakdown-value text-primary">
@if($invoice->tax_type === 'gst') {{ $taxTypeLabel }}
GST (CGST + SGST)
@else
IGST
@endif
</span>
</div>
<div class="breakdown-row">
<span class="breakdown-label">Tax Percentage</span>
<span class="breakdown-value text-primary">
@if($invoice->tax_type === 'gst')
{{ $invoice->cgst_percent + $invoice->sgst_percent }}%
@else
{{ $invoice->igst_percent }}%
@endif
</span> </span>
</div> </div>
@@ -509,15 +460,8 @@
</span> </span>
</div> </div>
<div class="breakdown-row">
<span class="breakdown-label">Charge Groups Total</span>
<span class="breakdown-value text-info" id="chargeGroupsTotal">
{{ number_format($invoice->charge_groups_total, 2) }}
</span>
</div>
<div class="breakdown-row" style="border-top: 2px solid #e2e8f0; padding-top: 0.75rem;"> <div class="breakdown-row" style="border-top: 2px solid #e2e8f0; padding-top: 0.75rem;">
<span class="breakdown-label fw-bold">Grand Total (Items + GST + Groups)</span> <span class="breakdown-label fw-bold">Grand Total (Charges + GST)</span>
<span class="breakdown-value fw-bold text-dark" id="totalInvoiceWithGst"> <span class="breakdown-value fw-bold text-dark" id="totalInvoiceWithGst">
{{ number_format($invoice->grand_total_with_charges, 2) }} {{ number_format($invoice->grand_total_with_charges, 2) }}
</span> </span>
@@ -538,13 +482,13 @@
</div> </div>
</div> </div>
{{-- Installment Summary (top cards) --}} {{-- Summary cards --}}
<div class="summary-grid-compact"> <div class="summary-grid-compact">
<div class="summary-card-compact total"> <div class="summary-card-compact total">
<div class="summary-value-compact text-success" id="totalInvoiceWithGstCard"> <div class="summary-value-compact text-success" id="totalInvoiceWithGstCard">
{{ number_format($invoice->grand_total_with_charges, 2) }} {{ number_format($invoice->grand_total_with_charges, 2) }}
</div> </div>
<div class="summary-label-compact">Grand Total (Items + GST + Groups)</div> <div class="summary-label-compact">Grand Total (Charges + GST)</div>
</div> </div>
<div class="summary-card-compact paid"> <div class="summary-card-compact paid">
<div class="summary-value-compact text-primary"> <div class="summary-value-compact text-primary">
@@ -789,15 +733,19 @@ document.addEventListener("DOMContentLoaded", function () {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining); document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
} }
if (document.getElementById("baseAmount")) { if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.baseAmount); document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
} }
if (document.getElementById("gstAmount")) { if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount); document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
} }
// grand total आता finalAmountWithGst नाही, पण API अजून तेच key देत आहे,
// त्यामुळे इथे फक्त card आणि breakdown value update करतो:
if (document.getElementById("totalInvoiceWithGst")) { if (document.getElementById("totalInvoiceWithGst")) {
document.getElementById("totalInvoiceWithGst").textContent = "" + formatINR(data.finalAmountWithGst); document.getElementById("totalInvoiceWithGst").textContent =
"" + formatINR(data.grandTotal);
}
const totalCard = document.getElementById("totalInvoiceWithGstCard");
if (totalCard) {
totalCard.textContent = "" + formatINR(data.grandTotal);
} }
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact"); const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
@@ -858,13 +806,18 @@ document.addEventListener("DOMContentLoaded", function () {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining); document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
} }
if (document.getElementById("baseAmount")) { if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.baseAmount); document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
} }
if (document.getElementById("gstAmount")) { if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount); document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
} }
if (document.getElementById("totalInvoiceWithGst")) { if (document.getElementById("totalInvoiceWithGst")) {
document.getElementById("totalInvoiceWithGst").textContent = "" + formatINR(data.finalAmountWithGst); document.getElementById("totalInvoiceWithGst").textContent =
"" + formatINR(data.grandTotal);
}
const totalCard = document.getElementById("totalInvoiceWithGstCard");
if (totalCard) {
totalCard.textContent = "" + formatINR(data.grandTotal);
} }
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact"); const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
@@ -886,9 +839,8 @@ document.addEventListener("DOMContentLoaded", function () {
alert("Something went wrong. Please try again."); alert("Something went wrong. Please try again.");
}); });
}); });
});
document.addEventListener('DOMContentLoaded', function() { // Auto due date = invoice date + 10 days
const invoiceDateInput = document.querySelector('input[name="invoice_date"]'); const invoiceDateInput = document.querySelector('input[name="invoice_date"]');
const dueDateInput = document.querySelector('input[name="due_date"]'); const dueDateInput = document.querySelector('input[name="due_date"]');

File diff suppressed because it is too large Load Diff