latest()->paginate(15); return view('invoices.index', compact('invoices')); } public function create(): View { $clients = Client::where('status', 'active')->orderBy('name')->get(); $statuses = InvoiceStatus::cases(); $invoiceSettings = InvoiceSettings::instance(); return view('invoices.create', compact('clients', 'statuses', 'invoiceSettings')); } public function store(Request $request): RedirectResponse { $validated = $request->validate([ 'client_id' => 'nullable|exists:clients,id', 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'tax_rate' => 'required|numeric|min:0|max:100', 'currency' => 'required|string|max:10', 'issue_date' => 'required|date', 'due_date' => 'required|date|after_or_equal:issue_date', 'notes' => 'nullable|string', 'signature_image' => 'nullable|image|max:2048', 'signer_name' => 'nullable|string|max:255', 'signer_title' => 'nullable|string|max:255', 'bank_name' => 'nullable|string|max:100', 'bank_account' => 'nullable|string|max:50', 'bank_holder' => 'nullable|string|max:100', 'branch_name' => 'nullable|string|max:100', 'items' => 'required|array|min:1', 'items.*.description' => 'required|string', 'items.*.quantity' => 'required|integer|min:1', 'items.*.unit_price' => 'required|numeric|min:0', 'items.*.is_taxable' => 'nullable|boolean', ]); $items = $validated['items']; unset($validated['items']); $validated['status'] = InvoiceStatus::Draft->value; if ($request->hasFile('signature_image')) { $validated['signature_image'] = $request->file('signature_image')->store('invoice-signatures', 'public'); } else { // Fallback to settings signature if none provided at creation $settings = InvoiceSettings::instance(); if ($settings->signature_image) { // We reference the same image path, or copy it. // Assuming referencing the same public image is fine. $validated['signature_image'] = $settings->signature_image; } } $invoice = $this->invoiceService->create($validated, $items); return redirect()->route('invoices.show', $invoice) ->with('success', '🧾 Invoice created!'); } public function show(Invoice $invoice): View { $invoice->load('client', 'items'); $invoiceSettings = InvoiceSettings::instance(); return view('invoices.show', compact('invoice', 'invoiceSettings')); } public function edit(Invoice $invoice): View { $clients = Client::where('status', 'active')->orderBy('name')->get(); $statuses = InvoiceStatus::cases(); $invoiceSettings = InvoiceSettings::instance(); $invoice->load('client', 'items'); return view('invoices.edit', compact('invoice', 'clients', 'statuses', 'invoiceSettings')); } public function update(Request $request, Invoice $invoice): RedirectResponse { $validated = $request->validate([ 'client_id' => 'nullable|exists:clients,id', 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'tax_rate' => 'required|numeric|min:0|max:100', 'status' => 'required|in:draft,sent,paid,overdue,cancelled', 'issue_date' => 'required|date', 'due_date' => 'required|date', 'paid_date' => 'nullable|date', 'notes' => 'nullable|string', 'signature_image' => 'nullable|image|max:2048', 'signer_name' => 'nullable|string|max:255', 'signer_title' => 'nullable|string|max:255', 'bank_name' => 'nullable|string|max:100', 'bank_account' => 'nullable|string|max:50', 'bank_holder' => 'nullable|string|max:100', 'branch_name' => 'nullable|string|max:100', 'items' => 'required|array|min:1', 'items.*.description' => 'required|string', 'items.*.quantity' => 'required|integer|min:1', 'items.*.unit_price' => 'required|numeric|min:0', 'items.*.is_taxable' => 'nullable|boolean', ]); $items = $validated['items']; unset($validated['items']); if ($request->hasFile('signature_image')) { if ($invoice->signature_image && \Illuminate\Support\Facades\Storage::disk('public')->exists($invoice->signature_image)) { \Illuminate\Support\Facades\Storage::disk('public')->delete($invoice->signature_image); } $validated['signature_image'] = $request->file('signature_image')->store('invoice-signatures', 'public'); } elseif ($request->boolean('remove_signature')) { if ($invoice->signature_image && \Illuminate\Support\Facades\Storage::disk('public')->exists($invoice->signature_image)) { \Illuminate\Support\Facades\Storage::disk('public')->delete($invoice->signature_image); } $validated['signature_image'] = null; } $this->invoiceService->update($invoice, $validated, $items); return redirect()->route('invoices.show', $invoice) ->with('success', '🧾 Invoice updated!'); } public function destroy(Invoice $invoice): RedirectResponse { $invoice->delete(); return redirect()->route('invoices.index')->with('success', 'Invoice deleted.'); } public function pdf(Invoice $invoice): Response { return $this->invoiceService->generatePdf($invoice); } }