From b11cdc39c24846e0b8a6a5590cd7e59e7a6c0665 Mon Sep 17 00:00:00 2001 From: sackey Date: Sat, 16 Aug 2025 15:27:29 +0000 Subject: [PATCH] feat: Enhance invoice creation and editing with improved part selection and error handling --- app/Livewire/Invoices/Create.php | 9 + app/Livewire/Invoices/CreateFromEstimate.php | 80 ++-- .../views/livewire/estimates/show.blade.php | 2 +- .../invoices/create-from-estimate.blade.php | 354 ++++++++++-------- .../views/livewire/invoices/create.blade.php | 2 +- .../views/livewire/invoices/edit.blade.php | 2 +- 6 files changed, 257 insertions(+), 192 deletions(-) diff --git a/app/Livewire/Invoices/Create.php b/app/Livewire/Invoices/Create.php index d4dff5a..3be042d 100644 --- a/app/Livewire/Invoices/Create.php +++ b/app/Livewire/Invoices/Create.php @@ -52,6 +52,10 @@ class Create extends Component public $service_items = []; + public $filteredParts = []; + + public $partSearchTerms = []; + public function mount() { $this->invoice_date = now()->format('Y-m-d'); @@ -63,6 +67,9 @@ class Create extends Component $this->parts = Part::where('status', 'active')->orderBy('name')->get(); $this->service_items = ServiceItem::where('status', 'active')->orderBy('service_name')->get(); + // Initialize filtered parts and search terms for each line item + $this->filteredParts = $this->parts->toArray(); + // Initialize with one empty line item $this->addLineItem(); } @@ -181,10 +188,12 @@ class Create extends Component } // Create invoice + $branch = Branch::find($this->branch_id); $invoice = Invoice::create([ 'customer_id' => $this->customer_id, 'branch_id' => $this->branch_id, 'created_by' => Auth::id(), + 'invoice_number' => Invoice::generateInvoiceNumber($branch->code ?? 'MAIN'), 'invoice_date' => $this->invoice_date, 'due_date' => $this->due_date, 'description' => $this->description, diff --git a/app/Livewire/Invoices/CreateFromEstimate.php b/app/Livewire/Invoices/CreateFromEstimate.php index 702e9d4..a89a46f 100644 --- a/app/Livewire/Invoices/CreateFromEstimate.php +++ b/app/Livewire/Invoices/CreateFromEstimate.php @@ -7,6 +7,7 @@ use App\Models\Estimate; use App\Models\Invoice; use App\Models\Part; use App\Models\ServiceItem; +use Illuminate\Support\Facades\DB; use Livewire\Attributes\Validate; use Livewire\Component; @@ -152,40 +153,59 @@ class CreateFromEstimate extends Component { $this->validate(); - $invoice = Invoice::create([ - 'job_card_id' => $this->estimate->job_card_id, - 'estimate_id' => $this->estimate->id, - 'customer_id' => $this->estimate->customer_id, - 'branch_id' => $this->branch_id, - 'created_by' => auth()->id(), - 'invoice_number' => Invoice::generateInvoiceNumber($this->branch_id), - 'subject' => $this->subject, - 'description' => $this->description, - 'invoice_date' => $this->invoice_date, - 'due_date' => $this->due_date, - 'subtotal' => $this->subtotal, - 'tax_rate' => $this->tax_rate, - 'tax_amount' => $this->taxAmount, - 'total_amount' => $this->total, - 'status' => 'draft', - ]); - - // Create line items - foreach ($this->lineItems as $item) { - $invoice->invoiceLineItems()->create([ - 'type' => $item['type'], - 'service_item_id' => $item['service_item_id'] ?: null, - 'part_id' => $item['part_id'] ?: null, - 'description' => $item['description'], - 'quantity' => $item['quantity'], - 'unit_price' => $item['unit_price'], - 'total' => $item['total'], + DB::transaction(function () { + $branch = Branch::find($this->branch_id); + $invoice = Invoice::create([ + 'job_card_id' => $this->estimate->job_card_id, + 'estimate_id' => $this->estimate->id, + 'customer_id' => $this->estimate->customer_id, + 'branch_id' => $this->branch_id, + 'created_by' => auth()->id(), + 'invoice_number' => Invoice::generateInvoiceNumber($branch->code ?? 'MAIN'), + 'subject' => $this->subject, + 'description' => $this->description, + 'invoice_date' => $this->invoice_date, + 'due_date' => $this->due_date, + 'subtotal' => $this->estimate->subtotal_amount, + 'tax_rate' => 10.0, // Default tax rate + 'tax_amount' => $this->estimate->tax_amount, + 'total_amount' => $this->estimate->total_amount, + 'status' => 'draft', ]); - } + + // Copy line items from estimate to invoice + foreach ($this->estimate->lineItems as $estimateItem) { + // Map estimate line item types to invoice line item types + $invoiceType = match ($estimateItem->type) { + 'parts' => 'part', + 'labour' => 'labour', + 'miscellaneous' => 'other', + default => $estimateItem->type + }; + + $lineItemData = [ + 'type' => $invoiceType, + 'description' => $estimateItem->description, + 'quantity' => $estimateItem->quantity, + 'unit_price' => $estimateItem->unit_price, + 'total_amount' => $estimateItem->total_amount, + ]; + + // Add part reference if it's a parts item + if ($estimateItem->type === 'parts' && $estimateItem->part_id) { + $lineItemData['part_id'] = $estimateItem->part_id; + $lineItemData['part_number'] = $estimateItem->part->part_number ?? null; + } + + $invoice->invoiceLineItems()->create($lineItemData); + } + + $this->invoice = $invoice; + }); session()->flash('success', 'Invoice created successfully from estimate.'); - return redirect()->route('invoices.show', $invoice); + return redirect()->route('invoices.show', $this->invoice); } public function render() diff --git a/resources/views/livewire/estimates/show.blade.php b/resources/views/livewire/estimates/show.blade.php index e1510c7..c77b87b 100644 --- a/resources/views/livewire/estimates/show.blade.php +++ b/resources/views/livewire/estimates/show.blade.php @@ -82,7 +82,7 @@ Create Work Order @can('create', \App\Models\Invoice::class) - + Convert to Invoice @endcan diff --git a/resources/views/livewire/invoices/create-from-estimate.blade.php b/resources/views/livewire/invoices/create-from-estimate.blade.php index bca886b..964fad9 100644 --- a/resources/views/livewire/invoices/create-from-estimate.blade.php +++ b/resources/views/livewire/invoices/create-from-estimate.blade.php @@ -1,171 +1,207 @@ -
-
-
-
-

Create Invoice from Estimate

-

Converting estimate #{{ $estimate->estimate_number }} to invoice

-
-
- - Back to Estimate - -
-
-
+
+ Create Invoice from Estimate - -
-
- -
-

Source Estimate

-

{{ $estimate->subject }} - {{ $estimate->estimate_number }}

-
-
-
+
+
+
+
+
+
+

Create Invoice from Estimate #{{ $estimate->estimate_number }}

+

Converting estimate to invoice

+
+ + ← Back to Invoices + +
-
+ @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + + -
-

Invoice Details

- -
-
- - Subject - - - -
+ +
+

Customer Information

+
+

Name: {{ $estimate->customer->first_name }} {{ $estimate->customer->last_name }}

+

Email: {{ $estimate->customer->email }}

+

Phone: {{ $estimate->customer->phone }}

+
+
-
- - Branch - - Select branch - @foreach($branches as $branch) - {{ $branch->name }} - @endforeach - - - -
+ +
+

Original Estimate

+
+

Estimate #: {{ $estimate->estimate_number }}

+

Date: {{ $estimate->estimate_date->format('Y-m-d') }}

+

Total: ${{ number_format($estimate->total_amount, 2) }}

+

Status: {{ ucfirst($estimate->status) }}

+
+
+
-
- - Invoice Date - - - -
+ +
+

Invoice Details

+ +
+
+ + + @error('subject') + {{ $message }} + @enderror +
-
- - Due Date - - - -
+
+ + + @error('branch_id') + {{ $message }} + @enderror +
-
- - Description - - - -
+
+ + + @error('invoice_date') + {{ $message }} + @enderror +
+ +
+ + + @error('due_date') + {{ $message }} + @enderror +
+ +
+ + + @error('description') + {{ $message }} + @enderror +
+
+
+ + +
+

Line Items to be Converted

+ +
+ + + + + + + + + + + + + @foreach($estimate->lineItems as $item) + + + + + + + + + @endforeach + + + + + + + +
TypeItemDescriptionQtyUnit PriceTotal
+ {{ ucfirst($item->type) }} + + @if($item->type === 'parts' && $item->part) + {{ $item->part->name }} ({{ $item->part->part_number }}) + @else + {{ $item->description }} + @endif + + {{ $item->description }} + + {{ $item->quantity }} + + ${{ number_format($item->unit_price, 2) }} + + ${{ number_format($item->total_amount, 2) }} +
+ Total Amount: + + ${{ number_format($estimate->total_amount, 2) }} +
+
+
+ +
+ + Cancel + + +
+
- - -
-
-

Line Items

- - Add Item - -
- -
- @foreach($lineItems as $index => $item) -
-
-

Item {{ $index + 1 }}

- @if(count($lineItems) > 1) - - Remove - - @endif -
- -
-
- - Type - - Service - Part - Other - - -
- - @if($item['type'] === 'service') -
- - Service Item - - Select service - @foreach($serviceItems as $serviceItem) - {{ $serviceItem->name }} - ${{ number_format($serviceItem->price, 2) }} - @endforeach - - -
- @elseif($item['type'] === 'part') -
- - Part - - -
- @else -
- @endif - -
- - Description - - -
- -
- - Quantity - - -
- -
- - Unit Price - - -
- -
- - Total - - -
-
+
+
@endforeach
diff --git a/resources/views/livewire/invoices/create.blade.php b/resources/views/livewire/invoices/create.blade.php index 0f0426b..deb0b19 100644 --- a/resources/views/livewire/invoices/create.blade.php +++ b/resources/views/livewire/invoices/create.blade.php @@ -78,7 +78,7 @@ @if($item['type'] === 'parts')
Part - @if(count($parts) === 0) diff --git a/resources/views/livewire/invoices/edit.blade.php b/resources/views/livewire/invoices/edit.blade.php index 9b9961e..a0f3c1b 100644 --- a/resources/views/livewire/invoices/edit.blade.php +++ b/resources/views/livewire/invoices/edit.blade.php @@ -84,7 +84,7 @@ @if($item['type'] === 'parts')
Part - @if(count($parts) === 0)