- Increased icon sizes in service items, service orders, users, and technician management for better visibility. - Added custom loading indicators with appropriate icons in search fields for vehicles, work orders, and technicians. - Introduced invoice management routes for better organization and access control. - Created a new test for the estimate PDF functionality to ensure proper rendering and data integrity.
197 lines
5.9 KiB
PHP
197 lines
5.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 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();
|
|
|
|
$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'],
|
|
]);
|
|
}
|
|
|
|
session()->flash('success', 'Invoice created successfully from estimate.');
|
|
|
|
return redirect()->route('invoices.show', $invoice);
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.invoices.create-from-estimate')
|
|
->layout('layouts.app');
|
|
}
|
|
}
|