pdf Updated, Invoice Updated, Report Updated
This commit is contained in:
@@ -22,7 +22,8 @@ class AdminCustomerController extends Controller
|
||||
$query = User::with([
|
||||
'marks',
|
||||
'orders',
|
||||
'invoices.installments' // 🔥 IMPORTANT
|
||||
'invoices.installments',
|
||||
'invoices.chargeGroups', // 🔥 for order total calculation
|
||||
])->orderBy('id', 'desc');
|
||||
|
||||
if (!empty($search)) {
|
||||
@@ -159,4 +160,4 @@ class AdminCustomerController extends Controller
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,30 +2,31 @@
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Controller; // ⬅️ हे नक्की असू दे
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceItem;
|
||||
use App\Models\InvoiceInstallment;
|
||||
use App\Models\InvoiceChargeGroup;
|
||||
use App\Models\InvoiceChargeGroupItem;
|
||||
use App\Models\InvoiceInstallment;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Mpdf\Mpdf;
|
||||
|
||||
|
||||
class AdminInvoiceController extends Controller
|
||||
{
|
||||
// -------------------------------------------------------------
|
||||
// INVOICE LIST PAGE
|
||||
// INDEX (LIST ALL INVOICES WITH FILTERS)
|
||||
// -------------------------------------------------------------
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Invoice::with(['items', 'customer', 'container']);
|
||||
$query = Invoice::query();
|
||||
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where(function ($q) use ($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
|
||||
// -------------------------------------------------------------
|
||||
public function popup($id)
|
||||
{
|
||||
$invoice = Invoice::with([
|
||||
'items',
|
||||
'chargeGroups.items',
|
||||
])->findOrFail($id);
|
||||
{
|
||||
$invoice = Invoice::with([
|
||||
'items',
|
||||
'chargeGroups.items',
|
||||
])->findOrFail($id);
|
||||
|
||||
$shipment = null;
|
||||
$shipment = null;
|
||||
|
||||
$groupedItemIds = $invoice->chargeGroups
|
||||
->flatMap(fn($group) => $group->items->pluck('invoice_item_id'))
|
||||
->unique()
|
||||
->values()
|
||||
->toArray();
|
||||
$groupedItemIds = $invoice->chargeGroups
|
||||
->flatMap(fn($group) => $group->items->pluck('invoice_item_id'))
|
||||
->unique()
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
return view('admin.popup_invoice', compact('invoice', 'shipment', 'groupedItemIds'));
|
||||
}
|
||||
return view('admin.popup_invoice', compact('invoice', 'shipment', 'groupedItemIds'));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// EDIT INVOICE PAGE
|
||||
@@ -79,31 +80,29 @@ class AdminInvoiceController extends Controller
|
||||
'chargeGroups.items',
|
||||
'installments',
|
||||
])->findOrFail($id);
|
||||
|
||||
// ✅ Customer details sync — जर test data आला असेल तर fix होईल
|
||||
|
||||
// ✅ Customer details sync
|
||||
if ($invoice->customer) {
|
||||
$needsUpdate = [];
|
||||
|
||||
|
||||
if (empty($invoice->customer_email) || $invoice->customer_email === 'test@demo.com') {
|
||||
$needsUpdate['customer_email'] = $invoice->customer->email;
|
||||
}
|
||||
|
||||
if (empty($invoice->customer_address) || $invoice->customer_address === 'TEST ADDRESS') {
|
||||
$needsUpdate['customer_address'] = $invoice->customer->address;
|
||||
}
|
||||
|
||||
if (empty($invoice->pincode) || $invoice->pincode === '999999') {
|
||||
$needsUpdate['pincode'] = $invoice->customer->pincode;
|
||||
}
|
||||
|
||||
|
||||
if (!empty($needsUpdate)) {
|
||||
$invoice->update($needsUpdate);
|
||||
$invoice->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$shipment = null;
|
||||
|
||||
|
||||
$groupedItemIds = $invoice->chargeGroups
|
||||
->flatMap(function ($group) {
|
||||
return $group->items->pluck('invoice_item_id');
|
||||
@@ -111,7 +110,7 @@ class AdminInvoiceController extends Controller
|
||||
->unique()
|
||||
->values()
|
||||
->toArray();
|
||||
|
||||
|
||||
return view('admin.invoice_edit', compact('invoice', 'shipment', 'groupedItemIds'));
|
||||
}
|
||||
|
||||
@@ -122,16 +121,16 @@ class AdminInvoiceController extends Controller
|
||||
{
|
||||
Log::info('🟡 Invoice Update Request Received', [
|
||||
'invoice_id' => $id,
|
||||
'request' => $request->all(),
|
||||
'request' => $request->all(),
|
||||
]);
|
||||
|
||||
$invoice = Invoice::findOrFail($id);
|
||||
|
||||
$data = $request->validate([
|
||||
'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',
|
||||
'notes' => 'nullable|string',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
|
||||
Log::info('✅ Validated Invoice Header Update Data', $data);
|
||||
@@ -140,17 +139,17 @@ class AdminInvoiceController extends Controller
|
||||
$invoice->refresh();
|
||||
|
||||
Log::info('🔍 Invoice AFTER HEADER UPDATE', [
|
||||
'invoice_id' => $invoice->id,
|
||||
'charge_groups_total' => $invoice->charge_groups_total,
|
||||
'gst_amount' => $invoice->gst_amount,
|
||||
'invoice_id' => $invoice->id,
|
||||
'charge_groups_total' => $invoice->charge_groups_total,
|
||||
'gst_amount' => $invoice->gst_amount,
|
||||
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
|
||||
]);
|
||||
|
||||
$this->generateInvoicePDF($invoice);
|
||||
|
||||
return redirect()
|
||||
->route('admin.invoices.edit', $invoice->id)
|
||||
->with('success', 'Invoice updated & PDF generated successfully.');
|
||||
->route('admin.invoices.edit', $invoice->id)
|
||||
->with('success', 'Invoice updated & PDF generated successfully.');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
@@ -160,12 +159,12 @@ class AdminInvoiceController extends Controller
|
||||
{
|
||||
Log::info('🟡 Invoice Items Update Request', [
|
||||
'invoice_id' => $invoice->id,
|
||||
'payload' => $request->all(),
|
||||
'payload' => $request->all(),
|
||||
]);
|
||||
|
||||
$data = $request->validate([
|
||||
'items' => ['required', 'array'],
|
||||
'items.*.price' => ['required', 'numeric', 'min:0'],
|
||||
'items' => ['required', 'array'],
|
||||
'items.*.price' => ['required', 'numeric', 'min:0'],
|
||||
'items.*.ttl_amount' => ['required', 'numeric', 'min:0'],
|
||||
]);
|
||||
|
||||
@@ -177,12 +176,12 @@ class AdminInvoiceController extends Controller
|
||||
if (!$item) {
|
||||
Log::warning('Invoice item not found or mismatched invoice', [
|
||||
'invoice_id' => $invoice->id,
|
||||
'item_id' => $itemId,
|
||||
'item_id' => $itemId,
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$item->price = $itemData['price'];
|
||||
$item->price = $itemData['price'];
|
||||
$item->ttl_amount = $itemData['ttl_amount'];
|
||||
$item->save();
|
||||
}
|
||||
@@ -195,15 +194,16 @@ class AdminInvoiceController extends Controller
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// PDF GENERATION
|
||||
// PDF GENERATION (EXISTING - केवळ chargeGroups load ला confirm कर)
|
||||
// -------------------------------------------------------------
|
||||
public function generateInvoicePDF($invoice)
|
||||
{
|
||||
$invoice->load(['items', 'customer', 'container']);
|
||||
// ✅ यामध्ये chargeGroups आणि installments load कर
|
||||
$invoice->load(['items', 'customer', 'container', 'chargeGroups.items', 'installments']);
|
||||
$shipment = null;
|
||||
|
||||
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
|
||||
$folder = public_path('invoices/');
|
||||
$folder = public_path('invoices/');
|
||||
|
||||
if (!file_exists($folder)) {
|
||||
mkdir($folder, 0777, true);
|
||||
@@ -216,13 +216,13 @@ class AdminInvoiceController extends Controller
|
||||
}
|
||||
|
||||
$mpdf = new Mpdf([
|
||||
'mode' => 'utf-8',
|
||||
'format' => 'A4',
|
||||
'mode' => 'utf-8',
|
||||
'format' => 'A4',
|
||||
'default_font' => 'sans-serif',
|
||||
]);
|
||||
|
||||
$html = view('admin.pdf.invoice', [
|
||||
'invoice' => $invoice,
|
||||
'invoice' => $invoice,
|
||||
'shipment' => $shipment,
|
||||
])->render();
|
||||
|
||||
@@ -239,9 +239,9 @@ class AdminInvoiceController extends Controller
|
||||
{
|
||||
$request->validate([
|
||||
'installment_date' => 'required|date',
|
||||
'payment_method' => 'required|string',
|
||||
'reference_no' => 'nullable|string',
|
||||
'amount' => 'required|numeric|min:1',
|
||||
'payment_method' => 'required|string',
|
||||
'reference_no' => 'nullable|string',
|
||||
'amount' => 'required|numeric|min:1',
|
||||
]);
|
||||
|
||||
$invoice = Invoice::findOrFail($invoice_id);
|
||||
@@ -253,51 +253,54 @@ class AdminInvoiceController extends Controller
|
||||
|
||||
if ($request->amount > $remaining) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'status' => 'error',
|
||||
'message' => 'Installment amount exceeds remaining balance.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$installment = InvoiceInstallment::create([
|
||||
'invoice_id' => $invoice_id,
|
||||
'invoice_id' => $invoice_id,
|
||||
'installment_date' => $request->installment_date,
|
||||
'payment_method' => $request->payment_method,
|
||||
'reference_no' => $request->reference_no,
|
||||
'amount' => $request->amount,
|
||||
'payment_method' => $request->payment_method,
|
||||
'reference_no' => $request->reference_no,
|
||||
'amount' => $request->amount,
|
||||
]);
|
||||
|
||||
$newPaid = $paidTotal + $request->amount;
|
||||
$newPaid = $paidTotal + $request->amount;
|
||||
$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([
|
||||
'payment_method' => $request->payment_method,
|
||||
'reference_no' => $request->reference_no,
|
||||
'status' => ($newPaid >= $grandTotal && $grandTotal > 0) ? 'paid' : $invoice->status,
|
||||
'reference_no' => $request->reference_no,
|
||||
'status' => $newStatus,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Installment added successfully.',
|
||||
'installment' => $installment,
|
||||
'status' => 'success',
|
||||
'message' => 'Installment added successfully.',
|
||||
'installment' => $installment,
|
||||
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
|
||||
'gstAmount' => $invoice->gst_amount ?? 0,
|
||||
'grandTotal' => $grandTotal,
|
||||
'totalPaid' => $newPaid,
|
||||
'remaining' => $remaining,
|
||||
'isCompleted' => $remaining <= 0,
|
||||
'isZero' => $newPaid == 0,
|
||||
'gstAmount' => $invoice->gst_amount ?? 0,
|
||||
'grandTotal' => $grandTotal,
|
||||
'totalPaid' => $newPaid,
|
||||
'remaining' => $remaining,
|
||||
'newStatus' => $newStatus,
|
||||
'isCompleted' => $remaining <= 0,
|
||||
'isZero' => $newPaid == 0,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -307,7 +310,7 @@ class AdminInvoiceController extends Controller
|
||||
public function deleteInstallment($id)
|
||||
{
|
||||
$installment = InvoiceInstallment::findOrFail($id);
|
||||
$invoice = $installment->invoice;
|
||||
$invoice = $installment->invoice;
|
||||
|
||||
$installment->delete();
|
||||
$invoice->refresh();
|
||||
@@ -317,21 +320,32 @@ class AdminInvoiceController extends Controller
|
||||
$paidTotal = $invoice->installments()->sum('amount');
|
||||
$remaining = max(0, $grandTotal - $paidTotal);
|
||||
|
||||
if ($paidTotal <= 0 && $grandTotal > 0) {
|
||||
$invoice->update(['status' => 'pending']);
|
||||
} elseif ($paidTotal > 0 && $paidTotal < $grandTotal) {
|
||||
$invoice->update(['status' => 'pending']);
|
||||
$isOverdue = now()->startOfDay()->gt(\Carbon\Carbon::parse($invoice->due_date)->startOfDay());
|
||||
|
||||
if ($grandTotal > 0 && $paidTotal >= $grandTotal) {
|
||||
$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([
|
||||
'status' => 'success',
|
||||
'message' => 'Installment deleted.',
|
||||
'status' => 'success',
|
||||
'message' => 'Installment deleted.',
|
||||
'chargeGroupsTotal' => $invoice->charge_groups_total ?? 0,
|
||||
'gstAmount' => $invoice->gst_amount ?? 0,
|
||||
'grandTotal' => $grandTotal,
|
||||
'totalPaid' => $paidTotal,
|
||||
'remaining' => $remaining,
|
||||
'isZero' => $paidTotal == 0,
|
||||
'gstAmount' => $invoice->gst_amount ?? 0,
|
||||
'grandTotal' => $grandTotal,
|
||||
'totalPaid' => $paidTotal,
|
||||
'remaining' => $remaining,
|
||||
'newStatus' => $newStatus,
|
||||
'isZero' => $paidTotal == 0,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -342,126 +356,149 @@ class AdminInvoiceController extends Controller
|
||||
{
|
||||
Log::info('🟡 storeChargeGroup HIT', [
|
||||
'invoice_id' => $invoiceId,
|
||||
'payload' => $request->all(),
|
||||
'payload' => $request->all(),
|
||||
]);
|
||||
|
||||
|
||||
$invoice = Invoice::with('items', 'chargeGroups')->findOrFail($invoiceId);
|
||||
|
||||
|
||||
$data = $request->validate([
|
||||
'groupname' => 'required|string|max:255',
|
||||
'basistype' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
|
||||
'basisvalue' => 'required|numeric',
|
||||
'rate' => 'required|numeric|min:0.0001',
|
||||
'autototal' => 'required|numeric|min:0.01',
|
||||
'itemids' => 'required|array',
|
||||
'itemids.*' => 'integer|exists:invoice_items,id',
|
||||
|
||||
'tax_type' => 'nullable|in:none,gst,igst',
|
||||
'gst_percent' => 'nullable|numeric|min:0|max:28',
|
||||
'groupname' => 'required|string|max:255',
|
||||
'basistype' => 'required|in:ttl_qty,amount,ttl_cbm,ttl_kg',
|
||||
'basisvalue' => 'required|numeric',
|
||||
'rate' => 'required|numeric|min:0.0001',
|
||||
'autototal' => 'required|numeric|min:0.01',
|
||||
'itemids' => 'required|array',
|
||||
'itemids.*' => 'integer|exists:invoice_items,id',
|
||||
|
||||
'tax_type' => 'nullable|in:none,gst,igst',
|
||||
'gst_percent' => 'nullable|numeric|min:0|max:28',
|
||||
'total_with_gst' => 'nullable|numeric|min:0',
|
||||
]);
|
||||
|
||||
|
||||
Log::info('✅ storeChargeGroup VALIDATED', $data);
|
||||
|
||||
// duplicate name check
|
||||
|
||||
$exists = InvoiceChargeGroup::where('invoice_id', $invoice->id)
|
||||
->where('group_name', $data['groupname'])
|
||||
->exists();
|
||||
|
||||
|
||||
if ($exists) {
|
||||
return back()
|
||||
->withErrors(['groupname' => 'This group name is already used for this invoice.'])
|
||||
->withInput();
|
||||
}
|
||||
|
||||
$taxType = $data['tax_type'] ?? 'gst';
|
||||
|
||||
$taxType = $data['tax_type'] ?? 'gst';
|
||||
$gstPercent = $data['gst_percent'] ?? 0;
|
||||
$baseTotal = $data['autototal'];
|
||||
|
||||
$baseTotal = $data['autototal'];
|
||||
|
||||
$totalWithGst = $data['total_with_gst'] ?? $baseTotal;
|
||||
if ($totalWithGst == 0 && $gstPercent > 0) {
|
||||
$gstAmount = ($baseTotal * $gstPercent) / 100;
|
||||
$gstAmount = ($baseTotal * $gstPercent) / 100;
|
||||
$totalWithGst = $baseTotal + $gstAmount;
|
||||
}
|
||||
|
||||
// 1) Group create
|
||||
|
||||
$group = InvoiceChargeGroup::create([
|
||||
'invoice_id' => $invoice->id,
|
||||
'group_name' => $data['groupname'],
|
||||
'basis_type' => $data['basistype'],
|
||||
'basis_value' => $data['basisvalue'],
|
||||
'rate' => $data['rate'],
|
||||
'total_charge' => $baseTotal,
|
||||
'tax_type' => $taxType,
|
||||
'gst_percent' => $gstPercent,
|
||||
'invoice_id' => $invoice->id,
|
||||
'group_name' => $data['groupname'],
|
||||
'basis_type' => $data['basistype'],
|
||||
'basis_value' => $data['basisvalue'],
|
||||
'rate' => $data['rate'],
|
||||
'total_charge' => $baseTotal,
|
||||
'tax_type' => $taxType,
|
||||
'gst_percent' => $gstPercent,
|
||||
'total_with_gst' => $totalWithGst,
|
||||
]);
|
||||
|
||||
// 2) Items link
|
||||
|
||||
foreach ($data['itemids'] as $itemId) {
|
||||
InvoiceChargeGroupItem::create([
|
||||
'group_id' => $group->id,
|
||||
'group_id' => $group->id,
|
||||
'invoice_item_id' => $itemId,
|
||||
]);
|
||||
}
|
||||
|
||||
// 3) सर्व groups वरून invoice level totals
|
||||
|
||||
$invoice->load('chargeGroups');
|
||||
|
||||
$chargeGroupsBase = $invoice->chargeGroups->sum('total_charge'); // base
|
||||
$chargeGroupsWithG = $invoice->chargeGroups->sum('total_with_gst'); // base + gst
|
||||
$chargeGroupsGst = $chargeGroupsWithG - $chargeGroupsBase; // gst only
|
||||
|
||||
|
||||
$chargeGroupsBase = $invoice->chargeGroups->sum('total_charge');
|
||||
$chargeGroupsWithG = $invoice->chargeGroups->sum('total_with_gst');
|
||||
$chargeGroupsGst = $chargeGroupsWithG - $chargeGroupsBase;
|
||||
|
||||
$invoiceGstPercent = $group->gst_percent ?? 0;
|
||||
$invoiceTaxType = $group->tax_type ?? 'gst';
|
||||
|
||||
$invoiceTaxType = $group->tax_type ?? 'gst';
|
||||
|
||||
$cgstPercent = 0;
|
||||
$sgstPercent = 0;
|
||||
$igstPercent = 0;
|
||||
|
||||
|
||||
if ($invoiceTaxType === 'gst') {
|
||||
$cgstPercent = $invoiceGstPercent / 2;
|
||||
$sgstPercent = $invoiceGstPercent / 2;
|
||||
} elseif ($invoiceTaxType === 'igst') {
|
||||
$igstPercent = $invoiceGstPercent;
|
||||
}
|
||||
|
||||
// 🔴 इथे main fix:
|
||||
// final_amount = base (total_charge sum)
|
||||
// final_amount_with_gst = base + gst (total_with_gst sum)
|
||||
// grand_total_with_charges = final_amount_with_gst (same)
|
||||
|
||||
$invoice->update([
|
||||
'charge_groups_total' => $chargeGroupsBase,
|
||||
'gst_amount' => $chargeGroupsGst,
|
||||
'gst_percent' => $invoiceGstPercent,
|
||||
'tax_type' => $invoiceTaxType,
|
||||
'cgst_percent' => $cgstPercent,
|
||||
'sgst_percent' => $sgstPercent,
|
||||
'igst_percent' => $igstPercent,
|
||||
|
||||
'final_amount' => $chargeGroupsBase,
|
||||
'final_amount_with_gst' => $chargeGroupsWithG,
|
||||
'charge_groups_total' => $chargeGroupsBase,
|
||||
'gst_amount' => $chargeGroupsGst,
|
||||
'gst_percent' => $invoiceGstPercent,
|
||||
'tax_type' => $invoiceTaxType,
|
||||
'cgst_percent' => $cgstPercent,
|
||||
'sgst_percent' => $sgstPercent,
|
||||
'igst_percent' => $igstPercent,
|
||||
|
||||
'final_amount' => $chargeGroupsBase,
|
||||
'final_amount_with_gst' => $chargeGroupsWithG,
|
||||
'grand_total_with_charges' => $chargeGroupsWithG,
|
||||
]);
|
||||
|
||||
|
||||
Log::info('✅ Invoice updated from Charge Group (CG total + GST)', [
|
||||
'invoice_id' => $invoice->id,
|
||||
'charge_groups_total' => $chargeGroupsBase,
|
||||
'gst_amount' => $chargeGroupsGst,
|
||||
'gst_percent' => $invoiceGstPercent,
|
||||
'tax_type' => $invoiceTaxType,
|
||||
'cgst_percent' => $cgstPercent,
|
||||
'sgst_percent' => $sgstPercent,
|
||||
'igst_percent' => $igstPercent,
|
||||
'final_amount' => $invoice->final_amount,
|
||||
'final_amount_with_gst' => $invoice->final_amount_with_gst,
|
||||
'invoice_id' => $invoice->id,
|
||||
'charge_groups_total' => $chargeGroupsBase,
|
||||
'gst_amount' => $chargeGroupsGst,
|
||||
'grand_total_with_charges'=> $invoice->grand_total_with_charges,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Charge group saved successfully.',
|
||||
'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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,104 +5,197 @@ namespace App\Http\Controllers\Admin;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use Mpdf\Mpdf;
|
||||
|
||||
class AdminReportController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the reports page with joined data
|
||||
*/
|
||||
// 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)
|
||||
// UI साठी main action
|
||||
public function containerReport(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')
|
||||
->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(
|
||||
// INVOICE
|
||||
'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.id as container_id',
|
||||
'containers.container_number',
|
||||
'containers.container_date',
|
||||
'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();
|
||||
|
||||
return view('admin.reports', compact('reports'));
|
||||
DB::raw('COUNT(DISTINCT invoices.mark_no) as total_mark_nos'),
|
||||
DB::raw('COUNT(DISTINCT invoices.customer_id) as total_customers'),
|
||||
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 . '"',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -565,8 +565,6 @@ class ContainerController extends Controller
|
||||
$invoice->save();
|
||||
$invoiceCount++;
|
||||
|
||||
$totalAmount = 0;
|
||||
|
||||
foreach ($rowsForCustomer as $item) {
|
||||
$row = $item['row'];
|
||||
$offset = $item['offset'];
|
||||
@@ -606,16 +604,7 @@ class ContainerController extends Controller
|
||||
'shop_no' => $shopNo,
|
||||
'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).";
|
||||
@@ -972,4 +961,4 @@ class ContainerController extends Controller
|
||||
'summary' => $summary,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
public/invoices/invoice-INV-2026-000253.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000253.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000256.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000256.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000257.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000257.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000258.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000258.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000259.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000259.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000260.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000260.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000261.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000261.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000262.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000262.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000263.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000263.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000264.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000264.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2026-000265.pdf
Normal file
BIN
public/invoices/invoice-INV-2026-000265.pdf
Normal file
Binary file not shown.
@@ -40,7 +40,6 @@
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* DOWNLOAD BUTTONS - NEW STYLES */
|
||||
.cm-download-pdf {
|
||||
background: linear-gradient(100deg, #4c6fff 0%, #8e54e9 100%) !important;
|
||||
color: #fff !important;
|
||||
@@ -287,7 +286,7 @@
|
||||
}
|
||||
|
||||
.cm-table-scroll-outer {
|
||||
margin: 10px 14px 0 14px;
|
||||
margin: 10px 14px 30px 14px;
|
||||
border-radius: 14px;
|
||||
border: 1.5px solid #c9a359;
|
||||
box-shadow: 0 4px 14px rgba(76,111,255,0.18);
|
||||
@@ -487,11 +486,6 @@
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-table-scroll-outer {
|
||||
margin: 10px 14px 30px 14px; /* फक्त हे बदल करा */
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="container-fluid cm-wrapper">
|
||||
@@ -522,7 +516,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- बाकीचा सगळा code same आहे - काही बदल नाही -->
|
||||
<div class="card cm-main-card">
|
||||
<div class="card-header">
|
||||
<h5>Container Information</h5>
|
||||
@@ -682,7 +675,7 @@
|
||||
|
||||
<div class="cm-filter-bar">
|
||||
<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>
|
||||
<input type="text" id="cmRowSearch" class="cm-filter-input"
|
||||
placeholder="Quick search..." onkeyup="cmFilterRows()">
|
||||
@@ -745,9 +738,9 @@
|
||||
str_contains($norm, 'TOTALAMOUNT')
|
||||
);
|
||||
|
||||
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
|
||||
$isTotalColumn = $isTotalQty || $isTotalCbm || $isTotalKg || $isAmount;
|
||||
$isLockedByInvoice = in_array($row->row_index, $lockedRowIndexes ?? []);
|
||||
$isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice;
|
||||
$isReadOnly = $isTotalColumn || $container->status !== 'pending' || $isLockedByInvoice;
|
||||
@endphp
|
||||
|
||||
<td>
|
||||
@@ -792,7 +785,6 @@
|
||||
</button>
|
||||
@endif
|
||||
|
||||
<!-- Toast notification missing होती, add केली -->
|
||||
<div id="cmToast" class="cm-toast"></div>
|
||||
|
||||
<script>
|
||||
@@ -800,7 +792,7 @@ function cmFilterRows() {
|
||||
const input = document.getElementById('cmRowSearch');
|
||||
if (!input) return;
|
||||
const filter = input.value.toLowerCase();
|
||||
const table = document.getElementById('cmExcelTable');
|
||||
const table = document.getElementById('cmExcelTable');
|
||||
if (!table) return;
|
||||
const rows = table.getElementsByTagName('tr');
|
||||
for (let i = 1; i < rows.length; i++) {
|
||||
@@ -831,6 +823,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
setTimeout(() => toast.classList.remove('cm-show'), 2500);
|
||||
}
|
||||
|
||||
// फक्त number काढण्यासाठी helper, पण cell मध्ये format नाही करणार
|
||||
function parseNumber(str) {
|
||||
if (!str) return 0;
|
||||
const cleaned = String(str).replace(/,/g, '').trim();
|
||||
@@ -838,11 +831,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
return isNaN(val) ? 0 : val;
|
||||
}
|
||||
|
||||
function formatNumber(val, decimals) {
|
||||
if (isNaN(val)) val = 0;
|
||||
return val.toFixed(decimals);
|
||||
}
|
||||
|
||||
function recalcRow(row) {
|
||||
const inputs = row.querySelectorAll('.cm-cell-input');
|
||||
|
||||
@@ -894,27 +882,28 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}
|
||||
});
|
||||
|
||||
// इथे आपण फक्त VALUE बदलतो, कोणतंही toFixed नाही वापरत
|
||||
if (ttlQtyInput && ctnInput && qtyInput) {
|
||||
const newTtlQty = ctn * qty;
|
||||
ttlQtyInput.value = formatNumber(newTtlQty, 0);
|
||||
ttlQtyInput.value = newTtlQty === 0 ? '' : String(newTtlQty);
|
||||
ttlQty = newTtlQty;
|
||||
}
|
||||
|
||||
if (ttlCbmInput && cbmInput && ctnInput) {
|
||||
const newTtlCbm = cbm * ctn;
|
||||
ttlCbmInput.value = formatNumber(newTtlCbm, 3);
|
||||
ttlCbmInput.value = newTtlCbm === 0 ? '' : String(newTtlCbm);
|
||||
ttlCbm = newTtlCbm;
|
||||
}
|
||||
|
||||
if (ttlKgInput && kgInput && ctnInput) {
|
||||
const newTtlKg = kg * ctn;
|
||||
ttlKgInput.value = formatNumber(newTtlKg, 2);
|
||||
ttlKgInput.value = newTtlKg === 0 ? '' : String(newTtlKg);
|
||||
ttlKg = newTtlKg;
|
||||
}
|
||||
|
||||
if (amountInput && priceInput && ttlQtyInput) {
|
||||
const newAmount = price * ttlQty;
|
||||
amountInput.value = formatNumber(newAmount, 2);
|
||||
amountInput.value = newAmount === 0 ? '' : String(newAmount);
|
||||
amount = newAmount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,7 +661,8 @@
|
||||
<th class="table-header">Customer ID</th>
|
||||
<th class="table-header">Orders</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">Create Date</th>
|
||||
<th class="table-header">Status</th>
|
||||
@@ -672,20 +673,27 @@
|
||||
<tbody id="customersTableBody">
|
||||
@forelse($customers as $c)
|
||||
@php
|
||||
// Orders = invoice count
|
||||
// 1) Orders = total invoice count
|
||||
$ordersCount = $c->invoices->count();
|
||||
|
||||
// Order Total = items total from all invoices (final_amount)
|
||||
$orderTotal = $c->invoices->sum('final_amount');
|
||||
// 2) Order Total = सर्व invoices च्या charge groups चा base total (without GST)
|
||||
$orderTotal = $c->invoices->sum(function($invoice) {
|
||||
return $invoice->chargeGroups->sum('total_charge');
|
||||
});
|
||||
|
||||
// Total payable = grand total with GST + groups
|
||||
$totalPayable = $c->invoices->sum('grand_total_with_charges');
|
||||
// 3) GST Amount = सर्व invoices च्या gst_amount चा sum
|
||||
$gstTotal = $c->invoices->sum('gst_amount');
|
||||
|
||||
// Total paid via installments
|
||||
$totalPaid = $c->invoiceInstallments->sum('amount');
|
||||
// 3) Total Payable = customer ने किती paid केले (installments sum)
|
||||
$totalPaid = $c->invoices->flatMap->installments->sum('amount');
|
||||
|
||||
// Remaining amount
|
||||
$remainingAmount = max($totalPayable - $totalPaid, 0);
|
||||
// 4) Remaining = grand_total_with_charges - paid
|
||||
$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
|
||||
<tr>
|
||||
<td class="customer-info-column">
|
||||
@@ -724,7 +732,13 @@
|
||||
|
||||
<td class="total-column">
|
||||
<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>
|
||||
</td>
|
||||
|
||||
@@ -771,7 +785,7 @@
|
||||
</tr>
|
||||
@empty
|
||||
<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>
|
||||
<span class="text-muted">No customers found.</span>
|
||||
</td>
|
||||
@@ -836,4 +850,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
@endsection
|
||||
@@ -1236,7 +1236,7 @@
|
||||
<th class="column-header">Customer</th>
|
||||
<th class="column-header">Container</th> {{-- NEW --}}
|
||||
<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">Status</th>
|
||||
<th class="column-header">Invoice Date</th>
|
||||
@@ -1286,8 +1286,8 @@
|
||||
₹{{ number_format($invoice->final_amount, 2) }}
|
||||
</td>
|
||||
|
||||
<td class="gst-cell">
|
||||
{{ $invoice->gst_percent }}%
|
||||
<td class="amount-cell">
|
||||
₹{{ number_format($invoice->gst_amount, 2) }}
|
||||
</td>
|
||||
|
||||
<td class="amount-cell">
|
||||
@@ -1379,8 +1379,8 @@
|
||||
<span class="mobile-detail-value">₹{{ number_format($invoice->final_amount, 2) }}</span>
|
||||
</div>
|
||||
<div class="mobile-detail-item">
|
||||
<span class="mobile-detail-label">GST</span>
|
||||
<span class="mobile-detail-value">{{ $invoice->gst_percent }}%</span>
|
||||
<span class="mobile-detail-label">GST Amount</span>
|
||||
<span class="mobile-detail-value">₹{{ number_format($invoice->gst_amount, 2) }}</span>
|
||||
</div>
|
||||
<div class="mobile-detail-item">
|
||||
<span class="mobile-detail-label">Total</span>
|
||||
@@ -1694,7 +1694,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
${invoice.container ? (invoice.container.container_number ?? '—') : '—'}
|
||||
</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>
|
||||
<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>
|
||||
</div>
|
||||
<div class="mobile-detail-item">
|
||||
<span class="mobile-detail-label">GST</span>
|
||||
<span class="mobile-detail-value">${invoice.gst_percent}%</span>
|
||||
<span class="mobile-detail-label">GST Amount</span>
|
||||
<span class="mobile-detail-value">₹${parseFloat(invoice.gst_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</span>
|
||||
</div>
|
||||
<div class="mobile-detail-item">
|
||||
<span class="mobile-detail-label">Total</span>
|
||||
|
||||
@@ -325,95 +325,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Edit Invoice Header --}}
|
||||
<div class="glass-card">
|
||||
<div class="card-header-compact d-flex justify-content-between align-items-center">
|
||||
<h4>
|
||||
<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>
|
||||
@if(false)
|
||||
{{-- Edit Invoice Details card – HIDDEN --}}
|
||||
{{-- तुझा full header edit form इथे hidden ठेवलेला आहे --}}
|
||||
@endif
|
||||
|
||||
@php
|
||||
$totalPaid = $invoice->totalPaid();
|
||||
$remaining = $invoice->remainingAmount();
|
||||
|
||||
// Mixed tax type label from charge groups
|
||||
$taxTypes = $invoice->chargeGroups
|
||||
? $invoice->chargeGroups->pluck('tax_type')->filter()->unique()->values()
|
||||
: collect([]);
|
||||
@@ -440,47 +360,10 @@
|
||||
}
|
||||
@endphp
|
||||
|
||||
{{-- Amount Breakdown --}}
|
||||
<div class="amount-breakdown-compact">
|
||||
<h6 class="fw-bold mb-3 text-dark">
|
||||
<i class="fas fa-calculator me-2"></i>Amount Breakdown
|
||||
</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>
|
||||
@if(false)
|
||||
{{-- Amount Breakdown HIDDEN --}}
|
||||
{{-- जुनं breakdown section इथे hidden आहे --}}
|
||||
@endif
|
||||
|
||||
{{-- Summary cards --}}
|
||||
<div class="summary-grid-compact">
|
||||
@@ -491,13 +374,13 @@
|
||||
<div class="summary-label-compact">Grand Total (Charges + GST)</div>
|
||||
</div>
|
||||
<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) }}
|
||||
</div>
|
||||
<div class="summary-label-compact">Total Paid</div>
|
||||
</div>
|
||||
<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) }}
|
||||
</div>
|
||||
<div class="summary-label-compact">Remaining</div>
|
||||
@@ -683,91 +566,128 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": submitForm.querySelector('input[name="_token"]').value,
|
||||
"Accept": "application/json",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
body: new FormData(submitForm)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
|
||||
submitBtn.disabled = false;
|
||||
.then(async res => {
|
||||
let data;
|
||||
try {
|
||||
data = await res.json();
|
||||
} catch (e) {
|
||||
throw new Error("Invalid server response.");
|
||||
}
|
||||
|
||||
if (data.status === "error") {
|
||||
alert(data.message);
|
||||
return;
|
||||
}
|
||||
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
|
||||
submitBtn.disabled = false;
|
||||
|
||||
const table = document.getElementById("installmentTable");
|
||||
const index = table.rows.length + 1;
|
||||
if (!res.ok) {
|
||||
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", `
|
||||
<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">
|
||||
${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>
|
||||
`);
|
||||
const table = document.getElementById("installmentTable");
|
||||
const index = table.querySelectorAll("tr").length + 1;
|
||||
|
||||
if (document.getElementById("paidAmount")) {
|
||||
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);
|
||||
}
|
||||
document.getElementById("noInstallmentsMsg")?.classList.add("d-none");
|
||||
|
||||
if (document.getElementById("totalInvoiceWithGst")) {
|
||||
document.getElementById("totalInvoiceWithGst").textContent =
|
||||
"₹" + formatINR(data.grandTotal);
|
||||
}
|
||||
const totalCard = document.getElementById("totalInvoiceWithGstCard");
|
||||
if (totalCard) {
|
||||
totalCard.textContent = "₹" + formatINR(data.grandTotal);
|
||||
}
|
||||
const pmMethod = data.installment.payment_method
|
||||
? data.installment.payment_method.toUpperCase()
|
||||
: "-";
|
||||
const refNo = data.installment.reference_no
|
||||
? `<span class="text-muted">${data.installment.reference_no}</span>`
|
||||
: '<span class="text-muted">-</span>';
|
||||
|
||||
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
||||
if (paidCard) paidCard.textContent = "₹" + formatINR(data.totalPaid);
|
||||
table.insertAdjacentHTML("beforeend", `
|
||||
<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 (remainingCard) remainingCard.textContent = "₹" + formatINR(data.remaining);
|
||||
if (document.getElementById("paidAmount")) {
|
||||
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) {
|
||||
toggleBtn.remove();
|
||||
formBox.classList.add("d-none");
|
||||
}
|
||||
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
|
||||
if (remainingCard) remainingCard.textContent = "₹" + formatINR(data.remaining);
|
||||
|
||||
alert(data.message);
|
||||
})
|
||||
.catch(() => {
|
||||
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Submit Installment';
|
||||
submitBtn.disabled = false;
|
||||
alert("Something went wrong. Please try again.");
|
||||
});
|
||||
const fsTotal = document.getElementById("finalSummaryTotalPaid");
|
||||
if (fsTotal) fsTotal.textContent = "₹" + formatINR(data.totalPaid);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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: {
|
||||
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').getAttribute("content"),
|
||||
"Accept": "application/json",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.status === "success") {
|
||||
row.style.opacity = 0;
|
||||
setTimeout(() => row.remove(), 300);
|
||||
.then(async res => {
|
||||
let data;
|
||||
try { data = await res.json(); } catch(e) { throw new Error("Invalid server response."); }
|
||||
|
||||
if (document.getElementById("paidAmount")) {
|
||||
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 (!res.ok || data.status !== "success") {
|
||||
const msg = data && data.message ? data.message : "Something went wrong. Please try again.";
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
||||
if (paidCard) paidCard.textContent = "₹" + formatINR(data.totalPaid);
|
||||
row.style.opacity = 0;
|
||||
setTimeout(() => row.remove(), 300);
|
||||
|
||||
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
|
||||
if (remainingCard) remainingCard.textContent = "₹" + formatINR(data.remaining);
|
||||
if (document.getElementById("paidAmount")) {
|
||||
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) {
|
||||
document.getElementById("noInstallmentsMsg")?.classList.remove("d-none");
|
||||
}
|
||||
const paidCard = document.querySelector(".summary-card-compact.paid .summary-value-compact");
|
||||
if (paidCard) paidCard.textContent = "₹" + formatINR(data.totalPaid);
|
||||
|
||||
alert(data.message);
|
||||
} else {
|
||||
alert(data.message || "Something went wrong. Please try again.");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
alert("Something went wrong. Please try again.");
|
||||
});
|
||||
const remainingCard = document.querySelector(".summary-card-compact.remaining .summary-value-compact");
|
||||
if (remainingCard) remainingCard.textContent = "₹" + formatINR(data.remaining);
|
||||
|
||||
const fsTotal = document.getElementById("finalSummaryTotalPaid");
|
||||
if (fsTotal) fsTotal.textContent = "₹" + formatINR(data.totalPaid);
|
||||
|
||||
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
|
||||
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)
|
||||
// Header AJAX (सध्या card hidden आहे, तरीही ठेवलेलं)
|
||||
const headerForm = document.getElementById('invoiceHeaderForm');
|
||||
const headerBtn = document.getElementById('btnHeaderSave');
|
||||
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...';
|
||||
|
||||
const formData = new FormData(headerForm);
|
||||
|
||||
|
||||
fetch(headerForm.action, {
|
||||
method: 'POST',
|
||||
@@ -884,7 +807,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
body: formData
|
||||
})
|
||||
.then(async res => {
|
||||
let data = null;
|
||||
let data;
|
||||
try { data = await res.json(); } catch(e) {}
|
||||
|
||||
if (!res.ok) {
|
||||
@@ -899,7 +822,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
headerMsg.classList.remove('text-danger');
|
||||
headerMsg.classList.add('text-light');
|
||||
|
||||
// popup_invoice वरचा status badge update करायचा असल्यास:
|
||||
const status = document.getElementById('statusSelect')?.value;
|
||||
const badge = document.querySelector('.status-badge');
|
||||
if (badge && status) {
|
||||
|
||||
@@ -1,272 +1,623 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ $invoice->invoice_number }}</title>
|
||||
<title>Invoice #{{ $invoice->invoice_number }}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', 'Roboto', Arial, sans-serif;
|
||||
background: #F7FBFC;
|
||||
color: #1A222B;
|
||||
font-family: 'DejaVu Sans', sans-serif;
|
||||
font-size: 10px;
|
||||
color: #1a202c;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
.container {
|
||||
max-width: 850px;
|
||||
margin: 24px auto 0 auto;
|
||||
padding: 18px;
|
||||
background: #fff;
|
||||
border-radius: 13px;
|
||||
box-shadow: 0 2px 14px rgba(40,105,160,0.08);
|
||||
padding: 35px 32px 18px 32px;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
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;
|
||||
|
||||
/* ── TOP HEADER ── */
|
||||
.top-header {
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
border-bottom: 2px solid #1a202c;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
.invoice-details {
|
||||
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 {
|
||||
.header-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 9px;
|
||||
margin-bottom: 13px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
th {
|
||||
background: #F6F7F9;
|
||||
padding: 10px 0;
|
||||
font-size: 15px;
|
||||
color: #6781A6;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
.header-table td {
|
||||
vertical-align: middle;
|
||||
padding: 0;
|
||||
}
|
||||
/* Logo cell — fixed width, logo fits inside */
|
||||
.logo-cell {
|
||||
width: 60px;
|
||||
}
|
||||
.logo-cell img {
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
display: block;
|
||||
}
|
||||
/* Company name cell */
|
||||
.title-cell {
|
||||
padding-left: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
td {
|
||||
padding: 7px 0;
|
||||
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 {
|
||||
.company-name {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #23355D;
|
||||
color: #1a202c;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
.gst-row td {
|
||||
font-weight: 500;
|
||||
color: #23BF47;
|
||||
}
|
||||
.total-row td {
|
||||
.company-subtitle {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
font-size: 17px;
|
||||
color: #222;
|
||||
color: #2d3748;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.payment-info {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 9px;
|
||||
font-size: 15px;
|
||||
.company-tagline {
|
||||
font-size: 8px;
|
||||
color: #718096;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.ref-number {
|
||||
font-size: 14px;
|
||||
color: #6781A6;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 2px;
|
||||
|
||||
/* ── INFO TABLE (Customer | Invoice side by side) ── */
|
||||
.info-outer {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #cbd5e0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.footer {
|
||||
border-top: 1.2px solid #E6EBF0;
|
||||
margin-top: 25px;
|
||||
padding-top: 12px;
|
||||
font-size: 16px;
|
||||
color: #888;
|
||||
.info-outer td {
|
||||
vertical-align: top;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
.info-left { width: 50%; }
|
||||
.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;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.footer strong {
|
||||
color: #222;
|
||||
font-weight: 500;
|
||||
.sbox-row1 { background: #ffffff; }
|
||||
.sbox-row2 { background: #f7fafc; }
|
||||
.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>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Header Section -->
|
||||
<div class="header">
|
||||
<div class="logo-company">
|
||||
<img src="{{ public_path('images/kent_logo2.png') }}" class="logo">
|
||||
<div class="company-details">
|
||||
<div class="company-title">{{ $invoice->company_name ?? 'Kent International Pvt. Ltd.' }}</div>
|
||||
<div class="company-sub"></div>
|
||||
{{ $invoice->company_address ?? '123 Business Park, Sector 5' }}<br>
|
||||
{{ $invoice->company_city ?? 'Gurugram, Haryana 122001' }}<br>
|
||||
{{ $invoice->company_country ?? 'India' }}<br>
|
||||
GST: {{ $invoice->company_gst ?? 'GST123456789' }}<br>
|
||||
Email: {{ $invoice->company_email ?? 'billing@kent.com' }}<br>
|
||||
Phone: {{ $invoice->company_phone ?? '+91 124 123 4567' }}
|
||||
|
||||
{{-- ══ VARIABLES ══ --}}
|
||||
@php
|
||||
$companyName = $invoice->company_name ?: ($invoice->customer->company_name ?? null);
|
||||
$custName = $invoice->customer_name;
|
||||
$custAddress = $invoice->customer_address ?: ($invoice->customer->address ?? null);
|
||||
$custPincode = $invoice->pincode ?: ($invoice->customer->pincode ?? null);
|
||||
$custEmail = $invoice->customer_email ?: ($invoice->customer->email ?? null);
|
||||
$custMobile = $invoice->customer_mobile ?: ($invoice->customer->mobile_no ?? null);
|
||||
@endphp
|
||||
|
||||
{{-- ══ 1) HEADER — Logo + Company Name ══ --}}
|
||||
<div class="top-header">
|
||||
<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 |
|
||||
Email: info@company.com |
|
||||
Phone: +91 1234567890
|
||||
</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 class="invoice-details">
|
||||
<div class="invoice-title">INVOICE</div>
|
||||
Invoice #: {{ $invoice->invoice_number }}<br>
|
||||
Issue Date: {{ date('d M Y', strtotime($invoice->invoice_date)) }}<br>
|
||||
Due Date: {{ date('d M Y', strtotime($invoice->due_date)) }}
|
||||
@if(strtolower($invoice->status) == 'paid')
|
||||
<div class="paid-label">
|
||||
<span class="paid-tag">Paid</span>
|
||||
</div>
|
||||
<div class="paid-date">
|
||||
Paid on: {{ date('d M Y', strtotime($invoice->paid_date ?? now())) }}
|
||||
</div>
|
||||
@else
|
||||
<div class="paid-date" style="color:#d00;">Status: {{ ucfirst($invoice->status) }}</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bill To Section -->
|
||||
<div class="bill-section">
|
||||
<div class="bill-title">Bill To</div>
|
||||
<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 class="info-line"><strong>GST Type:</strong> {{ strtoupper($invoice->tax_type ?? 'GST') }}</div>
|
||||
<div class="info-line"><strong>GST %:</strong> {{ number_format($invoice->gst_percent ?? 0, 2) }}%</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{{-- ══ 3) BILL TO ══ --}}
|
||||
<div class="bill-to-box">
|
||||
<div class="bill-to-label">Bill To</div>
|
||||
@if($companyName)
|
||||
<div class="bill-name">{{ $companyName }}</div>
|
||||
<div class="bill-sub">{{ $custName }}</div>
|
||||
@else
|
||||
<div class="bill-name">{{ $custName }}</div>
|
||||
@endif
|
||||
<div class="bill-detail">
|
||||
@if($custAddress){{ $custAddress }}<br>@endif
|
||||
@if($custPincode){{ $custPincode }}<br>@endif
|
||||
@if($custEmail)Email: {{ $custEmail }}<br>@endif
|
||||
@if($custMobile)Phone: {{ $custMobile }}@endif
|
||||
</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 & 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! | For queries: info@company.com | +91 1234567890
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -95,6 +95,7 @@
|
||||
.status-paid { background: #dcfce7; color: #166534; }
|
||||
.status-overdue { background: #fee2e2; color: #991b1b; }
|
||||
.status-pending { background: #fef3c7; color: #92400e; }
|
||||
.status-paying { background: #dbeafe; color: #1e40af; }
|
||||
.status-default { background: #f1f5f9; color: #475569; }
|
||||
|
||||
/* ── ID BOXES ── */
|
||||
@@ -681,19 +682,23 @@
|
||||
</div>
|
||||
<div>
|
||||
@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
|
||||
</span>
|
||||
@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
|
||||
</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')
|
||||
<span class="status-badge status-pending">
|
||||
<span class="status-badge status-pending" id="invoiceStatusBadge">
|
||||
<i class="fas fa-clock"></i> Pending
|
||||
</span>
|
||||
@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) }}
|
||||
</span>
|
||||
@endif
|
||||
@@ -1194,6 +1199,7 @@
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Description</th>
|
||||
<th class="text-center">Mark No</th>
|
||||
<th class="text-center">QTY</th>
|
||||
<th class="text-center">TTL QTY</th>
|
||||
<th class="text-center">CBM</th>
|
||||
@@ -1203,7 +1209,6 @@
|
||||
<th class="text-end">TTL Amount</th>
|
||||
<th class="text-end">Rate</th>
|
||||
<th class="text-end">Total Charge</th>
|
||||
{{-- नवीन GST % कॉलम --}}
|
||||
<th class="text-end">GST %</th>
|
||||
<th class="text-end">Item GST</th>
|
||||
<th class="text-end">Item Total (with GST)</th>
|
||||
@@ -1243,34 +1248,27 @@
|
||||
$itemTotalWithGst = $itemTotal + $itemGst;
|
||||
}
|
||||
|
||||
$itemGstPercent = $groupGstPercent ?? 0; // same as group
|
||||
$itemGstPercent = $groupGstPercent ?? 0;
|
||||
@endphp
|
||||
|
||||
<tr>
|
||||
<td>{{ $giIndex + 1 }}</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->ttl_qty }}</td>
|
||||
<td class="text-center">{{ $it->cbm }}</td>
|
||||
<td class="text-center">{{ $it->ttl_cbm }}</td>
|
||||
<td class="text-center">{{ $it->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">
|
||||
{{ number_format($rate, 2) }}
|
||||
</td>
|
||||
<td class="text-end">{{ 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;">
|
||||
{{ number_format($itemTotal, 2) }}
|
||||
</td>
|
||||
|
||||
{{-- GST % (per item = group GST %) --}}
|
||||
<td class="text-end">
|
||||
{{ number_format($itemGstPercent, 2) }}%
|
||||
</td>
|
||||
|
||||
<td class="text-end" style="color:#ef4444;">
|
||||
{{ $itemGst > 0 ? number_format($itemGst, 2) : '0.00' }}
|
||||
</td>
|
||||
@@ -1293,8 +1291,23 @@
|
||||
</div>
|
||||
</div>
|
||||
@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-header-compact">
|
||||
<i class="fas fa-calculator"></i>
|
||||
@@ -1302,13 +1315,6 @@
|
||||
</div>
|
||||
|
||||
<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) --}}
|
||||
<div class="summary-row-compact">
|
||||
<span class="label">Total Charge (Before GST)</span>
|
||||
@@ -1333,23 +1339,53 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
{{--
|
||||
<div class="summary-row-compact total">
|
||||
<span class="label">Total Charge With GST</span>
|
||||
<span class="value">
|
||||
₹{{ number_format($invoice->final_amount_with_gst, 2) }}
|
||||
{{-- Total Paid (installments sum) --}}
|
||||
<div class="summary-row-compact">
|
||||
<span class="label" style="color:#10b981;font-weight:600;">
|
||||
<i class="fas fa-check-circle me-1" style="font-size:0.8rem;"></i>Total Paid
|
||||
</span>
|
||||
<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>
|
||||
</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 ═══════════════════════════ -->
|
||||
<div class="invoice-footer">
|
||||
<!-- <div class="invoice-footer">
|
||||
@if($invoice->pdf_path && $showActions)
|
||||
<a href="{{ asset($invoice->pdf_path) }}" class="btn-download" download>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
function shareInvoice() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -71,7 +71,19 @@ Route::prefix('admin')
|
||||
Route::get('/dashboard', [AdminOrderController::class, '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'))
|
||||
->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'])
|
||||
->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');
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user