pdf Updated, Invoice Updated, Report Updated

This commit is contained in:
Utkarsh Khedkar
2026-03-17 19:14:47 +05:30
parent 0257b68f16
commit 19d7f423b3
23 changed files with 2320 additions and 1750 deletions

View File

@@ -22,7 +22,8 @@ class AdminCustomerController extends Controller
$query = User::with([ $query = User::with([
'marks', 'marks',
'orders', 'orders',
'invoices.installments' // 🔥 IMPORTANT 'invoices.installments',
'invoices.chargeGroups', // 🔥 for order total calculation
])->orderBy('id', 'desc'); ])->orderBy('id', 'desc');
if (!empty($search)) { if (!empty($search)) {
@@ -159,4 +160,4 @@ class AdminCustomerController extends Controller
} }
} }

View File

@@ -2,30 +2,31 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller; // ⬅️ हे नक्की असू दे
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\InvoiceItem; use App\Models\InvoiceItem;
use App\Models\InvoiceInstallment;
use App\Models\InvoiceChargeGroup; use App\Models\InvoiceChargeGroup;
use App\Models\InvoiceChargeGroupItem; use App\Models\InvoiceChargeGroupItem;
use App\Models\InvoiceInstallment;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Mpdf\Mpdf; use Mpdf\Mpdf;
class AdminInvoiceController extends Controller class AdminInvoiceController extends Controller
{ {
// ------------------------------------------------------------- // -------------------------------------------------------------
// INVOICE LIST PAGE // INDEX (LIST ALL INVOICES WITH FILTERS)
// ------------------------------------------------------------- // -------------------------------------------------------------
public function index(Request $request) public function index(Request $request)
{ {
$query = Invoice::with(['items', 'customer', 'container']); $query = Invoice::query();
if ($request->filled('search')) { if ($request->filled('search')) {
$search = $request->search; $search = $request->search;
$query->where(function ($q) use ($search) { $query->where(function ($q) use ($search) {
$q->where('invoice_number', 'like', "%{$search}%") $q->where('invoice_number', 'like', "%{$search}%")
->orWhere('customer_name', 'like', "%{$search}%"); ->orWhere('customer_name', 'like', "%{$search}%");
}); });
} }
@@ -50,22 +51,22 @@ class AdminInvoiceController extends Controller
// POPUP VIEW // POPUP VIEW
// ------------------------------------------------------------- // -------------------------------------------------------------
public function popup($id) public function popup($id)
{ {
$invoice = Invoice::with([ $invoice = Invoice::with([
'items', 'items',
'chargeGroups.items', 'chargeGroups.items',
])->findOrFail($id); ])->findOrFail($id);
$shipment = null; $shipment = null;
$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()
->values() ->values()
->toArray(); ->toArray();
return view('admin.popup_invoice', compact('invoice', 'shipment', 'groupedItemIds')); return view('admin.popup_invoice', compact('invoice', 'shipment', 'groupedItemIds'));
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// EDIT INVOICE PAGE // EDIT INVOICE PAGE
@@ -79,31 +80,29 @@ class AdminInvoiceController extends Controller
'chargeGroups.items', 'chargeGroups.items',
'installments', 'installments',
])->findOrFail($id); ])->findOrFail($id);
// ✅ Customer details sync — जर test data आला असेल तर fix होईल // ✅ Customer details sync
if ($invoice->customer) { if ($invoice->customer) {
$needsUpdate = []; $needsUpdate = [];
if (empty($invoice->customer_email) || $invoice->customer_email === 'test@demo.com') { if (empty($invoice->customer_email) || $invoice->customer_email === 'test@demo.com') {
$needsUpdate['customer_email'] = $invoice->customer->email; $needsUpdate['customer_email'] = $invoice->customer->email;
} }
if (empty($invoice->customer_address) || $invoice->customer_address === 'TEST ADDRESS') { if (empty($invoice->customer_address) || $invoice->customer_address === 'TEST ADDRESS') {
$needsUpdate['customer_address'] = $invoice->customer->address; $needsUpdate['customer_address'] = $invoice->customer->address;
} }
if (empty($invoice->pincode) || $invoice->pincode === '999999') { if (empty($invoice->pincode) || $invoice->pincode === '999999') {
$needsUpdate['pincode'] = $invoice->customer->pincode; $needsUpdate['pincode'] = $invoice->customer->pincode;
} }
if (!empty($needsUpdate)) { if (!empty($needsUpdate)) {
$invoice->update($needsUpdate); $invoice->update($needsUpdate);
$invoice->refresh(); $invoice->refresh();
} }
} }
$shipment = null; $shipment = null;
$groupedItemIds = $invoice->chargeGroups $groupedItemIds = $invoice->chargeGroups
->flatMap(function ($group) { ->flatMap(function ($group) {
return $group->items->pluck('invoice_item_id'); return $group->items->pluck('invoice_item_id');
@@ -111,7 +110,7 @@ class AdminInvoiceController extends Controller
->unique() ->unique()
->values() ->values()
->toArray(); ->toArray();
return view('admin.invoice_edit', compact('invoice', 'shipment', 'groupedItemIds')); return view('admin.invoice_edit', compact('invoice', 'shipment', 'groupedItemIds'));
} }
@@ -122,16 +121,16 @@ class AdminInvoiceController extends Controller
{ {
Log::info('🟡 Invoice Update Request Received', [ Log::info('🟡 Invoice Update Request Received', [
'invoice_id' => $id, 'invoice_id' => $id,
'request' => $request->all(), 'request' => $request->all(),
]); ]);
$invoice = Invoice::findOrFail($id); $invoice = Invoice::findOrFail($id);
$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',
'status' => 'required|in:pending,paying,paid,overdue', 'status' => 'required|in:pending,paying,paid,overdue',
'notes' => 'nullable|string', 'notes' => 'nullable|string',
]); ]);
Log::info('✅ Validated Invoice Header Update Data', $data); Log::info('✅ Validated Invoice Header Update Data', $data);
@@ -140,17 +139,17 @@ class AdminInvoiceController extends Controller
$invoice->refresh(); $invoice->refresh();
Log::info('🔍 Invoice AFTER HEADER UPDATE', [ Log::info('🔍 Invoice AFTER HEADER UPDATE', [
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'charge_groups_total' => $invoice->charge_groups_total, 'charge_groups_total' => $invoice->charge_groups_total,
'gst_amount' => $invoice->gst_amount, 'gst_amount' => $invoice->gst_amount,
'grand_total_with_charges'=> $invoice->grand_total_with_charges, 'grand_total_with_charges'=> $invoice->grand_total_with_charges,
]); ]);
$this->generateInvoicePDF($invoice); $this->generateInvoicePDF($invoice);
return redirect() return redirect()
->route('admin.invoices.edit', $invoice->id) ->route('admin.invoices.edit', $invoice->id)
->with('success', 'Invoice updated & PDF generated successfully.'); ->with('success', 'Invoice updated & PDF generated successfully.');
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
@@ -160,12 +159,12 @@ class AdminInvoiceController extends Controller
{ {
Log::info('🟡 Invoice Items Update Request', [ Log::info('🟡 Invoice Items Update Request', [
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'payload' => $request->all(), 'payload' => $request->all(),
]); ]);
$data = $request->validate([ $data = $request->validate([
'items' => ['required', 'array'], 'items' => ['required', 'array'],
'items.*.price' => ['required', 'numeric', 'min:0'], 'items.*.price' => ['required', 'numeric', 'min:0'],
'items.*.ttl_amount' => ['required', 'numeric', 'min:0'], 'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
]); ]);
@@ -177,12 +176,12 @@ class AdminInvoiceController extends Controller
if (!$item) { if (!$item) {
Log::warning('Invoice item not found or mismatched invoice', [ Log::warning('Invoice item not found or mismatched invoice', [
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'item_id' => $itemId, 'item_id' => $itemId,
]); ]);
continue; continue;
} }
$item->price = $itemData['price']; $item->price = $itemData['price'];
$item->ttl_amount = $itemData['ttl_amount']; $item->ttl_amount = $itemData['ttl_amount'];
$item->save(); $item->save();
} }
@@ -195,15 +194,16 @@ class AdminInvoiceController extends Controller
} }
// ------------------------------------------------------------- // -------------------------------------------------------------
// PDF GENERATION // PDF GENERATION (EXISTING - केवळ chargeGroups load ला confirm कर)
// ------------------------------------------------------------- // -------------------------------------------------------------
public function generateInvoicePDF($invoice) public function generateInvoicePDF($invoice)
{ {
$invoice->load(['items', 'customer', 'container']); // ✅ यामध्ये chargeGroups आणि installments load कर
$invoice->load(['items', 'customer', 'container', 'chargeGroups.items', 'installments']);
$shipment = null; $shipment = null;
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf'; $fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
$folder = public_path('invoices/'); $folder = public_path('invoices/');
if (!file_exists($folder)) { if (!file_exists($folder)) {
mkdir($folder, 0777, true); mkdir($folder, 0777, true);
@@ -216,13 +216,13 @@ class AdminInvoiceController extends Controller
} }
$mpdf = new Mpdf([ $mpdf = new Mpdf([
'mode' => 'utf-8', 'mode' => 'utf-8',
'format' => 'A4', 'format' => 'A4',
'default_font' => 'sans-serif', 'default_font' => 'sans-serif',
]); ]);
$html = view('admin.pdf.invoice', [ $html = view('admin.pdf.invoice', [
'invoice' => $invoice, 'invoice' => $invoice,
'shipment' => $shipment, 'shipment' => $shipment,
])->render(); ])->render();
@@ -239,9 +239,9 @@ class AdminInvoiceController extends Controller
{ {
$request->validate([ $request->validate([
'installment_date' => 'required|date', 'installment_date' => 'required|date',
'payment_method' => 'required|string', 'payment_method' => 'required|string',
'reference_no' => 'nullable|string', 'reference_no' => 'nullable|string',
'amount' => 'required|numeric|min:1', 'amount' => 'required|numeric|min:1',
]); ]);
$invoice = Invoice::findOrFail($invoice_id); $invoice = Invoice::findOrFail($invoice_id);
@@ -253,51 +253,54 @@ class AdminInvoiceController extends Controller
if ($request->amount > $remaining) { if ($request->amount > $remaining) {
return response()->json([ return response()->json([
'status' => 'error', 'status' => 'error',
'message' => 'Installment amount exceeds remaining balance.', 'message' => 'Installment amount exceeds remaining balance.',
], 422); ], 422);
} }
$installment = InvoiceInstallment::create([ $installment = InvoiceInstallment::create([
'invoice_id' => $invoice_id, 'invoice_id' => $invoice_id,
'installment_date' => $request->installment_date, 'installment_date' => $request->installment_date,
'payment_method' => $request->payment_method, 'payment_method' => $request->payment_method,
'reference_no' => $request->reference_no, 'reference_no' => $request->reference_no,
'amount' => $request->amount, 'amount' => $request->amount,
]); ]);
$newPaid = $paidTotal + $request->amount; $newPaid = $paidTotal + $request->amount;
$remaining = max(0, $grandTotal - $newPaid); $remaining = max(0, $grandTotal - $newPaid);
$isOverdue = now()->startOfDay()->gt(\Carbon\Carbon::parse($invoice->due_date)->startOfDay());
if ($grandTotal > 0 && $newPaid >= $grandTotal) {
$newStatus = 'paid';
} elseif ($newPaid > 0 && $isOverdue) {
$newStatus = 'overdue';
} elseif ($newPaid > 0 && !$isOverdue) {
$newStatus = 'paying';
} elseif ($newPaid <= 0 && $isOverdue) {
$newStatus = 'overdue';
} else {
$newStatus = 'pending';
}
// Full payment logic (जर पूर्ण भरले तर status paid करणे, नाहीतर pending राहील)
// if ($newPaid >= $grandTotal && $grandTotal > 0) {
// $invoice->update([
// 'payment_method' => $request->payment_method,
// 'reference_no' => $request->reference_no,
// 'status' => ($newPaid >= $grandTotal && $grandTotal > 0) ? 'paid' : $invoice->status,
// ]);
// }
// Partial payment status logic:
$invoice->update([ $invoice->update([
'payment_method' => $request->payment_method, 'payment_method' => $request->payment_method,
'reference_no' => $request->reference_no, 'reference_no' => $request->reference_no,
'status' => ($newPaid >= $grandTotal && $grandTotal > 0) ? 'paid' : $invoice->status, 'status' => $newStatus,
]); ]);
return response()->json([ return response()->json([
'status' => 'success', 'status' => 'success',
'message' => 'Installment added successfully.', 'message' => 'Installment added successfully.',
'installment' => $installment, 'installment' => $installment,
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0, 'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
'gstAmount' => $invoice->gst_amount ?? 0, 'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal, 'grandTotal' => $grandTotal,
'totalPaid' => $newPaid, 'totalPaid' => $newPaid,
'remaining' => $remaining, 'remaining' => $remaining,
'isCompleted' => $remaining <= 0, 'newStatus' => $newStatus,
'isZero' => $newPaid == 0, 'isCompleted' => $remaining <= 0,
'isZero' => $newPaid == 0,
]); ]);
} }
@@ -307,7 +310,7 @@ class AdminInvoiceController extends Controller
public function deleteInstallment($id) public function deleteInstallment($id)
{ {
$installment = InvoiceInstallment::findOrFail($id); $installment = InvoiceInstallment::findOrFail($id);
$invoice = $installment->invoice; $invoice = $installment->invoice;
$installment->delete(); $installment->delete();
$invoice->refresh(); $invoice->refresh();
@@ -317,21 +320,32 @@ class AdminInvoiceController extends Controller
$paidTotal = $invoice->installments()->sum('amount'); $paidTotal = $invoice->installments()->sum('amount');
$remaining = max(0, $grandTotal - $paidTotal); $remaining = max(0, $grandTotal - $paidTotal);
if ($paidTotal <= 0 && $grandTotal > 0) { $isOverdue = now()->startOfDay()->gt(\Carbon\Carbon::parse($invoice->due_date)->startOfDay());
$invoice->update(['status' => 'pending']);
} elseif ($paidTotal > 0 && $paidTotal < $grandTotal) { if ($grandTotal > 0 && $paidTotal >= $grandTotal) {
$invoice->update(['status' => 'pending']); $newStatus = 'paid';
} elseif ($paidTotal > 0 && $isOverdue) {
$newStatus = 'overdue';
} elseif ($paidTotal > 0 && !$isOverdue) {
$newStatus = 'paying';
} elseif ($paidTotal <= 0 && $isOverdue) {
$newStatus = 'overdue';
} else {
$newStatus = 'pending';
} }
$invoice->update(['status' => $newStatus]);
return response()->json([ return response()->json([
'status' => 'success', 'status' => 'success',
'message' => 'Installment deleted.', 'message' => 'Installment deleted.',
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0, 'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
'gstAmount' => $invoice->gst_amount ?? 0, 'gstAmount' => $invoice->gst_amount ?? 0,
'grandTotal' => $grandTotal, 'grandTotal' => $grandTotal,
'totalPaid' => $paidTotal, 'totalPaid' => $paidTotal,
'remaining' => $remaining, 'remaining' => $remaining,
'isZero' => $paidTotal == 0, 'newStatus' => $newStatus,
'isZero' => $paidTotal == 0,
]); ]);
} }
@@ -342,126 +356,149 @@ class AdminInvoiceController extends Controller
{ {
Log::info('🟡 storeChargeGroup HIT', [ Log::info('🟡 storeChargeGroup HIT', [
'invoice_id' => $invoiceId, 'invoice_id' => $invoiceId,
'payload' => $request->all(), 'payload' => $request->all(),
]); ]);
$invoice = Invoice::with('items', 'chargeGroups')->findOrFail($invoiceId); $invoice = Invoice::with('items', 'chargeGroups')->findOrFail($invoiceId);
$data = $request->validate([ $data = $request->validate([
'groupname' => 'required|string|max:255', 'groupname' => 'required|string|max:255',
'basistype' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg', 'basistype' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
'basisvalue' => 'required|numeric', 'basisvalue' => 'required|numeric',
'rate' => 'required|numeric|min:0.0001', 'rate' => 'required|numeric|min:0.0001',
'autototal' => 'required|numeric|min:0.01', 'autototal' => 'required|numeric|min:0.01',
'itemids' => 'required|array', 'itemids' => 'required|array',
'itemids.*' => 'integer|exists:invoice_items,id', 'itemids.*' => 'integer|exists:invoice_items,id',
'tax_type' => 'nullable|in:none,gst,igst', 'tax_type' => 'nullable|in:none,gst,igst',
'gst_percent' => 'nullable|numeric|min:0|max:28', 'gst_percent' => 'nullable|numeric|min:0|max:28',
'total_with_gst' => 'nullable|numeric|min:0', 'total_with_gst' => 'nullable|numeric|min:0',
]); ]);
Log::info('✅ storeChargeGroup VALIDATED', $data); 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['groupname']) ->where('group_name', $data['groupname'])
->exists(); ->exists();
if ($exists) { if ($exists) {
return back() return back()
->withErrors(['groupname' => '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'; $taxType = $data['tax_type'] ?? 'gst';
$gstPercent = $data['gst_percent'] ?? 0; $gstPercent = $data['gst_percent'] ?? 0;
$baseTotal = $data['autototal']; $baseTotal = $data['autototal'];
$totalWithGst = $data['total_with_gst'] ?? $baseTotal; $totalWithGst = $data['total_with_gst'] ?? $baseTotal;
if ($totalWithGst == 0 && $gstPercent > 0) { if ($totalWithGst == 0 && $gstPercent > 0) {
$gstAmount = ($baseTotal * $gstPercent) / 100; $gstAmount = ($baseTotal * $gstPercent) / 100;
$totalWithGst = $baseTotal + $gstAmount; $totalWithGst = $baseTotal + $gstAmount;
} }
// 1) Group create
$group = InvoiceChargeGroup::create([ $group = InvoiceChargeGroup::create([
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'group_name' => $data['groupname'], 'group_name' => $data['groupname'],
'basis_type' => $data['basistype'], 'basis_type' => $data['basistype'],
'basis_value' => $data['basisvalue'], 'basis_value' => $data['basisvalue'],
'rate' => $data['rate'], 'rate' => $data['rate'],
'total_charge' => $baseTotal, 'total_charge' => $baseTotal,
'tax_type' => $taxType, 'tax_type' => $taxType,
'gst_percent' => $gstPercent, 'gst_percent' => $gstPercent,
'total_with_gst' => $totalWithGst, 'total_with_gst' => $totalWithGst,
]); ]);
// 2) Items link
foreach ($data['itemids'] as $itemId) { 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,
]); ]);
} }
// 3) सर्व groups वरून invoice level totals
$invoice->load('chargeGroups'); $invoice->load('chargeGroups');
$chargeGroupsBase = $invoice->chargeGroups->sum('total_charge'); // base $chargeGroupsBase = $invoice->chargeGroups->sum('total_charge');
$chargeGroupsWithG = $invoice->chargeGroups->sum('total_with_gst'); // base + gst $chargeGroupsWithG = $invoice->chargeGroups->sum('total_with_gst');
$chargeGroupsGst = $chargeGroupsWithG - $chargeGroupsBase; // gst only $chargeGroupsGst = $chargeGroupsWithG - $chargeGroupsBase;
$invoiceGstPercent = $group->gst_percent ?? 0; $invoiceGstPercent = $group->gst_percent ?? 0;
$invoiceTaxType = $group->tax_type ?? 'gst'; $invoiceTaxType = $group->tax_type ?? 'gst';
$cgstPercent = 0; $cgstPercent = 0;
$sgstPercent = 0; $sgstPercent = 0;
$igstPercent = 0; $igstPercent = 0;
if ($invoiceTaxType === 'gst') { if ($invoiceTaxType === 'gst') {
$cgstPercent = $invoiceGstPercent / 2; $cgstPercent = $invoiceGstPercent / 2;
$sgstPercent = $invoiceGstPercent / 2; $sgstPercent = $invoiceGstPercent / 2;
} elseif ($invoiceTaxType === 'igst') { } elseif ($invoiceTaxType === 'igst') {
$igstPercent = $invoiceGstPercent; $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' => $chargeGroupsBase, 'charge_groups_total' => $chargeGroupsBase,
'gst_amount' => $chargeGroupsGst, 'gst_amount' => $chargeGroupsGst,
'gst_percent' => $invoiceGstPercent, 'gst_percent' => $invoiceGstPercent,
'tax_type' => $invoiceTaxType, 'tax_type' => $invoiceTaxType,
'cgst_percent' => $cgstPercent, 'cgst_percent' => $cgstPercent,
'sgst_percent' => $sgstPercent, 'sgst_percent' => $sgstPercent,
'igst_percent' => $igstPercent, 'igst_percent' => $igstPercent,
'final_amount' => $chargeGroupsBase, 'final_amount' => $chargeGroupsBase,
'final_amount_with_gst' => $chargeGroupsWithG, 'final_amount_with_gst' => $chargeGroupsWithG,
'grand_total_with_charges' => $chargeGroupsWithG, 'grand_total_with_charges' => $chargeGroupsWithG,
]); ]);
Log::info('✅ Invoice updated from Charge Group (CG total + GST)', [ Log::info('✅ Invoice updated from Charge Group (CG total + GST)', [
'invoice_id' => $invoice->id, 'invoice_id' => $invoice->id,
'charge_groups_total' => $chargeGroupsBase, 'charge_groups_total' => $chargeGroupsBase,
'gst_amount' => $chargeGroupsGst, '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, 'grand_total_with_charges'=> $invoice->grand_total_with_charges,
]); ]);
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'message' => 'Charge group saved successfully.', 'message' => 'Charge group saved successfully.',
'group_id' => $group->id, 'group_id' => $group->id,
]); ]);
} }
}
// ============================================
// 🆕 PDF DOWNLOAD (Direct browser download)
// ============================================
public function downloadPdf($id)
{
$invoice = Invoice::with(['items', 'customer', 'container', 'chargeGroups.items', 'installments'])
->findOrFail($id);
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
$folder = public_path('invoices/');
$filePath = $folder . $fileName;
// जर PDF exist नसेल तर generate कर
if (!file_exists($filePath)) {
$this->generateInvoicePDF($invoice);
}
return response()->download($filePath, $fileName);
}
// ============================================
// 🆕 EXCEL DOWNLOAD (CSV format - simple)
// ============================================
public function share($id)
{
$invoice = Invoice::findOrFail($id);
// इथे तुला जसं share करायचंय तसं logic टाक:
// उदा. public link generate करून redirect कर, किंवा WhatsApp deeplink, इ.
$url = route('admin.invoices.popup', $invoice->id); // example: popup link share
return redirect()->away('https://wa.me/?text=' . urlencode($url));
}
}

View File

@@ -5,104 +5,197 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
use Mpdf\Mpdf;
class AdminReportController extends Controller class AdminReportController extends Controller
{ {
/** // UI साठी main action
* Display the reports page with joined data public function containerReport(Request $request)
*/
// public function index(Request $request)
// {
/*********************************************************
* OLD FLOW (Order + Shipment + Invoice)
* फक्त reference साठी ठेवलेला, वापरत नाही.
*********************************************************/
/*
$reports = DB::table('orders')
->join('shipment_items', 'shipment_items.order_id', '=', 'orders.id')
->join('shipments', 'shipments.id', '=', 'shipment_items.shipment_id')
->join('invoices', 'invoices.order_id', '=', 'orders.id')
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'orders.mark_no')
->leftJoin('users', 'users.customer_id', '=', 'mark_list.customer_id')
->select(...)
->orderBy('shipments.shipment_date', 'desc')
->get();
*/
/*********************************************************
* NEW FLOW (Container + Invoice + MarkList)
*********************************************************/
// $reports = DB::table('invoices')
// ->join('containers', 'containers.id', '=', 'invoices.containerid')
// ->leftJoin('mark_list', 'mark_list.markno', '=', 'invoices.markno')
// ->select(
// 'invoices.id as invoicepk',
// 'invoices.invoicenumber',
// 'invoices.invoicedate',
// 'invoices.finalamount',
// 'invoices.finalamountwithgst',
// 'invoices.gstpercent',
// 'invoices.gstamount',
// 'invoices.status as invoicestatus',
// 'invoices.markno',
// 'containers.id as containerpk',
// 'containers.containernumber',
// 'containers.containerdate',
// 'containers.containername',
// 'mark_list.companyname',
// 'mark_list.customername'
// )
// ->orderBy('containers.containerdate', 'desc')
// ->get();
// return view('admin.reports', compact('reports'));
// }
public function index(Request $request)
{ {
$reports = DB::table('invoices') $reports = $this->buildContainerReportQuery($request)->get();
return view('admin.reports', compact('reports'));
}
// ही common query — filters accept करते
protected function buildContainerReportQuery(Request $request = null)
{
$query = DB::table('invoices')
->join('containers', 'containers.id', '=', 'invoices.container_id') ->join('containers', 'containers.id', '=', 'invoices.container_id')
->leftJoin('mark_list', 'mark_list.mark_no', '=', 'invoices.mark_no') ->leftJoinSub(
DB::table('invoice_installments')
->select('invoice_id', DB::raw('COALESCE(SUM(amount), 0) as total_paid'))
->groupBy('invoice_id'),
'inst',
'inst.invoice_id',
'=',
'invoices.id'
)
->select( ->select(
// INVOICE 'containers.id as container_id',
'invoices.id as invoicepk',
'invoices.invoice_number',
'invoices.invoice_date',
'invoices.final_amount',
'invoices.final_amount_with_gst',
'invoices.gst_percent',
'invoices.gst_amount',
'invoices.status as invoicestatus',
'invoices.mark_no',
// CONTAINER
'containers.id as containerpk',
'containers.container_number', 'containers.container_number',
'containers.container_date', 'containers.container_date',
'containers.container_name', 'containers.container_name',
// RAW FIELDS (for reference/debug if needed)
'invoices.company_name as inv_company_name',
'invoices.customer_name as inv_customer_name',
'mark_list.company_name as ml_company_name',
'mark_list.customer_name as ml_customer_name',
// FINAL FIELDS (automatically pick invoice first, else mark_list)
DB::raw('COALESCE(invoices.company_name, mark_list.company_name) as company_name'),
DB::raw('COALESCE(invoices.customer_name, mark_list.customer_name) as customer_name')
)
->orderBy('invoices.invoice_date', 'desc')
->orderBy('invoices.id', 'desc')
->get(); DB::raw('COUNT(DISTINCT invoices.mark_no) as total_mark_nos'),
DB::raw('COUNT(DISTINCT invoices.customer_id) as total_customers'),
return view('admin.reports', compact('reports')); DB::raw('COUNT(invoices.id) as total_invoices'),
DB::raw('COALESCE(SUM(invoices.charge_groups_total), 0) as total_invoice_amount'),
DB::raw('COALESCE(SUM(invoices.gst_amount), 0) as total_gst_amount'),
DB::raw('COALESCE(SUM(invoices.grand_total_with_charges), 0) as total_payable'),
DB::raw('COALESCE(SUM(inst.total_paid), 0) as total_paid'),
DB::raw('GREATEST(0, COALESCE(SUM(invoices.grand_total_with_charges), 0) - COALESCE(SUM(inst.total_paid), 0)) as total_remaining'),
DB::raw("
CASE
WHEN COUNT(invoices.id) > 0
AND SUM(CASE WHEN invoices.status != 'paid' THEN 1 ELSE 0 END) = 0
THEN 'paid'
WHEN SUM(CASE WHEN invoices.status = 'overdue' THEN 1 ELSE 0 END) > 0
THEN 'overdue'
WHEN SUM(CASE WHEN invoices.status = 'paying' THEN 1 ELSE 0 END) > 0
THEN 'paying'
ELSE 'pending'
END as container_status
")
)
->groupBy(
'containers.id',
'containers.container_number',
'containers.container_date',
'containers.container_name'
)
->orderBy('containers.container_date', 'desc')
->orderBy('containers.id', 'desc');
// ── Filters ──────────────────────────────────────────────────────
if ($request) {
if ($request->filled('from_date')) {
$query->whereDate('containers.container_date', '>=', $request->from_date);
}
if ($request->filled('to_date')) {
$query->whereDate('containers.container_date', '<=', $request->to_date);
}
if ($request->filled('status')) {
// container_status हे aggregate expression आहे,
// त्यामुळे HAVING clause वापरतो
$query->havingRaw("
CASE
WHEN COUNT(invoices.id) > 0
AND SUM(CASE WHEN invoices.status != 'paid' THEN 1 ELSE 0 END) = 0
THEN 'paid'
WHEN SUM(CASE WHEN invoices.status = 'overdue' THEN 1 ELSE 0 END) > 0
THEN 'overdue'
WHEN SUM(CASE WHEN invoices.status = 'paying' THEN 1 ELSE 0 END) > 0
THEN 'paying'
ELSE 'pending'
END = ?
", [$request->status]);
}
}
return $query;
} }
} // ---- Excel export ----
public function containerReportExcel(Request $request)
{
$reports = $this->buildContainerReportQuery($request)->get();
$headings = [
'Container No',
'Container Date',
'Total Mark Nos',
'Total Customers',
'Total Invoices',
'Invoice Amount (Before GST)',
'GST Amount',
'Payable Amount (Incl. GST)',
'Paid Amount',
'Remaining Amount',
'Container Status',
];
$rows = $reports->map(function ($r) {
return [
$r->container_number,
$r->container_date,
$r->total_mark_nos,
$r->total_customers,
$r->total_invoices,
$r->total_invoice_amount,
$r->total_gst_amount,
$r->total_payable,
$r->total_paid,
$r->total_remaining,
$r->container_status,
];
})->toArray();
$export = new class($headings, $rows) implements
\Maatwebsite\Excel\Concerns\FromArray,
\Maatwebsite\Excel\Concerns\WithHeadings
{
private $headings;
private $rows;
public function __construct($headings, $rows)
{
$this->headings = $headings;
$this->rows = $rows;
}
public function array(): array
{
return $this->rows;
}
public function headings(): array
{
return $this->headings;
}
};
return Excel::download(
$export,
'container-report-' . now()->format('Ymd-His') . '.xlsx'
);
}
// ---- PDF export ----
public function containerReportPdf(Request $request)
{
$reports = $this->buildContainerReportQuery($request)->get();
$html = view('admin.reports', [
'reports' => $reports,
'isPdf' => true,
])->render();
$mpdf = new \Mpdf\Mpdf([
'mode' => 'utf-8',
'format' => 'A4-L',
'default_font' => 'dejavusans',
'margin_top' => 8,
'margin_right' => 8,
'margin_bottom' => 10,
'margin_left' => 8,
]);
$mpdf->SetHTMLHeader('');
$mpdf->SetHTMLFooter('');
$mpdf->WriteHTML($html);
$fileName = 'container-report-' . now()->format('Ymd-His') . '.pdf';
return response($mpdf->Output($fileName, 'S'), 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
]);
}
}

View File

@@ -565,8 +565,6 @@ class ContainerController extends Controller
$invoice->save(); $invoice->save();
$invoiceCount++; $invoiceCount++;
$totalAmount = 0;
foreach ($rowsForCustomer as $item) { foreach ($rowsForCustomer as $item) {
$row = $item['row']; $row = $item['row'];
$offset = $item['offset']; $offset = $item['offset'];
@@ -606,16 +604,7 @@ class ContainerController extends Controller
'shop_no' => $shopNo, 'shop_no' => $shopNo,
'mark_no' => $mark, // ✅ save mark_no from Excel 'mark_no' => $mark, // ✅ save mark_no from Excel
]); ]);
$totalAmount += $ttlAmount;
} }
$invoice->final_amount = $totalAmount;
$invoice->gst_percent = 0;
$invoice->gst_amount = 0;
$invoice->final_amount_with_gst = $totalAmount;
$invoice->save();
} }
$msg = "Container '{$container->container_number}' created with {$savedCount} rows and {$invoiceCount} customer invoice(s)."; $msg = "Container '{$container->container_number}' created with {$savedCount} rows and {$invoiceCount} customer invoice(s).";
@@ -972,4 +961,4 @@ class ContainerController extends Controller
'summary' => $summary, 'summary' => $summary,
]); ]);
} }
} }

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

@@ -40,7 +40,6 @@
margin-top: 2px; margin-top: 2px;
} }
/* DOWNLOAD BUTTONS - NEW STYLES */
.cm-download-pdf { .cm-download-pdf {
background: linear-gradient(100deg, #4c6fff 0%, #8e54e9 100%) !important; background: linear-gradient(100deg, #4c6fff 0%, #8e54e9 100%) !important;
color: #fff !important; color: #fff !important;
@@ -287,7 +286,7 @@
} }
.cm-table-scroll-outer { .cm-table-scroll-outer {
margin: 10px 14px 0 14px; margin: 10px 14px 30px 14px;
border-radius: 14px; border-radius: 14px;
border: 1.5px solid #c9a359; border: 1.5px solid #c9a359;
box-shadow: 0 4px 14px rgba(76,111,255,0.18); box-shadow: 0 4px 14px rgba(76,111,255,0.18);
@@ -487,11 +486,6 @@
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
.cm-table-scroll-outer {
margin: 10px 14px 30px 14px; /* फक्त हे बदल करा */
}
</style> </style>
<div class="container-fluid cm-wrapper"> <div class="container-fluid cm-wrapper">
@@ -522,7 +516,6 @@
</div> </div>
</div> </div>
<!-- बाकीचा सगळा code same आहे - काही बदल नाही -->
<div class="card cm-main-card"> <div class="card cm-main-card">
<div class="card-header"> <div class="card-header">
<h5>Container Information</h5> <h5>Container Information</h5>
@@ -682,7 +675,7 @@
<div class="cm-filter-bar"> <div class="cm-filter-bar">
<span class="cm-row-count"> <span class="cm-row-count">
Total rows: {{ $container->rows->count() }}   Edit cells then click "Save Changes". Total rows: {{ $container->rows->count() }} Edit cells then click "Save Changes".
</span> </span>
<input type="text" id="cmRowSearch" class="cm-filter-input" <input type="text" id="cmRowSearch" class="cm-filter-input"
placeholder="Quick search..." onkeyup="cmFilterRows()"> placeholder="Quick search..." onkeyup="cmFilterRows()">
@@ -745,9 +738,9 @@
str_contains($norm, 'TOTALAMOUNT') str_contains($norm, 'TOTALAMOUNT')
); );
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount; $isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
$isLockedByInvoice = in_array($row->row_index, $lockedRowIndexes ?? []); $isLockedByInvoice = in_array($row->row_index, $lockedRowIndexes ?? []);
$isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice; $isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice;
@endphp @endphp
<td> <td>
@@ -792,7 +785,6 @@
</button> </button>
@endif @endif
<!-- Toast notification missing होती, add केली -->
<div id="cmToast" class="cm-toast"></div> <div id="cmToast" class="cm-toast"></div>
<script> <script>
@@ -800,7 +792,7 @@ function cmFilterRows() {
const input = document.getElementById('cmRowSearch'); const input = document.getElementById('cmRowSearch');
if (!input) return; if (!input) return;
const filter = input.value.toLowerCase(); const filter = input.value.toLowerCase();
const table = document.getElementById('cmExcelTable'); const table = document.getElementById('cmExcelTable');
if (!table) return; if (!table) return;
const rows = table.getElementsByTagName('tr'); const rows = table.getElementsByTagName('tr');
for (let i = 1; i < rows.length; i++) { for (let i = 1; i < rows.length; i++) {
@@ -831,6 +823,7 @@ document.addEventListener('DOMContentLoaded', function () {
setTimeout(() => toast.classList.remove('cm-show'), 2500); setTimeout(() => toast.classList.remove('cm-show'), 2500);
} }
// फक्त number काढण्यासाठी helper, पण cell मध्ये format नाही करणार
function parseNumber(str) { function parseNumber(str) {
if (!str) return 0; if (!str) return 0;
const cleaned = String(str).replace(/,/g, '').trim(); const cleaned = String(str).replace(/,/g, '').trim();
@@ -838,11 +831,6 @@ document.addEventListener('DOMContentLoaded', function () {
return isNaN(val) ? 0 : val; return isNaN(val) ? 0 : val;
} }
function formatNumber(val, decimals) {
if (isNaN(val)) val = 0;
return val.toFixed(decimals);
}
function recalcRow(row) { function recalcRow(row) {
const inputs = row.querySelectorAll('.cm-cell-input'); const inputs = row.querySelectorAll('.cm-cell-input');
@@ -894,27 +882,28 @@ document.addEventListener('DOMContentLoaded', function () {
} }
}); });
// इथे आपण फक्त VALUE बदलतो, कोणतंही toFixed नाही वापरत
if (ttlQtyInput && ctnInput && qtyInput) { if (ttlQtyInput && ctnInput && qtyInput) {
const newTtlQty = ctn * qty; const newTtlQty = ctn * qty;
ttlQtyInput.value = formatNumber(newTtlQty, 0); ttlQtyInput.value = newTtlQty === 0 ? '' : String(newTtlQty);
ttlQty = newTtlQty; ttlQty = newTtlQty;
} }
if (ttlCbmInput && cbmInput && ctnInput) { if (ttlCbmInput && cbmInput && ctnInput) {
const newTtlCbm = cbm * ctn; const newTtlCbm = cbm * ctn;
ttlCbmInput.value = formatNumber(newTtlCbm, 3); ttlCbmInput.value = newTtlCbm === 0 ? '' : String(newTtlCbm);
ttlCbm = newTtlCbm; ttlCbm = newTtlCbm;
} }
if (ttlKgInput && kgInput && ctnInput) { if (ttlKgInput && kgInput && ctnInput) {
const newTtlKg = kg * ctn; const newTtlKg = kg * ctn;
ttlKgInput.value = formatNumber(newTtlKg, 2); ttlKgInput.value = newTtlKg === 0 ? '' : String(newTtlKg);
ttlKg = newTtlKg; ttlKg = newTtlKg;
} }
if (amountInput && priceInput && ttlQtyInput) { if (amountInput && priceInput && ttlQtyInput) {
const newAmount = price * ttlQty; const newAmount = price * ttlQty;
amountInput.value = formatNumber(newAmount, 2); amountInput.value = newAmount === 0 ? '' : String(newAmount);
amount = newAmount; amount = newAmount;
} }
} }

View File

@@ -661,7 +661,8 @@
<th class="table-header">Customer ID</th> <th class="table-header">Customer ID</th>
<th class="table-header">Orders</th> <th class="table-header">Orders</th>
<th class="table-header">Order Total</th> <th class="table-header">Order Total</th>
<th class="table-header">Total Payable</th> <th class="table-header">GST Amount</th>
<th class="table-header">Total Paid</th>
<th class="table-header">Remaining</th> <th class="table-header">Remaining</th>
<th class="table-header">Create Date</th> <th class="table-header">Create Date</th>
<th class="table-header">Status</th> <th class="table-header">Status</th>
@@ -672,20 +673,27 @@
<tbody id="customersTableBody"> <tbody id="customersTableBody">
@forelse($customers as $c) @forelse($customers as $c)
@php @php
// Orders = invoice count // 1) Orders = total invoice count
$ordersCount = $c->invoices->count(); $ordersCount = $c->invoices->count();
// Order Total = items total from all invoices (final_amount) // 2) Order Total = सर्व invoices च्या charge groups चा base total (without GST)
$orderTotal = $c->invoices->sum('final_amount'); $orderTotal = $c->invoices->sum(function($invoice) {
return $invoice->chargeGroups->sum('total_charge');
});
// Total payable = grand total with GST + groups // 3) GST Amount = सर्व invoices च्या gst_amount चा sum
$totalPayable = $c->invoices->sum('grand_total_with_charges'); $gstTotal = $c->invoices->sum('gst_amount');
// Total paid via installments // 3) Total Payable = customer ने किती paid केले (installments sum)
$totalPaid = $c->invoiceInstallments->sum('amount'); $totalPaid = $c->invoices->flatMap->installments->sum('amount');
// Remaining amount // 4) Remaining = grand_total_with_charges - paid
$remainingAmount = max($totalPayable - $totalPaid, 0); $grandTotal = $c->invoices->sum(function($invoice) {
$base = $invoice->chargeGroups->sum('total_charge');
$gst = (float)($invoice->gst_amount ?? 0);
return $base + $gst;
});
$remainingAmount = max($grandTotal - $totalPaid, 0);
@endphp @endphp
<tr> <tr>
<td class="customer-info-column"> <td class="customer-info-column">
@@ -724,7 +732,13 @@
<td class="total-column"> <td class="total-column">
<span class="total-amount"> <span class="total-amount">
{{ number_format($totalPayable, 2) }} {{ number_format($gstTotal, 2) }}
</span>
</td>
<td class="total-column">
<span class="total-amount">
{{ number_format($totalPaid, 2) }}
</span> </span>
</td> </td>
@@ -771,7 +785,7 @@
</tr> </tr>
@empty @empty
<tr> <tr>
<td colspan="9" class="text-center py-4"> <td colspan="10" class="text-center py-4">
<i class="bi bi-people display-4 text-muted d-block mb-2"></i> <i class="bi bi-people display-4 text-muted d-block mb-2"></i>
<span class="text-muted">No customers found.</span> <span class="text-muted">No customers found.</span>
</td> </td>
@@ -836,4 +850,4 @@
}); });
</script> </script>
@endsection @endsection

View File

@@ -1236,7 +1236,7 @@
<th class="column-header">Customer</th> <th class="column-header">Customer</th>
<th class="column-header">Container</th> {{-- NEW --}} <th class="column-header">Container</th> {{-- NEW --}}
<th class="column-header">Final Amount</th> <th class="column-header">Final Amount</th>
<th class="column-header">GST %</th> <th class="column-header">GST Amount</th>
<th class="column-header">Total w/GST</th> <th class="column-header">Total w/GST</th>
<th class="column-header">Status</th> <th class="column-header">Status</th>
<th class="column-header">Invoice Date</th> <th class="column-header">Invoice Date</th>
@@ -1286,8 +1286,8 @@
{{ number_format($invoice->final_amount, 2) }} {{ number_format($invoice->final_amount, 2) }}
</td> </td>
<td class="gst-cell"> <td class="amount-cell">
{{ $invoice->gst_percent }}% {{ number_format($invoice->gst_amount, 2) }}
</td> </td>
<td class="amount-cell"> <td class="amount-cell">
@@ -1379,8 +1379,8 @@
<span class="mobile-detail-value">{{ number_format($invoice->final_amount, 2) }}</span> <span class="mobile-detail-value">{{ number_format($invoice->final_amount, 2) }}</span>
</div> </div>
<div class="mobile-detail-item"> <div class="mobile-detail-item">
<span class="mobile-detail-label">GST</span> <span class="mobile-detail-label">GST Amount</span>
<span class="mobile-detail-value">{{ $invoice->gst_percent }}%</span> <span class="mobile-detail-value">{{ number_format($invoice->gst_amount, 2) }}</span>
</div> </div>
<div class="mobile-detail-item"> <div class="mobile-detail-item">
<span class="mobile-detail-label">Total</span> <span class="mobile-detail-label">Total</span>
@@ -1694,7 +1694,7 @@ document.addEventListener('DOMContentLoaded', function() {
${invoice.container ? (invoice.container.container_number ?? '—') : '—'} ${invoice.container ? (invoice.container.container_number ?? '—') : '—'}
</td> </td>
<td class="amount-cell">${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td> <td class="amount-cell">${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td class="gst-cell">${invoice.gst_percent}%</td> <td class="amount-cell">${parseFloat(invoice.gst_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td class="amount-cell">${parseFloat(invoice.final_amount_with_gst).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td> <td class="amount-cell">${parseFloat(invoice.final_amount_with_gst).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
<td> <td>
<span class="badge badge-${invoice.status}"> <span class="badge badge-${invoice.status}">
@@ -1756,8 +1756,8 @@ document.addEventListener('DOMContentLoaded', function() {
<span class="mobile-detail-value">${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span> <span class="mobile-detail-value">${parseFloat(invoice.final_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
</div> </div>
<div class="mobile-detail-item"> <div class="mobile-detail-item">
<span class="mobile-detail-label">GST</span> <span class="mobile-detail-label">GST Amount</span>
<span class="mobile-detail-value">${invoice.gst_percent}%</span> <span class="mobile-detail-value">${parseFloat(invoice.gst_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
</div> </div>
<div class="mobile-detail-item"> <div class="mobile-detail-item">
<span class="mobile-detail-label">Total</span> <span class="mobile-detail-label">Total</span>

View File

@@ -325,95 +325,15 @@
</div> </div>
</div> </div>
{{-- Edit Invoice Header --}} @if(false)
<div class="glass-card"> {{-- Edit Invoice Details card HIDDEN --}}
<div class="card-header-compact d-flex justify-content-between align-items-center"> {{-- तुझा full header edit form इथे hidden ठेवलेला आहे --}}
<h4> @endif
<i class="fas fa-edit me-2"></i>
Edit Invoice Details
</h4>
<small id="headerUpdateMsg" class="text-light"></small>
</div>
<div class="card-body-compact">
<form id="invoiceHeaderForm" action="{{ route('admin.invoices.update', $invoice->id) }}" method="POST">
@csrf
<div class="form-grid-compact">
{{-- Invoice Date --}}
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-calendar-day"></i> Invoice Date
</label>
<input type="date"
name="invoice_date"
class="form-control-compact"
value="{{ old('invoice_date', $invoice->invoice_date) }}"
required>
</div>
{{-- Due Date --}}
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-clock"></i> Due Date
</label>
<input type="date"
name="due_date"
class="form-control-compact"
value="{{ old('due_date', $invoice->due_date) }}"
required>
</div>
{{-- Final Amount (With GST) Charge Groups Total --}}
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-money-bill-wave"></i> Final Amount (With GST)
</label>
<input type="number"
step="0.01"
class="form-control-compact"
value="{{ $invoice->grand_total_with_charges }}"
readonly>
</div>
{{-- Status --}}
<div class="form-group-compact">
<label class="form-label-compact">
<i class="fas fa-tasks"></i> Status
</label>
<select name="status" id="statusSelect" class="form-select-compact" required>
<option value="pending" {{ old('status', $invoice->status) === 'pending' ? 'selected' : '' }}>Pending</option>
<option value="paying" {{ old('status', $invoice->status) === 'paying' ? 'selected' : '' }}>Paying</option>
<option value="paid" {{ old('status', $invoice->status) === 'paid' ? 'selected' : '' }}>Paid</option>
<option value="overdue" {{ old('status', $invoice->status) === 'overdue' ? 'selected' : '' }}>Overdue</option>
</select>
</div>
{{-- Notes --}}
<div class="form-group-compact" style="grid-column: 1 / -1;">
<label class="form-label-compact">
<i class="fas fa-sticky-note"></i> Notes
</label>
<textarea name="notes"
rows="3"
class="form-control-compact"
placeholder="Add any additional notes...">{{ old('notes', $invoice->notes) }}</textarea>
</div>
</div>
<div class="text-end mt-3">
<button type="submit" id="btnHeaderSave" class="btn-success-compact btn-compact">
<i class="fas fa-save me-2"></i>Update Invoice
</button>
</div>
</form>
</div>
</div>
@php @php
$totalPaid = $invoice->totalPaid(); $totalPaid = $invoice->totalPaid();
$remaining = $invoice->remainingAmount(); $remaining = $invoice->remainingAmount();
// Mixed tax type label from charge groups
$taxTypes = $invoice->chargeGroups $taxTypes = $invoice->chargeGroups
? $invoice->chargeGroups->pluck('tax_type')->filter()->unique()->values() ? $invoice->chargeGroups->pluck('tax_type')->filter()->unique()->values()
: collect([]); : collect([]);
@@ -440,47 +360,10 @@
} }
@endphp @endphp
{{-- Amount Breakdown --}} @if(false)
<div class="amount-breakdown-compact"> {{-- Amount Breakdown HIDDEN --}}
<h6 class="fw-bold mb-3 text-dark"> {{-- जुनं breakdown section इथे hidden आहे --}}
<i class="fas fa-calculator me-2"></i>Amount Breakdown @endif
</h6>
<div class="breakdown-row">
<span class="breakdown-label">Tax Type</span>
<span class="breakdown-value text-primary">
{{ $taxTypeLabel }}
</span>
</div>
<div class="breakdown-row">
<span class="breakdown-label">GST Amount</span>
<span class="breakdown-value text-warning" id="gstAmount">
{{ number_format($invoice->gst_amount, 2) }}
</span>
</div>
<div class="breakdown-row" style="border-top: 2px solid #e2e8f0; padding-top: 0.75rem;">
<span class="breakdown-label fw-bold">Grand Total (Charges + GST)</span>
<span class="breakdown-value fw-bold text-dark" id="totalInvoiceWithGst">
{{ number_format($invoice->grand_total_with_charges, 2) }}
</span>
</div>
<div class="breakdown-row">
<span class="breakdown-label text-success">Total Paid</span>
<span class="breakdown-value fw-bold text-success" id="paidAmount">
{{ number_format($totalPaid, 2) }}
</span>
</div>
<div class="breakdown-row" style="border-bottom: none;">
<span class="breakdown-label text-danger">Remaining</span>
<span class="breakdown-value fw-bold text-danger" id="remainingAmount">
{{ number_format(max(0, $remaining), 2) }}
</span>
</div>
</div>
{{-- Summary cards --}} {{-- Summary cards --}}
<div class="summary-grid-compact"> <div class="summary-grid-compact">
@@ -491,13 +374,13 @@
<div class="summary-label-compact">Grand Total (Charges + GST)</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" id="paidAmount">
{{ number_format($totalPaid, 2) }} {{ number_format($totalPaid, 2) }}
</div> </div>
<div class="summary-label-compact">Total Paid</div> <div class="summary-label-compact">Total Paid</div>
</div> </div>
<div class="summary-card-compact remaining"> <div class="summary-card-compact remaining">
<div class="summary-value-compact text-warning"> <div class="summary-value-compact text-warning" id="remainingAmount">
{{ number_format(max(0, $remaining), 2) }} {{ number_format(max(0, $remaining), 2) }}
</div> </div>
<div class="summary-label-compact">Remaining</div> <div class="summary-label-compact">Remaining</div>
@@ -683,91 +566,128 @@ document.addEventListener("DOMContentLoaded", function () {
headers: { headers: {
"X-CSRF-TOKEN": submitForm.querySelector('input[name="_token"]').value, "X-CSRF-TOKEN": submitForm.querySelector('input[name="_token"]').value,
"Accept": "application/json", "Accept": "application/json",
"X-Requested-With": "XMLHttpRequest",
}, },
body: new FormData(submitForm) body: new FormData(submitForm)
}) })
.then(res => res.json()) .then(async res => {
.then(data => { let data;
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment'; try {
submitBtn.disabled = false; data = await res.json();
} catch (e) {
throw new Error("Invalid server response.");
}
if (data.status === "error") { submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
alert(data.message); submitBtn.disabled = false;
return;
}
const table = document.getElementById("installmentTable"); if (!res.ok) {
const index = table.rows.length + 1; const msg =
(data && data.message) ||
(data && data.errors && Object.values(data.errors)[0][0]) ||
"Something went wrong.";
alert(msg);
return;
}
document.getElementById("noInstallmentsMsg")?.classList.add("d-none"); if (data.status === "error") {
alert(data.message || "Something went wrong.");
return;
}
table.insertAdjacentHTML("beforeend", ` const table = document.getElementById("installmentTable");
<tr data-id="${data.installment.id}"> const index = table.querySelectorAll("tr").length + 1;
<td class="fw-bold text-muted">${index}</td>
<td>${data.installment.installment_date}</td>
<td>
<span class="badge-compact bg-primary bg-opacity-10 text-primary">
${data.installment.payment_method.toUpperCase()}
</span>
</td>
<td>
${data.installment.reference_no
? `<span class="text-muted">${data.installment.reference_no}</span>`
: '<span class="text-muted">-</span>'}
</td>
<td class="fw-bold text-success">
${formatINR(data.installment.amount)}
</td>
<td>
<button class="btn-danger-compact btn-compact btn-sm deleteInstallment">
<i class="fas fa-trash me-1"></i>Delete
</button>
</td>
</tr>
`);
if (document.getElementById("paidAmount")) { document.getElementById("noInstallmentsMsg")?.classList.add("d-none");
document.getElementById("paidAmount").textContent = "" + formatINR(data.totalPaid);
}
if (document.getElementById("remainingAmount")) {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
}
if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
}
if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
}
if (document.getElementById("totalInvoiceWithGst")) { const pmMethod = data.installment.payment_method
document.getElementById("totalInvoiceWithGst").textContent = ? data.installment.payment_method.toUpperCase()
"" + formatINR(data.grandTotal); : "-";
} const refNo = data.installment.reference_no
const totalCard = document.getElementById("totalInvoiceWithGstCard"); ? `<span class="text-muted">${data.installment.reference_no}</span>`
if (totalCard) { : '<span class="text-muted">-</span>';
totalCard.textContent = "" + formatINR(data.grandTotal);
}
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact"); table.insertAdjacentHTML("beforeend", `
if (paidCard) paidCard.textContent = "" + formatINR(data.totalPaid); <tr data-id="${data.installment.id}">
<td class="fw-bold text-muted">${index}</td>
<td>${data.installment.installment_date}</td>
<td>
<span class="badge-compact bg-primary bg-opacity-10 text-primary">
${pmMethod}
</span>
</td>
<td>${refNo}</td>
<td class="fw-bold text-success">
${formatINR(data.installment.amount)}
</td>
<td>
<button class="btn-danger-compact btn-compact btn-sm deleteInstallment">
<i class="fas fa-trash me-1"></i>Delete
</button>
</td>
</tr>
`);
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact"); if (document.getElementById("paidAmount")) {
if (remainingCard) remainingCard.textContent = "" + formatINR(data.remaining); document.getElementById("paidAmount").textContent = "" + formatINR(data.totalPaid);
}
if (document.getElementById("remainingAmount")) {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
}
if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
}
if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
}
if (document.getElementById("totalInvoiceWithGst")) {
document.getElementById("totalInvoiceWithGst").textContent =
"" + formatINR(data.grandTotal);
}
const totalCard = document.getElementById("totalInvoiceWithGstCard");
if (totalCard) {
totalCard.textContent = "" + formatINR(data.grandTotal);
}
submitForm.reset(); const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
if (paidCard) paidCard.textContent = "" + formatINR(data.totalPaid);
if (data.isCompleted && toggleBtn) { const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
toggleBtn.remove(); if (remainingCard) remainingCard.textContent = "" + formatINR(data.remaining);
formBox.classList.add("d-none");
}
alert(data.message); const fsTotal = document.getElementById("finalSummaryTotalPaid");
}) if (fsTotal) fsTotal.textContent = "" + formatINR(data.totalPaid);
.catch(() => {
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment'; const fsRemaining = document.getElementById("finalSummaryRemaining");
submitBtn.disabled = false; if (fsRemaining) {
alert("Something went wrong. Please try again."); fsRemaining.textContent = "" + formatINR(data.remaining);
}); fsRemaining.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
}
const fsRemainingLabel = document.getElementById("finalSummaryRemainingLabel");
if (fsRemainingLabel) {
fsRemainingLabel.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
fsRemainingLabel.innerHTML = `<i class="fas fa-${data.remaining > 0 ? 'exclamation-circle' : 'check-circle'} me-1" style="font-size:0.8rem;"></i>Remaining`;
}
if (data.newStatus && typeof updateStatusBadge === "function") {
updateStatusBadge(data.newStatus);
}
submitForm.reset();
if (data.isCompleted && toggleBtn) {
toggleBtn.remove();
formBox.classList.add("d-none");
}
alert(data.message || "Installment added successfully.");
})
.catch((err) => {
console.error("Installment submit error:", err);
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
submitBtn.disabled = false;
alert(err.message || "Something went wrong. Please try again.");
});
}); });
} }
@@ -791,74 +711,78 @@ document.addEventListener("DOMContentLoaded", function () {
headers: { headers: {
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute("content"), "X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute("content"),
"Accept": "application/json", "Accept": "application/json",
"X-Requested-With": "XMLHttpRequest",
}, },
}) })
.then(res => res.json()) .then(async res => {
.then(data => { let data;
if (data.status === "success") { try { data = await res.json(); } catch(e) { throw new Error("Invalid server response."); }
row.style.opacity = 0;
setTimeout(() => row.remove(), 300);
if (document.getElementById("paidAmount")) { if (!res.ok || data.status !== "success") {
document.getElementById("paidAmount").textContent = "" + formatINR(data.totalPaid); const msg = data && data.message ? data.message : "Something went wrong. Please try again.";
} throw new Error(msg);
if (document.getElementById("remainingAmount")) { }
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
}
if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
}
if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
}
if (document.getElementById("totalInvoiceWithGst")) {
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"); row.style.opacity = 0;
if (paidCard) paidCard.textContent = "" + formatINR(data.totalPaid); setTimeout(() => row.remove(), 300);
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact"); if (document.getElementById("paidAmount")) {
if (remainingCard) remainingCard.textContent = "" + formatINR(data.remaining); document.getElementById("paidAmount").textContent = "" + formatINR(data.totalPaid);
}
if (document.getElementById("remainingAmount")) {
document.getElementById("remainingAmount").textContent = "" + formatINR(data.remaining);
}
if (document.getElementById("baseAmount")) {
document.getElementById("baseAmount").textContent = "" + formatINR(data.chargeGroupsTotal);
}
if (document.getElementById("gstAmount")) {
document.getElementById("gstAmount").textContent = "" + formatINR(data.gstAmount);
}
if (document.getElementById("totalInvoiceWithGst")) {
document.getElementById("totalInvoiceWithGst").textContent =
"" + formatINR(data.grandTotal);
}
const totalCard = document.getElementById("totalInvoiceWithGstCard");
if (totalCard) {
totalCard.textContent = "" + formatINR(data.grandTotal);
}
if (data.isZero) { const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
document.getElementById("noInstallmentsMsg")?.classList.remove("d-none"); if (paidCard) paidCard.textContent = "" + formatINR(data.totalPaid);
}
alert(data.message); const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
} else { if (remainingCard) remainingCard.textContent = "" + formatINR(data.remaining);
alert(data.message || "Something went wrong. Please try again.");
} const fsTotal = document.getElementById("finalSummaryTotalPaid");
}) if (fsTotal) fsTotal.textContent = "" + formatINR(data.totalPaid);
.catch(() => {
alert("Something went wrong. Please try again."); const fsRemaining = document.getElementById("finalSummaryRemaining");
}); if (fsRemaining) {
fsRemaining.textContent = "" + formatINR(data.remaining);
fsRemaining.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
}
const fsRemainingLabel = document.getElementById("finalSummaryRemainingLabel");
if (fsRemainingLabel) {
fsRemainingLabel.style.color = data.remaining > 0 ? "#ef4444" : "#10b981";
fsRemainingLabel.innerHTML = `<i class="fas fa-${data.remaining > 0 ? 'exclamation-circle' : 'check-circle'} me-1" style="font-size:0.8rem;"></i>Remaining`;
}
if (data.newStatus && typeof updateStatusBadge === "function") {
updateStatusBadge(data.newStatus);
}
if (data.isZero) {
document.getElementById("noInstallmentsMsg")?.classList.remove("d-none");
}
alert(data.message || "Installment deleted.");
})
.catch(err => {
alert(err.message || "Something went wrong. Please try again.");
});
}); });
// Auto due date = invoice date + 10 days // Header AJAX (सध्या card hidden आहे, तरीही ठेवलेलं)
const invoiceDateInput = document.querySelector('input[name="invoice_date"]');
const dueDateInput = document.querySelector('input[name="due_date"]');
if (invoiceDateInput && dueDateInput) {
invoiceDateInput.addEventListener('change', function() {
const selectedDate = new Date(this.value);
if (!isNaN(selectedDate.getTime())) {
selectedDate.setDate(selectedDate.getDate() + 10);
const year = selectedDate.getFullYear();
const month = String(selectedDate.getMonth() + 1).padStart(2, '0');
const day = String(selectedDate.getDate()).padStart(2, '0');
dueDateInput.value = `${year}-${month}-${day}`;
}
});
}
// ✅ Invoice header AJAX save (no page refresh)
const headerForm = document.getElementById('invoiceHeaderForm'); const headerForm = document.getElementById('invoiceHeaderForm');
const headerBtn = document.getElementById('btnHeaderSave'); const headerBtn = document.getElementById('btnHeaderSave');
const headerMsg = document.getElementById('headerUpdateMsg'); const headerMsg = document.getElementById('headerUpdateMsg');
@@ -872,7 +796,6 @@ document.addEventListener("DOMContentLoaded", function () {
headerBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...'; headerBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...';
const formData = new FormData(headerForm); const formData = new FormData(headerForm);
fetch(headerForm.action, { fetch(headerForm.action, {
method: 'POST', method: 'POST',
@@ -884,7 +807,7 @@ document.addEventListener("DOMContentLoaded", function () {
body: formData body: formData
}) })
.then(async res => { .then(async res => {
let data = null; let data;
try { data = await res.json(); } catch(e) {} try { data = await res.json(); } catch(e) {}
if (!res.ok) { if (!res.ok) {
@@ -899,7 +822,6 @@ document.addEventListener("DOMContentLoaded", function () {
headerMsg.classList.remove('text-danger'); headerMsg.classList.remove('text-danger');
headerMsg.classList.add('text-light'); headerMsg.classList.add('text-light');
// popup_invoice वरचा status badge update करायचा असल्यास:
const status = document.getElementById('statusSelect')?.value; const status = document.getElementById('statusSelect')?.value;
const badge = document.querySelector('.status-badge'); const badge = document.querySelector('.status-badge');
if (badge && status) { if (badge && status) {

View File

@@ -1,272 +1,623 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{{ $invoice->invoice_number }}</title> <title>Invoice #{{ $invoice->invoice_number }}</title>
<style> <style>
body { body {
font-family: 'Segoe UI', 'Roboto', Arial, sans-serif; font-family: 'DejaVu Sans', sans-serif;
background: #F7FBFC; font-size: 10px;
color: #1A222B; color: #1a202c;
line-height: 1.4;
margin: 0; margin: 0;
padding: 0; padding: 18px;
font-size: 15px;
}
.container {
max-width: 850px;
margin: 24px auto 0 auto;
background: #fff; background: #fff;
border-radius: 13px;
box-shadow: 0 2px 14px rgba(40,105,160,0.08);
padding: 35px 32px 18px 32px;
} }
.header {
display: flex; /* ── TOP HEADER ── */
justify-content: space-between; .top-header {
align-items: flex-start;
border-bottom: 2px solid #E6EBF0;
padding-bottom: 13px;
}
.logo-company {
display: flex;
align-items: flex-start;
}
.logo {
height: 50px;
margin-right: 13px;
}
.company-details {
margin-top: 0;
font-size: 15px;
}
.company-title {
font-size: 21px;
font-weight: bold;
margin-bottom: 2px;
}
.company-sub {
font-size: 16px;
margin-bottom: 8px; margin-bottom: 8px;
font-weight: 500; border-bottom: 2px solid #1a202c;
padding-bottom: 6px;
} }
.invoice-details { .header-table {
text-align: right;
min-width: 220px;
}
.invoice-title {
font-weight: bold;
font-size: 23px;
letter-spacing: 0.5px;
margin-bottom: 2px;
}
.paid-label {
margin-top: 8px;
margin-bottom: 2px;
}
.paid-tag {
background: #23BF47;
color: #fff;
font-weight: bold;
border-radius: 8px;
padding: 4px 16px 4px 22px;
font-size: 17px;
display: inline-block;
position: relative;
}
.paid-tag:before {
content: '';
position: absolute;
left: 7px;
top: 7px;
width: 10px;
height: 10px;
background: #fff;
border-radius: 50%;
}
.paid-date {
font-size: 14px;
color: #23BF47;
margin-top: 2px;
}
.bill-section {
background: #F3F7FB;
border-radius: 11px;
padding: 20px 18px 13px 18px;
margin: 28px 0 16px 0;
box-shadow: 0 0px 0px #0000;
}
.bill-title {
font-size: 17px;
font-weight: bold;
color: #23355D;
margin-bottom: 4px;
letter-spacing: 0.3px;
}
.bill-details {
font-size: 15px;
line-height: 1.6;
}
table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
margin-top: 9px;
margin-bottom: 13px;
background: #fff;
border-radius: 10px;
overflow: hidden;
} }
th { .header-table td {
background: #F6F7F9; vertical-align: middle;
padding: 10px 0; padding: 0;
font-size: 15px; }
color: #6781A6; /* Logo cell — fixed width, logo fits inside */
font-weight: bold; .logo-cell {
border: none; width: 60px;
}
.logo-cell img {
width: 55px;
height: 55px;
display: block;
}
/* Company name cell */
.title-cell {
padding-left: 10px;
text-align: left; text-align: left;
} }
td { .company-name {
padding: 7px 0; font-size: 18px;
color: #222;
font-size: 15px;
border: none;
text-align: left;
}
tbody tr:not(:last-child) td {
border-bottom: 1px solid #E6EBF0;
}
tbody tr:last-child td {
border-bottom: none;
}
.totals-row td {
font-weight: bold; font-weight: bold;
color: #23355D; color: #1a202c;
text-transform: uppercase;
letter-spacing: 2px;
} }
.gst-row td { .company-subtitle {
font-weight: 500; font-size: 11px;
color: #23BF47;
}
.total-row td {
font-weight: bold; font-weight: bold;
font-size: 17px; color: #2d3748;
color: #222; letter-spacing: 1px;
} }
.payment-info { .company-tagline {
margin-top: 24px; font-size: 8px;
margin-bottom: 9px; color: #718096;
font-size: 15px; margin-top: 3px;
} }
.ref-number {
font-size: 14px; /* ── INFO TABLE (Customer | Invoice side by side) ── */
color: #6781A6; .info-outer {
margin-bottom: 8px; width: 100%;
margin-top: 2px; border-collapse: collapse;
border: 1px solid #cbd5e0;
margin-bottom: 10px;
} }
.footer { .info-outer td {
border-top: 1.2px solid #E6EBF0; vertical-align: top;
margin-top: 25px; padding: 10px 12px;
padding-top: 12px; }
font-size: 16px; .info-left { width: 50%; }
color: #888; .info-right { width: 50%; border-left: 1px solid #cbd5e0; }
.section-title {
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #718096;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 3px;
margin-bottom: 5px;
}
.info-line {
font-size: 9px;
color: #2d3748;
margin-bottom: 2px;
line-height: 1.5;
}
/* ── STATUS BADGE ── */
.badge {
display: inline;
padding: 2px 6px;
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
border-radius: 3px;
}
.badge-paid { background: #c6f6d5; color: #22543d; }
.badge-pending { background: #fef3c7; color: #92400e; }
.badge-paying { background: #dbeafe; color: #1e40af; }
.badge-overdue { background: #fee2e2; color: #991b1b; }
/* ── BILL TO ── */
.bill-to-box {
border: 1px solid #cbd5e0;
padding: 8px 12px;
margin-bottom: 10px;
background: #f7fafc;
}
.bill-to-label {
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #718096;
margin-bottom: 4px;
}
.bill-name { font-size: 12px; font-weight: bold; color: #1a202c; margin-bottom: 2px; }
.bill-sub { font-size: 10px; font-weight: bold; color: #2d3748; margin-bottom: 2px; }
.bill-detail { font-size: 9px; color: #4a5568; line-height: 1.8; }
/* ── SUMMARY GRID — 4 boxes per row ── */
.summary-wrap { margin-bottom: 12px; }
.summary-label {
font-size: 9px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #4a5568;
margin-bottom: 4px;
}
.summary-outer {
width: 100%;
border-collapse: collapse;
}
.sbox {
width: 25%;
border: 1px solid #cbd5e0;
padding: 7px 8px;
text-align: center; text-align: center;
vertical-align: middle;
} }
.footer strong { .sbox-row1 { background: #ffffff; }
color: #222; .sbox-row2 { background: #f7fafc; }
font-weight: 500; .sbox-lbl {
font-size: 7px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
color: #718096;
margin-bottom: 3px;
}
.sbox-val {
font-size: 11px;
font-weight: bold;
color: #1a202c;
}
.sbox-sub {
font-size: 7px;
color: #a0aec0;
margin-top: 1px;
}
/* ── CHARGE GROUPS ── */
.cg-header {
font-size: 10px;
font-weight: bold;
color: #ffffff;
background: #2d3748;
padding: 5px 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
.cg-group-wrap {
border: 1px solid #cbd5e0;
margin-bottom: 10px;
}
.cg-sum-table {
width: 100%;
border-collapse: collapse;
font-size: 9px;
}
.cg-sum-table th {
background: #edf2f7;
padding: 5px;
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
color: #4a5568;
border: 1px solid #cbd5e0;
text-align: left;
}
.cg-sum-table td {
padding: 5px;
border: 1px solid #cbd5e0;
color: #2d3748;
font-size: 9px;
}
.cg-item-table {
width: 100%;
border-collapse: collapse;
font-size: 8px;
}
.cg-item-table th {
background: #f0fff4;
padding: 4px 5px;
font-size: 7px;
font-weight: bold;
text-transform: uppercase;
color: #276749;
border: 1px solid #c6f6d5;
text-align: left;
white-space: nowrap;
}
.cg-item-table td {
padding: 4px 5px;
border: 1px solid #e2e8f0;
color: #2d3748;
white-space: nowrap;
}
.row-even { background: #f7fafc; }
/* ── GRAND TOTAL ── */
.grand-outer {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
margin-bottom: 10px;
}
.grand-spacer { width: 60%; }
.grand-inner { width: 40%; vertical-align: top; }
.grand-table { width: 100%; border-collapse: collapse; font-size: 9px; }
.grand-table td { padding: 4px 8px; border: 1px solid #e2e8f0; }
.g-lbl { text-align: right; font-weight: bold; color: #4a5568; background: #f7fafc; }
.g-val { text-align: right; font-weight: bold; color: #1a202c; }
.g-total-lbl { text-align: right; font-size: 11px; font-weight: bold; color: #1a202c; background: #ebf8ff; border-top: 2px solid #2b6cb0; }
.g-total-val { text-align: right; font-size: 11px; font-weight: bold; color: #1a202c; background: #ebf8ff; border-top: 2px solid #2b6cb0; }
/* ── INSTALLMENTS ── */
.install-section { margin-top: 12px; }
.install-header {
font-size: 9px;
font-weight: bold;
color: #fff;
background: #2d3748;
padding: 5px 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
.install-table { width: 100%; border-collapse: collapse; font-size: 9px; }
.install-table th {
background: #edf2f7;
padding: 5px 6px;
font-size: 8px;
font-weight: bold;
text-transform: uppercase;
color: #4a5568;
border: 1px solid #cbd5e0;
}
.install-table td { padding: 4px 6px; border: 1px solid #e2e8f0; color: #2d3748; }
/* ── HELPERS ── */
.tr { text-align: right; }
.tc { text-align: center; }
.bold { font-weight: bold; }
/* ── FOOTER ── */
.footer {
margin-top: 16px;
padding-top: 8px;
border-top: 1px solid #e2e8f0;
font-size: 8px;
color: #718096;
text-align: center;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="container">
<!-- Header Section --> {{-- ══ VARIABLES ══ --}}
<div class="header"> @php
<div class="logo-company"> $companyName = $invoice->company_name ?: ($invoice->customer->company_name ?? null);
<img src="{{ public_path('images/kent_logo2.png') }}" class="logo"> $custName = $invoice->customer_name;
<div class="company-details"> $custAddress = $invoice->customer_address ?: ($invoice->customer->address ?? null);
<div class="company-title">{{ $invoice->company_name ?? 'Kent International Pvt. Ltd.' }}</div> $custPincode = $invoice->pincode ?: ($invoice->customer->pincode ?? null);
<div class="company-sub"></div> $custEmail = $invoice->customer_email ?: ($invoice->customer->email ?? null);
{{ $invoice->company_address ?? '123 Business Park, Sector 5' }}<br> $custMobile = $invoice->customer_mobile ?: ($invoice->customer->mobile_no ?? null);
{{ $invoice->company_city ?? 'Gurugram, Haryana 122001' }}<br> @endphp
{{ $invoice->company_country ?? 'India' }}<br>
GST: {{ $invoice->company_gst ?? 'GST123456789' }}<br> {{-- ══ 1) HEADER Logo + Company Name ══ --}}
Email: {{ $invoice->company_email ?? 'billing@kent.com' }}<br> <div class="top-header">
Phone: {{ $invoice->company_phone ?? '+91 124 123 4567' }} <table class="header-table">
<tr>
{{-- Logo: 55x55px DomPDF साठी public_path() वापरतो --}}
<td class="logo-cell">
<img src="{{ public_path('images/kent_logo2.png') }}" alt="Logo" width="55" height="55">
</td>
{{-- Company Name + Subtitle + Tagline --}}
<td class="title-cell">
<div class="company-name">KENT</div>
<div class="company-subtitle">International Pvt. Ltd.</div>
<div class="company-tagline">
Address Line 1, City, State Pincode &nbsp;|&nbsp;
Email: info@company.com &nbsp;|&nbsp;
Phone: +91 1234567890
</div> </div>
</td>
</tr>
</table>
</div>
{{-- ══ 2) CUSTOMER | INVOICE ══ --}}
<table class="info-outer">
<tr>
<td class="info-left">
<div class="section-title">Customer Details</div>
@if($companyName)
<div class="info-line bold" style="font-size:11px;">{{ $companyName }}</div>
<div class="info-line">{{ $custName }}</div>
@else
<div class="info-line bold" style="font-size:11px;">{{ $custName }}</div>
@endif
@if($custAddress)<div class="info-line">{{ $custAddress }}</div>@endif
@if($custPincode)<div class="info-line">{{ $custPincode }}</div>@endif
@if($custEmail)<div class="info-line">Email: {{ $custEmail }}</div>@endif
@if($custMobile)<div class="info-line">Phone: {{ $custMobile }}</div>@endif
</td>
<td class="info-right">
<div class="section-title">Invoice Details</div>
<div class="info-line"><strong>Invoice #:</strong> {{ $invoice->invoice_number }}</div>
<div class="info-line"><strong>Date:</strong> {{ \Carbon\Carbon::parse($invoice->invoice_date)->format('d M, Y') }}</div>
<div class="info-line"><strong>Due Date:</strong> {{ \Carbon\Carbon::parse($invoice->due_date)->format('d M, Y') }}</div>
<div class="info-line">
<strong>Status:</strong>
<span class="badge badge-{{ strtolower($invoice->status) }}">{{ strtoupper($invoice->status) }}</span>
</div> </div>
<div class="invoice-details"> <div class="info-line"><strong>GST Type:</strong> {{ strtoupper($invoice->tax_type ?? 'GST') }}</div>
<div class="invoice-title">INVOICE</div> <div class="info-line"><strong>GST %:</strong> {{ number_format($invoice->gst_percent ?? 0, 2) }}%</div>
Invoice #: {{ $invoice->invoice_number }}<br> </td>
Issue Date: {{ date('d M Y', strtotime($invoice->invoice_date)) }}<br> </tr>
Due Date: {{ date('d M Y', strtotime($invoice->due_date)) }} </table>
@if(strtolower($invoice->status) == 'paid')
<div class="paid-label"> {{-- ══ 3) BILL TO ══ --}}
<span class="paid-tag">Paid</span> <div class="bill-to-box">
</div> <div class="bill-to-label">Bill To</div>
<div class="paid-date"> @if($companyName)
Paid on: {{ date('d M Y', strtotime($invoice->paid_date ?? now())) }} <div class="bill-name">{{ $companyName }}</div>
</div> <div class="bill-sub">{{ $custName }}</div>
@else @else
<div class="paid-date" style="color:#d00;">Status: {{ ucfirst($invoice->status) }}</div> <div class="bill-name">{{ $custName }}</div>
@endif @endif
</div> <div class="bill-detail">
</div> @if($custAddress){{ $custAddress }}<br>@endif
<!-- Bill To Section --> @if($custPincode){{ $custPincode }}<br>@endif
<div class="bill-section"> @if($custEmail)Email: {{ $custEmail }}<br>@endif
<div class="bill-title">Bill To</div> @if($custMobile)Phone: {{ $custMobile }}@endif
<div class="bill-details">
<strong>{{ $invoice->customer_name }}</strong><br>
{{ $invoice->customer_address }}<br>
GST: {{ $invoice->customer_gst ?? '-' }}<br>
Email: {{ $invoice->customer_email }}<br>
Phone: {{ $invoice->customer_mobile }}
</div>
</div>
<!-- Items Table -->
<table>
<thead>
<tr>
<th>Description</th>
<th>Qty</th>
<th>Rate ()</th>
<th>Amount ()</th>
</tr>
</thead>
<tbody>
@foreach($invoice->items as $item)
<tr>
<td>{{ $item->description }}</td>
<td>{{ $item->qty }}</td>
<td>{{ number_format($item->price, 0) }}</td>
<td>{{ number_format($item->ttl_amount, 0) }}</td>
</tr>
@endforeach
<tr class="totals-row">
<td colspan="3" style="text-align:right;">Subtotal:</td>
<td>{{ number_format($invoice->subtotal, 0) }}</td>
</tr>
<tr class="gst-row">
<td colspan="3" style="text-align:right;">GST ({{ $invoice->gst_percent }}%):</td>
<td>{{ number_format($invoice->gst_amount, 0) }}</td>
</tr>
<tr class="total-row">
<td colspan="3" style="text-align:right;">Total:</td>
<td>{{ number_format($invoice->final_amount_with_gst, 0) }}</td>
</tr>
</tbody>
</table>
<!-- Payment Info & Reference -->
<div class="payment-info">
<strong>Payment Method:</strong> {{ $invoice->payment_method ?? 'Bank Transfer' }}
</div>
<div class="ref-number">
Reference Number: {{ $invoice->reference_no ?? "REF123456789" }}
</div>
<!-- Footer -->
<div class="footer">
Thank you for your business!<br>
For any queries, please contact us at <strong>{{ $invoice->company_email ?? 'billing@kent.com' }}</strong>
</div>
</div> </div>
</div>
{{-- ══ 4) 8 SUMMARY BOXES ══ --}}
@php
$allGroupItems = $invoice->chargeGroups->flatMap(fn($g) => $g->items);
$invoiceItemIds = $allGroupItems->pluck('invoice_item_id')->unique()->values();
$invoiceItems = $invoice->items->whereIn('id', $invoiceItemIds);
$totalMarkCount = $invoiceItems->pluck('mark_no')->filter()->unique()->count();
$totalCtn = $invoiceItems->sum('ctn');
$totalQty = $invoiceItems->sum('ttl_qty');
$totalCbm = $invoiceItems->sum('ttl_cbm');
$totalKg = $invoiceItems->sum('ttl_kg');
@endphp
<div class="summary-wrap">
<div class="summary-label">Container &amp; Shipment Summary</div>
<table class="summary-outer">
<tr>
<td class="sbox sbox-row1">
<div class="sbox-lbl">Container Name</div>
<div class="sbox-val">{{ $invoice->container->container_name ?? '—' }}</div>
</td>
<td class="sbox sbox-row1">
<div class="sbox-lbl">Container Date</div>
<div class="sbox-val">
@if($invoice->container && $invoice->container->container_date)
{{ \Carbon\Carbon::parse($invoice->container->container_date)->format('d M, Y') }}
@else@endif
</div>
</td>
<td class="sbox sbox-row1">
<div class="sbox-lbl">Container No.</div>
<div class="sbox-val">{{ $invoice->container->container_number ?? '—' }}</div>
</td>
<td class="sbox sbox-row1">
<div class="sbox-lbl">Total Mark No.</div>
<div class="sbox-val">{{ $totalMarkCount }}</div>
<div class="sbox-sub">Unique marks</div>
</td>
</tr>
<tr>
<td class="sbox sbox-row2">
<div class="sbox-lbl">Total CTN</div>
<div class="sbox-val">{{ number_format($totalCtn, 0) }}</div>
<div class="sbox-sub">Cartons</div>
</td>
<td class="sbox sbox-row2">
<div class="sbox-lbl">Total QTY</div>
<div class="sbox-val">{{ number_format($totalQty, 0) }}</div>
<div class="sbox-sub">Pieces</div>
</td>
<td class="sbox sbox-row2">
<div class="sbox-lbl">Total CBM</div>
<div class="sbox-val">{{ number_format($totalCbm, 3) }}</div>
<div class="sbox-sub">Cubic Meter</div>
</td>
<td class="sbox sbox-row2">
<div class="sbox-lbl">Total KG</div>
<div class="sbox-val">{{ number_format($totalKg, 2) }}</div>
<div class="sbox-sub">Kilograms</div>
</td>
</tr>
</table>
</div>
{{-- ══ 5) CHARGE GROUPS ══ --}}
@if($invoice->chargeGroups && $invoice->chargeGroups->count() > 0)
<div style="margin-bottom:14px;">
<div class="cg-header">Charge Groups</div>
@foreach($invoice->chargeGroups as $group)
@php
$groupItemIds = $group->items->pluck('invoice_item_id')->toArray();
$groupInvItems = $invoice->items->whereIn('id', $groupItemIds);
@endphp
<div class="cg-group-wrap">
<table class="cg-sum-table">
<thead>
<tr>
<th style="width:22%;">Group Name</th>
<th style="width:11%;">Basis</th>
<th style="width:11%;">Basis Value</th>
<th style="width:10%;">Rate</th>
<th style="width:13%;" class="tr">Total Charge</th>
<th style="width:8%;">GST %</th>
<th style="width:9%;">Tax Type</th>
<th style="width:16%;" class="tr">Total With GST</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>{{ $group->group_name ?? 'Group '.$group->id }}</strong></td>
<td>{{ strtoupper($group->basis_type) }}</td>
<td>{{ number_format($group->basis_value, 3) }}</td>
<td>{{ number_format($group->rate, 2) }}</td>
<td class="tr">{{ number_format($group->total_charge, 2) }}</td>
<td>{{ number_format($group->gst_percent ?? 0, 2) }}%</td>
<td>{{ strtoupper($group->tax_type ?? 'GST') }}</td>
<td class="tr"><strong>{{ number_format($group->total_with_gst, 2) }}</strong></td>
</tr>
</tbody>
</table>
@if($groupInvItems->count() > 0)
<table class="cg-item-table">
<thead>
<tr>
<th style="width:3%;">#</th>
<th style="width:15%;">Description</th>
<th style="width:7%;">Mark No</th>
<th style="width:5%;" class="tc">QTY</th>
<th style="width:6%;" class="tr">TTL QTY</th>
<th style="width:6%;" class="tr">CBM</th>
<th style="width:6%;" class="tr">TTL CBM</th>
<th style="width:5%;" class="tr">KG</th>
<th style="width:6%;" class="tr">TTL KG</th>
<th style="width:7%;" class="tr">TTL Amt</th>
<th style="width:5%;" class="tr">Rate</th>
<th style="width:7%;" class="tr">Total Charge</th>
<th style="width:5%;" class="tr">GST %</th>
<th style="width:6%;" class="tr">Item GST</th>
<th style="width:6%;" class="tr">Item Total</th>
</tr>
</thead>
<tbody>
@foreach($groupInvItems as $idx => $item)
@php
$groupBasisTotal = $groupInvItems->sum(function($i) use ($group) {
return match($group->basis_type) {
'ttl_qty' => $i->ttl_qty ?? 0,
'ttl_cbm' => $i->ttl_cbm ?? 0,
'ttl_kg' => $i->ttl_kg ?? 0,
'amount' => $i->ttl_amount ?? 0,
default => $i->ttl_amount ?? 0,
};
});
$itemBasisValue = match($group->basis_type) {
'ttl_qty' => $item->ttl_qty ?? 0,
'ttl_cbm' => $item->ttl_cbm ?? 0,
'ttl_kg' => $item->ttl_kg ?? 0,
'amount' => $item->ttl_amount ?? 0,
default => $item->ttl_amount ?? 0,
};
$itemCharge = $groupBasisTotal > 0
? ($itemBasisValue / $groupBasisTotal) * $group->total_charge
: 0;
$gstPct = $group->gst_percent ?? 0;
$itemGst = $itemCharge * $gstPct / 100;
$itemTotal = $itemCharge + $itemGst;
@endphp
<tr class="{{ $idx % 2 === 1 ? 'row-even' : '' }}">
<td class="tc">{{ $idx + 1 }}</td>
<td>{{ $item->description }}</td>
<td>{{ $item->mark_no ?? '—' }}</td>
<td class="tc">{{ $item->qty ?? 0 }}</td>
<td class="tr">{{ number_format($item->ttl_qty ?? 0, 0) }}</td>
<td class="tr">{{ number_format($item->cbm ?? 0, 3) }}</td>
<td class="tr">{{ number_format($item->ttl_cbm ?? 0, 3) }}</td>
<td class="tr">{{ number_format($item->kg ?? 0, 2) }}</td>
<td class="tr">{{ number_format($item->ttl_kg ?? 0, 2) }}</td>
<td class="tr">{{ number_format($item->ttl_amount ?? 0, 2) }}</td>
<td class="tr">{{ number_format($group->rate, 2) }}</td>
<td class="tr">{{ number_format($itemCharge, 2) }}</td>
<td class="tr">{{ number_format($gstPct, 2) }}%</td>
<td class="tr">{{ number_format($itemGst, 2) }}</td>
<td class="tr"><strong>{{ number_format($itemTotal, 2) }}</strong></td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
@endforeach
</div>
@endif
{{-- ══ GRAND TOTAL ══ --}}
<table class="grand-outer">
<tr>
<td class="grand-spacer"></td>
<td class="grand-inner">
<table class="grand-table">
<tr>
<td class="g-lbl">Charge Groups Total:</td>
<td class="g-val">{{ number_format($invoice->charge_groups_total ?? 0, 2) }}</td>
</tr>
<tr>
<td class="g-lbl">GST Amount ({{ number_format($invoice->gst_percent ?? 0, 2) }}%):</td>
<td class="g-val">{{ number_format($invoice->gst_amount ?? 0, 2) }}</td>
</tr>
<tr>
<td class="g-total-lbl">Grand Total:</td>
<td class="g-total-val">{{ number_format($invoice->grand_total_with_charges ?? 0, 2) }}</td>
</tr>
</table>
</td>
</tr>
</table>
{{-- ══ INSTALLMENTS ══ --}}
@if($invoice->installments && $invoice->installments->count() > 0)
<div class="install-section">
<div class="install-header">Payment Installments</div>
<table class="install-table">
<thead>
<tr>
<th style="width:6%;" class="tc">#</th>
<th style="width:20%;">Date</th>
<th style="width:25%;">Payment Method</th>
<th style="width:25%;">Reference No</th>
<th style="width:24%;" class="tr">Amount ()</th>
</tr>
</thead>
<tbody>
@foreach($invoice->installments as $idx => $inst)
<tr>
<td class="tc">{{ $idx + 1 }}</td>
<td>{{ \Carbon\Carbon::parse($inst->installment_date)->format('d M, Y') }}</td>
<td>{{ strtoupper($inst->payment_method) }}</td>
<td>{{ $inst->reference_no ?? '—' }}</td>
<td class="tr">{{ number_format($inst->amount, 2) }}</td>
</tr>
@endforeach
</tbody>
</table>
@php
$totalPaid = $invoice->installments->sum('amount');
$grandTotal = $invoice->grand_total_with_charges ?? 0;
$remaining = max(0, $grandTotal - $totalPaid);
@endphp
<table class="grand-outer" style="margin-top:6px;">
<tr>
<td class="grand-spacer"></td>
<td class="grand-inner">
<table class="grand-table">
<tr>
<td class="g-lbl" style="color:#059669;">Total Paid:</td>
<td class="g-val" style="color:#059669;">{{ number_format($totalPaid, 2) }}</td>
</tr>
<tr>
<td class="g-lbl" style="color:#dc2626;">Remaining:</td>
<td class="g-val" style="color:#dc2626;">{{ number_format($remaining, 2) }}</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
@endif
{{-- ══ FOOTER ══ --}}
<div class="footer">
Thank you for your business! &nbsp;|&nbsp; For queries: info@company.com &nbsp;|&nbsp; +91 1234567890
</div>
</body> </body>
</html> </html>

View File

@@ -95,6 +95,7 @@
.status-paid { background: #dcfce7; color: #166534; } .status-paid { background: #dcfce7; color: #166534; }
.status-overdue { background: #fee2e2; color: #991b1b; } .status-overdue { background: #fee2e2; color: #991b1b; }
.status-pending { background: #fef3c7; color: #92400e; } .status-pending { background: #fef3c7; color: #92400e; }
.status-paying { background: #dbeafe; color: #1e40af; }
.status-default { background: #f1f5f9; color: #475569; } .status-default { background: #f1f5f9; color: #475569; }
/* ── ID BOXES ── */ /* ── ID BOXES ── */
@@ -681,19 +682,23 @@
</div> </div>
<div> <div>
@if($invoice->status == 'paid') @if($invoice->status == 'paid')
<span class="status-badge status-paid"> <span class="status-badge status-paid" id="invoiceStatusBadge">
<i class="fas fa-check-circle"></i> Paid <i class="fas fa-check-circle"></i> Paid
</span> </span>
@elseif($invoice->status == 'overdue') @elseif($invoice->status == 'overdue')
<span class="status-badge status-overdue"> <span class="status-badge status-overdue" id="invoiceStatusBadge">
<i class="fas fa-exclamation-circle"></i> Overdue <i class="fas fa-exclamation-circle"></i> Overdue
</span> </span>
@elseif($invoice->status == 'paying')
<span class="status-badge status-paying" id="invoiceStatusBadge">
<i class="fas fa-spinner"></i> Paying
</span>
@elseif($invoice->status == 'pending') @elseif($invoice->status == 'pending')
<span class="status-badge status-pending"> <span class="status-badge status-pending" id="invoiceStatusBadge">
<i class="fas fa-clock"></i> Pending <i class="fas fa-clock"></i> Pending
</span> </span>
@else @else
<span class="status-badge status-default"> <span class="status-badge status-default" id="invoiceStatusBadge">
<i class="fas fa-question-circle"></i> {{ ucfirst($invoice->status) }} <i class="fas fa-question-circle"></i> {{ ucfirst($invoice->status) }}
</span> </span>
@endif @endif
@@ -1194,6 +1199,7 @@
<tr> <tr>
<th>#</th> <th>#</th>
<th>Description</th> <th>Description</th>
<th class="text-center">Mark No</th>
<th class="text-center">QTY</th> <th class="text-center">QTY</th>
<th class="text-center">TTL QTY</th> <th class="text-center">TTL QTY</th>
<th class="text-center">CBM</th> <th class="text-center">CBM</th>
@@ -1203,7 +1209,6 @@
<th class="text-end">TTL Amount</th> <th class="text-end">TTL Amount</th>
<th class="text-end">Rate</th> <th class="text-end">Rate</th>
<th class="text-end">Total Charge</th> <th class="text-end">Total Charge</th>
{{-- नवीन GST % कॉलम --}}
<th class="text-end">GST %</th> <th class="text-end">GST %</th>
<th class="text-end">Item GST</th> <th class="text-end">Item GST</th>
<th class="text-end">Item Total (with GST)</th> <th class="text-end">Item Total (with GST)</th>
@@ -1243,34 +1248,27 @@
$itemTotalWithGst = $itemTotal + $itemGst; $itemTotalWithGst = $itemTotal + $itemGst;
} }
$itemGstPercent = $groupGstPercent ?? 0; // same as group $itemGstPercent = $groupGstPercent ?? 0;
@endphp @endphp
<tr> <tr>
<td>{{ $giIndex + 1 }}</td> <td>{{ $giIndex + 1 }}</td>
<td>{{ $it->description }}</td> <td>{{ $it->description }}</td>
<td class="text-center">{{ $it->mark_no ?? '-' }}</td>
<td class="text-center">{{ $it->qty }}</td> <td class="text-center">{{ $it->qty }}</td>
<td class="text-center">{{ $it->ttl_qty }}</td> <td class="text-center">{{ $it->ttl_qty }}</td>
<td class="text-center">{{ $it->cbm }}</td> <td class="text-center">{{ $it->cbm }}</td>
<td class="text-center">{{ $it->ttl_cbm }}</td> <td class="text-center">{{ $it->ttl_cbm }}</td>
<td class="text-center">{{ $it->kg }}</td> <td class="text-center">{{ $it->kg }}</td>
<td class="text-center">{{ $it->ttl_kg }}</td> <td class="text-center">{{ $it->ttl_kg }}</td>
<td>{{ $item->mark_no }}</td> <td class="text-end">{{ number_format($it->ttl_amount, 2) }}</td>
<td class="text-end"> <td class="text-end">{{ number_format($rate, 2) }}</td>
{{ number_format($it->ttl_amount, 2) }}
</td>
<td class="text-end">
{{ number_format($rate, 2) }}
</td>
<td class="text-end" style="color:#06b6d4;font-weight:700;"> <td class="text-end" style="color:#06b6d4;font-weight:700;">
{{ number_format($itemTotal, 2) }} {{ number_format($itemTotal, 2) }}
</td> </td>
{{-- GST % (per item = group GST %) --}}
<td class="text-end"> <td class="text-end">
{{ number_format($itemGstPercent, 2) }}% {{ number_format($itemGstPercent, 2) }}%
</td> </td>
<td class="text-end" style="color:#ef4444;"> <td class="text-end" style="color:#ef4444;">
{{ $itemGst > 0 ? number_format($itemGst, 2) : '0.00' }} {{ $itemGst > 0 ? number_format($itemGst, 2) : '0.00' }}
</td> </td>
@@ -1293,8 +1291,23 @@
</div> </div>
</div> </div>
@endif @endif
{{-- ===== FINAL SUMMARY (POPUP) ===== --}}
@php
// Base amount (without GST) invoice level
$baseAmount = $invoice->final_amount;
$gstAmount = $invoice->gst_amount;
$groupsWithGst = $invoice->chargeGroups->sum('total_with_gst');
// Installments वरून live calculate (DB extra column नाही)
$summaryTotalPaid = $invoice->totalPaid();
$summaryRemaining = $invoice->remainingAmount();
// ✅ इथे isPaid define कर
$grandTotal = $invoice->grand_total_with_charges ?? 0;
$totalPaid = $invoice->installments->sum('amount') ?? 0;
$isPaid = ($invoice->status === 'paid') || ($grandTotal > 0 && round($totalPaid, 2) >= round($grandTotal, 2));
@endphp
{{-- ===== FINAL SUMMARY (POPUP) ===== --}}
<div class="summary-card-compact mt-3"> <div class="summary-card-compact mt-3">
<div class="summary-header-compact"> <div class="summary-header-compact">
<i class="fas fa-calculator"></i> <i class="fas fa-calculator"></i>
@@ -1302,13 +1315,6 @@
</div> </div>
<div class="summary-body-compact"> <div class="summary-body-compact">
@php
// Base amount (without GST) invoice level
$baseAmount = $invoice->final_amount; // items + groups base, जर तू तसे ठेवले असेल
$gstAmount = $invoice->gst_amount; // total GST
$groupsWithGst = $invoice->chargeGroups->sum('total_with_gst'); // सर्व charge groups with GST
@endphp
{{-- Total Charge (Before GST) --}} {{-- Total Charge (Before GST) --}}
<div class="summary-row-compact"> <div class="summary-row-compact">
<span class="label">Total Charge (Before GST)</span> <span class="label">Total Charge (Before GST)</span>
@@ -1333,23 +1339,53 @@
</span> </span>
</div> </div>
{{-- Total Paid (installments sum) --}}
{{-- <div class="summary-row-compact">
<div class="summary-row-compact total"> <span class="label" style="color:#10b981;font-weight:600;">
<span class="label">Total Charge With GST</span> <i class="fas fa-check-circle me-1" style="font-size:0.8rem;"></i>Total Paid
<span class="value"> </span>
{{ number_format($invoice->final_amount_with_gst, 2) }} <span class="value" id="finalSummaryTotalPaid" style="color:#10b981;">
{{ number_format($summaryTotalPaid, 2) }}
</span>
</div>
{{-- Remaining Balance --}}
<div class="summary-row-compact" style="border-bottom:none;">
<span class="label" id="finalSummaryRemainingLabel"
style="color:{{ $summaryRemaining > 0 ? '#ef4444' : '#10b981' }};font-weight:600;">
<i class="fas fa-{{ $summaryRemaining > 0 ? 'exclamation-circle' : 'check-circle' }} me-1"
style="font-size:0.8rem;"></i>Remaining
</span>
<span class="value" id="finalSummaryRemaining"
style="color:{{ $summaryRemaining > 0 ? '#ef4444' : '#10b981' }};">
{{ number_format($summaryRemaining, 2) }}
</span> </span>
</div> </div>
--}}
</div> </div>
</div> </div>
@if($isPaid)
<div class="popup-download-actions" style="margin-top: 20px; padding-top: 16px; border-top: 2px solid #e5e7eb; text-align: right;">
<a href="{{ route('admin.invoice.download.pdf', $invoice->id) }}"
class="btn btn-primary"
style="display:inline-flex; align-items:center; gap:6px; padding:10px 20px; text-decoration:none; background:#4a5568; color:#fff; border-radius:6px; font-weight:600; margin-right:8px;">
<i class="fas fa-file-pdf"></i>
Download PDF
</a>
<a href="{{ route('admin.invoice.share', $invoice->id) }}"
class="btn btn-success"
style="display:inline-flex; align-items:center; gap:6px; padding:10px 20px; text-decoration:none; background:#059669; color:#fff; border-radius:6px; font-weight:600;">
<i class="fas fa-share-alt"></i>
Share
</a>
</div>
@endif
<!-- ═══════════════════════════ FOOTER ═══════════════════════════ --> <!-- ═══════════════════════════ FOOTER ═══════════════════════════ -->
<div class="invoice-footer"> <!-- <div class="invoice-footer">
@if($invoice->pdf_path && $showActions) @if($invoice->pdf_path && $showActions)
<a href="{{ asset($invoice->pdf_path) }}" class="btn-download" download> <a href="{{ asset($invoice->pdf_path) }}" class="btn-download" download>
<i class="fas fa-download"></i> Download PDF <i class="fas fa-download"></i> Download PDF
@@ -1363,7 +1399,7 @@
<p style="margin:0;">For any inquiries, contact us at support@Kent Logistic</p> <p style="margin:0;">For any inquiries, contact us at support@Kent Logistic</p>
</div> </div>
</div> </div>
</div> </div> -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script> <script>
function shareInvoice() { function shareInvoice() {

File diff suppressed because it is too large Load Diff

View File

@@ -71,7 +71,19 @@ Route::prefix('admin')
Route::get('/dashboard', [AdminOrderController::class, 'dashboard']) Route::get('/dashboard', [AdminOrderController::class, 'dashboard'])
->name('admin.dashboard'); ->name('admin.dashboard');
Route::get('/reports', [AdminReportController::class, 'index'])->name('admin.reports'); Route::get('/reports/containers/excel', [AdminReportController::class, 'containerReportExcel'])
->name('admin.reports.containers.excel');
Route::get('/reports/containers/pdf', [AdminReportController::class, 'containerReportPdf'])
->name('admin.reports.containers.pdf');
//reports DOWNLOAD
Route::get('/reports', [AdminReportController::class, 'containerReport'])
->name('admin.reports');
Route::get('/chat-support', fn() => view('admin.chat_support')) Route::get('/chat-support', fn() => view('admin.chat_support'))
->name('admin.chat_support'); ->name('admin.chat_support');
@@ -376,4 +388,17 @@ Route::get('/admin/invoices/{invoice}/download', [AdminInvoiceController::class,
Route::get('/admin/containers/{container}/popup', [\App\Http\Controllers\ContainerController::class, 'popupPopup']) Route::get('/admin/containers/{container}/popup', [\App\Http\Controllers\ContainerController::class, 'popupPopup'])
->name('containers.popup'); ->name('containers.popup');
//Invoice pdf download route
Route::get('invoice/{id}/download-pdf', [AdminInvoiceController::class, 'downloadPdf'])
->name('admin.invoice.download.pdf');
// Route::get('invoice/{id}/download-excel', [AdminInvoiceController::class, 'downloadExcel'])
// ->name('admin.invoice.download.excel');
Route::get('invoice/{id}/share', [AdminInvoiceController::class, 'share'])
->name('admin.invoice.share');