diff --git a/app/Http/Controllers/Admin/AdminInvoiceController.php b/app/Http/Controllers/Admin/AdminInvoiceController.php index 94ac645..62cb0fe 100644 --- a/app/Http/Controllers/Admin/AdminInvoiceController.php +++ b/app/Http/Controllers/Admin/AdminInvoiceController.php @@ -7,6 +7,11 @@ use Illuminate\Http\Request; use App\Models\Invoice; use App\Models\InvoiceItem; use Mpdf\Mpdf; +use App\Models\InvoiceInstallment; +use Illuminate\Support\Facades\Log; + + + class AdminInvoiceController extends Controller { @@ -15,108 +20,242 @@ class AdminInvoiceController extends Controller // ------------------------------------------------------------- public function index() { - $invoices = Invoice::latest()->get(); + $invoices = Invoice::with(['order.shipments'])->latest()->get(); return view('admin.invoice', compact('invoices')); } // ------------------------------------------------------------- // POPUP VIEW (AJAX) // ------------------------------------------------------------- - public function popup($id) + public function popup($id) { - $invoice = Invoice::with('items')->findOrFail($id); - return view('admin.popup_invoice', compact('invoice')); + $invoice = Invoice::with(['items', 'order'])->findOrFail($id); + + // Find actual Shipment record + $shipment = \App\Models\Shipment::whereHas('items', function ($q) use ($invoice) { + $q->where('order_id', $invoice->order_id); + }) + ->first(); + + return view('admin.popup_invoice', compact('invoice', 'shipment')); } + // ------------------------------------------------------------- // EDIT INVOICE PAGE // ------------------------------------------------------------- public function edit($id) { - $invoice = Invoice::findOrFail($id); - return view('admin.invoice_edit', compact('invoice')); + $invoice = Invoice::with(['order.shipments'])->findOrFail($id); + + $shipment = $invoice->order?->shipments?->first(); + + return view('admin.invoice_edit', compact('invoice', 'shipment')); } // ------------------------------------------------------------- // UPDATE INVOICE // ------------------------------------------------------------- - public function update(Request $request, $id) - { - $invoice = Invoice::findOrFail($id); - // Validate editable fields - $data = $request->validate([ - 'invoice_date' => 'required|date', - 'due_date' => 'required|date|after_or_equal:invoice_date', - 'payment_method' => 'nullable|string', - 'reference_no' => 'nullable|string|max:255', - 'final_amount' => 'required|numeric|min:0', - 'gst_percent' => 'required|numeric|min:0|max:28', - 'status' => 'required|in:pending,paid,overdue', - 'notes' => 'nullable|string', - ]); +public function update(Request $request, $id) +{ + Log::info("🟡 Invoice Update Request Received", [ + 'invoice_id' => $id, + 'request' => $request->all() + ]); - // Auto-calc - $gst_amount = ($data['final_amount'] * $data['gst_percent']) / 100; - $final_amount_with_gst = $data['final_amount'] + $gst_amount; + $invoice = Invoice::findOrFail($id); - $data['gst_amount'] = $gst_amount; - $data['final_amount_with_gst'] = $final_amount_with_gst; + $data = $request->validate([ + 'invoice_date' => 'required|date', + 'due_date' => 'required|date|after_or_equal:invoice_date', + 'final_amount' => 'required|numeric|min:0', + 'tax_type' => 'required|in:gst,igst', + 'tax_percent' => 'required|numeric|min:0|max:28', + 'status' => 'required|in:pending,paid,overdue', + 'notes' => 'nullable|string', + ]); - // Update DB - $invoice->update($data); + Log::info("✅ Validated Invoice Update Data", $data); - // Generate PDF - $this->generateInvoicePDF($invoice); + $finalAmount = floatval($data['final_amount']); + $taxPercent = floatval($data['tax_percent']); + $taxAmount = 0; - return redirect() - ->route('admin.invoices.index') - ->with('success', 'Invoice updated & PDF generated successfully.'); + if ($data['tax_type'] === 'gst') { + Log::info("🟢 GST Selected", compact('taxPercent')); + + $data['cgst_percent'] = $taxPercent / 2; + $data['sgst_percent'] = $taxPercent / 2; + $data['igst_percent'] = 0; + } else { + Log::info("🔵 IGST Selected", compact('taxPercent')); + + $data['cgst_percent'] = 0; + $data['sgst_percent'] = 0; + $data['igst_percent'] = $taxPercent; } + $taxAmount = ($finalAmount * $taxPercent) / 100; + + $data['gst_amount'] = $taxAmount; + $data['final_amount_with_gst'] = $finalAmount + $taxAmount; + + // ✅ store original % for UI reference + $data['gst_percent'] = $taxPercent; + + Log::info("📌 Final Calculated Invoice Values", [ + 'invoice_id' => $invoice->id, + 'final_amount' => $finalAmount, + 'gst_amount' => $data['gst_amount'], + 'final_amount_with_gst' => $data['final_amount_with_gst'], + 'tax_type' => $data['tax_type'], + 'cgst_percent' => $data['cgst_percent'], + 'sgst_percent' => $data['sgst_percent'], + 'igst_percent' => $data['igst_percent'], + ]); + + $invoice->update($data); + + Log::info("✅ Invoice Updated Successfully", [ + 'invoice_id' => $invoice->id + ]); + + // regenerate PDF + $this->generateInvoicePDF($invoice); + + return redirect() + ->route('admin.invoices.index') + ->with('success', 'Invoice updated & PDF generated successfully.'); +} + + // ------------------------------------------------------------- // PDF GENERATION USING mPDF // ------------------------------------------------------------- public function generateInvoicePDF($invoice) { - // PDF Name + // Load relationship including shipment + $invoice->load(['items', 'order.shipments']); + + // Fetch shipment + $shipment = $invoice->order?->shipments?->first(); + + // PDF filename $fileName = 'invoice-' . $invoice->invoice_number . '.pdf'; - // Save directly in /public/invoices + // Directory path $folder = public_path('invoices/'); - // Create folder if not exists if (!file_exists($folder)) { mkdir($folder, 0777, true); } - // Full path $filePath = $folder . $fileName; - // Delete old file + // Delete old file if exists if (file_exists($filePath)) { unlink($filePath); } - // Initialize mPDF + // Create mPDF instance $mpdf = new Mpdf([ 'mode' => 'utf-8', 'format' => 'A4', - 'default_font' => 'sans-serif' + 'default_font' => 'sans-serif', ]); // Load HTML view - $html = view('admin.pdf.invoice', compact('invoice'))->render(); + $html = view('admin.pdf.invoice', [ + 'invoice' => $invoice, + 'shipment' => $shipment + ])->render(); - // Generate + // Write HTML to PDF $mpdf->WriteHTML($html); - // Save to public/invoices + // Save PDF $mpdf->Output($filePath, 'F'); - // Save path in DB + // Update DB path $invoice->update([ 'pdf_path' => 'invoices/' . $fileName ]); } + + + +public function storeInstallment(Request $request, $invoice_id) +{ + $request->validate([ + 'installment_date' => 'required|date', + 'payment_method' => 'required|string', + 'reference_no' => 'nullable|string', + 'amount' => 'required|numeric|min:1', + ]); + + $invoice = Invoice::findOrFail($invoice_id); + + $paidTotal = $invoice->installments()->sum('amount'); + $remaining = $invoice->final_amount - $paidTotal; + + if ($request->amount > $remaining) { + return response()->json([ + 'status' => 'error', + 'message' => 'Installment amount exceeds remaining balance.' + ], 422); + } + + $installment = InvoiceInstallment::create([ + 'invoice_id' => $invoice_id, + 'installment_date' => $request->installment_date, + 'payment_method' => $request->payment_method, + 'reference_no' => $request->reference_no, + 'amount' => $request->amount, + ]); + + // Update invoice status to paid if fully cleared + $newPaid = $paidTotal + $request->amount; + + if ($newPaid >= $invoice->final_amount) { + $invoice->update(['status' => 'paid']); + } + + return response()->json([ + 'status' => 'success', + 'message' => 'Installment added successfully.', + 'installment' => $installment, + 'totalPaid' => $newPaid, + 'remaining' => max(0, $invoice->final_amount - $newPaid), + 'isCompleted' => $newPaid >= $invoice->final_amount + ]); +} + +public function deleteInstallment($id) +{ + $installment = InvoiceInstallment::findOrFail($id); + $invoice = $installment->invoice; + + // delete installment + $installment->delete(); + + // recalc totals + $paidTotal = $invoice->installments()->sum('amount'); + $remaining = $invoice->final_amount - $paidTotal; + + // auto update invoice status + if ($remaining > 0 && $invoice->status === "paid") { + $invoice->update(['status' => 'pending']); + } + + return response()->json([ + 'status' => 'success', + 'message' => 'Installment deleted.', + 'totalPaid' => $paidTotal, + 'remaining' => $remaining, + 'isZero' => $paidTotal == 0 + ]); +} + + } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index e8c5739..b59b456 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -9,34 +9,41 @@ class Invoice extends Model { use HasFactory; - protected $fillable = [ - 'order_id', - 'customer_id', - 'mark_no', + protected $fillable = [ + 'order_id', + 'customer_id', + 'mark_no', - 'invoice_number', - 'invoice_date', - 'due_date', + 'invoice_number', + 'invoice_date', + 'due_date', - 'payment_method', - 'reference_no', - 'status', + 'payment_method', + 'reference_no', + 'status', - 'final_amount', - 'gst_percent', - 'gst_amount', - 'final_amount_with_gst', + 'final_amount', // without tax - 'customer_name', - 'company_name', - 'customer_email', - 'customer_mobile', - 'customer_address', - 'pincode', + 'tax_type', // gst / igst + 'gst_percent', // only used for gst UI input + 'cgst_percent', + 'sgst_percent', + 'igst_percent', + + 'gst_amount', // total tax amount + 'final_amount_with_gst', + + 'customer_name', + 'company_name', + 'customer_email', + 'customer_mobile', + 'customer_address', + 'pincode', + + 'pdf_path', + 'notes', +]; - 'pdf_path', - 'notes', - ]; /**************************** * Relationships @@ -74,4 +81,16 @@ class Invoice extends Model { return $this->status === 'pending' && now()->gt($this->due_date); } + + public function getShipment() + { + return $this->order?->shipments?->first(); + } + + public function installments() +{ + return $this->hasMany(InvoiceInstallment::class); +} + + } diff --git a/app/Models/InvoiceInstallment.php b/app/Models/InvoiceInstallment.php new file mode 100644 index 0000000..a62f63e --- /dev/null +++ b/app/Models/InvoiceInstallment.php @@ -0,0 +1,24 @@ +belongsTo(Invoice::class); + } +} diff --git a/app/Models/Order.php b/app/Models/Order.php index 3113fa7..c1ce73c 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -46,4 +46,15 @@ class Order extends Model ->withTimestamps(); } + public function shipmentItems() + { + return $this->hasMany(\App\Models\ShipmentItem::class, 'order_id', 'id'); + } + + public function shipments() + { + return $this->belongsToMany(\App\Models\Shipment::class, 'shipment_items', 'order_id', 'shipment_id'); + } + + } diff --git a/database/migrations/2025_11_24_052927_update_gst_fields_in_invoices_table.php b/database/migrations/2025_11_24_052927_update_gst_fields_in_invoices_table.php new file mode 100644 index 0000000..0645f02 --- /dev/null +++ b/database/migrations/2025_11_24_052927_update_gst_fields_in_invoices_table.php @@ -0,0 +1,53 @@ +enum('tax_type', ['gst', 'igst']) + ->default('gst') + ->after('final_amount'); + + // Old gst_percent becomes optional + $table->decimal('gst_percent', 5, 2) + ->nullable() + ->change(); + + // Split GST % + $table->decimal('cgst_percent', 5, 2) + ->nullable() + ->after('gst_percent'); + + $table->decimal('sgst_percent', 5, 2) + ->nullable() + ->after('cgst_percent'); + + // IGST % + $table->decimal('igst_percent', 5, 2) + ->nullable() + ->after('sgst_percent'); + + // Tax amount recalculation is the same + // gst_amount and final_amount_with_gst already exist + }); + } + + public function down(): void + { + Schema::table('invoices', function (Blueprint $table) { + $table->dropColumn([ + 'tax_type', + 'cgst_percent', + 'sgst_percent', + 'igst_percent', + ]); + }); + } +}; diff --git a/database/migrations/2025_11_24_131305_create_invoice_installments_table.php b/database/migrations/2025_11_24_131305_create_invoice_installments_table.php new file mode 100644 index 0000000..f9e33cc --- /dev/null +++ b/database/migrations/2025_11_24_131305_create_invoice_installments_table.php @@ -0,0 +1,30 @@ +id(); + + $table->unsignedBigInteger('invoice_id'); + $table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); + + $table->date('installment_date'); + $table->string('payment_method')->nullable(); // cash, bank, UPI, cheque, etc + $table->string('reference_no')->nullable(); + $table->decimal('amount', 10, 2); + + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('invoice_installments'); + } +}; diff --git a/public/invoices/invoice-INV-2025-000008.pdf b/public/invoices/invoice-INV-2025-000008.pdf index 8c6af73..e6c8df1 100644 Binary files a/public/invoices/invoice-INV-2025-000008.pdf and b/public/invoices/invoice-INV-2025-000008.pdf differ diff --git a/public/invoices/invoice-INV-2025-000009.pdf b/public/invoices/invoice-INV-2025-000009.pdf new file mode 100644 index 0000000..362d2f4 Binary files /dev/null and b/public/invoices/invoice-INV-2025-000009.pdf differ diff --git a/public/invoices/invoice-INV-2025-000010.pdf b/public/invoices/invoice-INV-2025-000010.pdf new file mode 100644 index 0000000..435c32b Binary files /dev/null and b/public/invoices/invoice-INV-2025-000010.pdf differ diff --git a/public/invoices/invoice-INV-2025-000011.pdf b/public/invoices/invoice-INV-2025-000011.pdf new file mode 100644 index 0000000..5c37efa Binary files /dev/null and b/public/invoices/invoice-INV-2025-000011.pdf differ diff --git a/public/invoices/invoice-INV-2025-000012.pdf b/public/invoices/invoice-INV-2025-000012.pdf new file mode 100644 index 0000000..188bf50 Binary files /dev/null and b/public/invoices/invoice-INV-2025-000012.pdf differ diff --git a/public/invoices/invoice-INV-2025-000013.pdf b/public/invoices/invoice-INV-2025-000013.pdf new file mode 100644 index 0000000..350715c Binary files /dev/null and b/public/invoices/invoice-INV-2025-000013.pdf differ diff --git a/public/invoices/invoice-INV-2025-000014.pdf b/public/invoices/invoice-INV-2025-000014.pdf new file mode 100644 index 0000000..a2f5b70 Binary files /dev/null and b/public/invoices/invoice-INV-2025-000014.pdf differ diff --git a/public/invoices/invoice-INV-2025-000022.pdf b/public/invoices/invoice-INV-2025-000022.pdf new file mode 100644 index 0000000..663e06a Binary files /dev/null and b/public/invoices/invoice-INV-2025-000022.pdf differ diff --git a/resources/views/admin/invoice_edit.blade.php b/resources/views/admin/invoice_edit.blade.php index 8f11fc9..0d9f9df 100644 --- a/resources/views/admin/invoice_edit.blade.php +++ b/resources/views/admin/invoice_edit.blade.php @@ -3,430 +3,369 @@ @section('page-title', 'Edit Invoice') @section('content') + -