Car-Repairs-Shop/app/Livewire/Invoices/CreateFromEstimate.php
sackey b11cdc39c2
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
feat: Enhance invoice creation and editing with improved part selection and error handling
2025-08-16 15:27:29 +00:00

217 lines
6.9 KiB
PHP

<?php
namespace App\Livewire\Invoices;
use App\Models\Branch;
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;
class CreateFromEstimate extends Component
{
public Estimate $estimate;
#[Validate('required|string|max:255')]
public $subject = '';
#[Validate('nullable|string')]
public $description = '';
#[Validate('required|exists:branches,id')]
public $branch_id = '';
#[Validate('required|date')]
public $invoice_date = '';
#[Validate('required|date|after_or_equal:invoice_date')]
public $due_date = '';
#[Validate('required|numeric|min:0')]
public $tax_rate = 0;
public $lineItems = [];
public $branches = [];
public $serviceItems = [];
public $parts = [];
public function mount(Estimate $estimate)
{
$this->estimate = $estimate;
$this->authorize('create', Invoice::class);
// Pre-fill form with estimate data
$this->subject = $estimate->subject;
$this->description = $estimate->description;
$this->branch_id = $estimate->branch_id;
$this->invoice_date = now()->format('Y-m-d');
$this->due_date = now()->addDays(30)->format('Y-m-d');
$this->tax_rate = $estimate->tax_rate;
// Load estimate line items
$this->lineItems = $estimate->estimateLineItems->map(function ($item) {
return [
'type' => $item->type,
'service_item_id' => $item->service_item_id,
'part_id' => $item->part_id,
'description' => $item->description,
'quantity' => $item->quantity,
'unit_price' => $item->unit_price,
'total' => $item->total,
];
})->toArray();
$this->loadFormData();
}
public function loadFormData()
{
$this->branches = Branch::all();
$this->serviceItems = ServiceItem::all();
$this->parts = Part::all();
}
public function addLineItem()
{
$this->lineItems[] = [
'type' => 'service',
'service_item_id' => '',
'part_id' => '',
'description' => '',
'quantity' => 1,
'unit_price' => 0,
'total' => 0,
];
}
public function removeLineItem($index)
{
unset($this->lineItems[$index]);
$this->lineItems = array_values($this->lineItems);
}
public function updatedLineItems($value, $key)
{
$keyParts = explode('.', $key);
$index = $keyParts[0];
$field = $keyParts[1];
if ($field === 'type') {
// Reset related fields when type changes
$this->lineItems[$index]['service_item_id'] = '';
$this->lineItems[$index]['part_id'] = '';
$this->lineItems[$index]['description'] = '';
$this->lineItems[$index]['unit_price'] = 0;
}
if ($field === 'service_item_id' && $this->lineItems[$index]['type'] === 'service') {
$serviceItem = ServiceItem::find($value);
if ($serviceItem) {
$this->lineItems[$index]['description'] = $serviceItem->name;
$this->lineItems[$index]['unit_price'] = $serviceItem->price;
}
}
if ($field === 'part_id' && $this->lineItems[$index]['type'] === 'part') {
$part = Part::find($value);
if ($part) {
$this->lineItems[$index]['description'] = $part->name;
$this->lineItems[$index]['unit_price'] = $part->sell_price;
}
}
// Recalculate total for this line item
if (in_array($field, ['quantity', 'unit_price'])) {
$quantity = (float) $this->lineItems[$index]['quantity'];
$unitPrice = (float) $this->lineItems[$index]['unit_price'];
$this->lineItems[$index]['total'] = $quantity * $unitPrice;
}
}
public function getSubtotalProperty()
{
return collect($this->lineItems)->sum('total');
}
public function getTaxAmountProperty()
{
return $this->subtotal * ($this->tax_rate / 100);
}
public function getTotalProperty()
{
return $this->subtotal + $this->taxAmount;
}
public function save()
{
$this->validate();
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', $this->invoice);
}
public function render()
{
return view('livewire.invoices.create-from-estimate')
->layout('layouts.app');
}
}