232 lines
7.0 KiB
PHP
232 lines
7.0 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Invoices;
|
|
|
|
use App\Models\Branch;
|
|
use App\Models\Customer;
|
|
use App\Models\Invoice;
|
|
use App\Models\InvoiceLineItem;
|
|
use App\Models\Part;
|
|
use App\Models\ServiceItem;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\Validate;
|
|
use Livewire\Component;
|
|
|
|
class Create extends Component
|
|
{
|
|
#[Validate('required')]
|
|
public $customer_id = '';
|
|
|
|
#[Validate('required')]
|
|
public $branch_id = '';
|
|
|
|
#[Validate('required|date')]
|
|
public $invoice_date = '';
|
|
|
|
#[Validate('required|date|after:invoice_date')]
|
|
public $due_date = '';
|
|
|
|
#[Validate('nullable|string')]
|
|
public $description = '';
|
|
|
|
#[Validate('nullable|string')]
|
|
public $notes = '';
|
|
|
|
#[Validate('nullable|string')]
|
|
public $terms_and_conditions = '';
|
|
|
|
#[Validate('numeric|min:0|max:100')]
|
|
public $tax_rate = 8.50;
|
|
|
|
#[Validate('numeric|min:0')]
|
|
public $discount_amount = 0;
|
|
|
|
public $line_items = [];
|
|
|
|
public $customers = [];
|
|
|
|
public $branches = [];
|
|
|
|
public $parts = [];
|
|
|
|
public $service_items = [];
|
|
|
|
public $filteredParts = [];
|
|
|
|
public $partSearchTerms = [];
|
|
|
|
public function mount()
|
|
{
|
|
$this->invoice_date = now()->format('Y-m-d');
|
|
$this->due_date = now()->addDays(30)->format('Y-m-d');
|
|
$this->branch_id = Auth::user()->branch_id ?? '';
|
|
|
|
$this->customers = Customer::orderBy('first_name')->orderBy('last_name')->get();
|
|
$this->branches = Branch::orderBy('name')->get();
|
|
$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();
|
|
}
|
|
|
|
public function addLineItem()
|
|
{
|
|
$this->line_items[] = [
|
|
'type' => 'labour',
|
|
'description' => '',
|
|
'quantity' => 1,
|
|
'unit_price' => 0,
|
|
'part_id' => '',
|
|
'part_number' => '',
|
|
'technical_notes' => '',
|
|
];
|
|
}
|
|
|
|
public function removeLineItem($index)
|
|
{
|
|
unset($this->line_items[$index]);
|
|
$this->line_items = array_values($this->line_items);
|
|
}
|
|
|
|
public function updatedLineItems($value, $key)
|
|
{
|
|
// Auto-populate part details when part is selected
|
|
if (str_contains($key, 'part_id')) {
|
|
$index = explode('.', $key)[0];
|
|
$partId = $this->line_items[$index]['part_id'];
|
|
|
|
if ($partId) {
|
|
$part = Part::find($partId);
|
|
if ($part) {
|
|
$this->line_items[$index]['description'] = $part->name;
|
|
$this->line_items[$index]['unit_price'] = $part->sell_price ?? 0;
|
|
$this->line_items[$index]['part_number'] = $part->part_number;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset part selection when type changes
|
|
if (str_contains($key, 'type')) {
|
|
$index = explode('.', $key)[0];
|
|
$this->line_items[$index]['part_id'] = '';
|
|
$this->line_items[$index]['part_number'] = '';
|
|
if ($this->line_items[$index]['type'] !== 'parts') {
|
|
$this->line_items[$index]['description'] = '';
|
|
$this->line_items[$index]['unit_price'] = 0;
|
|
}
|
|
}
|
|
|
|
// Auto-populate service item details
|
|
if (str_contains($key, 'service_item_id')) {
|
|
$index = explode('.', $key)[0];
|
|
$serviceItemId = $this->line_items[$index]['service_item_id'] ?? null;
|
|
|
|
if ($serviceItemId) {
|
|
$serviceItem = ServiceItem::find($serviceItemId);
|
|
if ($serviceItem) {
|
|
$this->line_items[$index]['description'] = $serviceItem->service_name;
|
|
$this->line_items[$index]['unit_price'] = $serviceItem->price ?? 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function calculateSubtotal()
|
|
{
|
|
return collect($this->line_items)->sum(function ($item) {
|
|
return ($item['quantity'] ?? 0) * ($item['unit_price'] ?? 0);
|
|
});
|
|
}
|
|
|
|
public function calculateTax()
|
|
{
|
|
$subtotal = $this->calculateSubtotal() - $this->discount_amount;
|
|
|
|
return $subtotal * ($this->tax_rate / 100);
|
|
}
|
|
|
|
public function calculateTotal()
|
|
{
|
|
$subtotal = $this->calculateSubtotal();
|
|
$tax = $this->calculateTax();
|
|
|
|
return $subtotal + $tax - $this->discount_amount;
|
|
}
|
|
|
|
public function save()
|
|
{
|
|
$this->validate();
|
|
|
|
// Validate line items
|
|
if (empty($this->line_items)) {
|
|
$this->addError('line_items', 'At least one line item is required.');
|
|
|
|
return;
|
|
}
|
|
|
|
foreach ($this->line_items as $index => $item) {
|
|
if (empty($item['description'])) {
|
|
$this->addError("line_items.{$index}.description", 'Description is required.');
|
|
|
|
return;
|
|
}
|
|
if ($item['quantity'] <= 0) {
|
|
$this->addError("line_items.{$index}.quantity", 'Quantity must be greater than 0.');
|
|
|
|
return;
|
|
}
|
|
if ($item['unit_price'] < 0) {
|
|
$this->addError("line_items.{$index}.unit_price", 'Unit price cannot be negative.');
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
'notes' => $this->notes,
|
|
'terms_and_conditions' => $this->terms_and_conditions,
|
|
'tax_rate' => $this->tax_rate,
|
|
'discount_amount' => $this->discount_amount,
|
|
'status' => 'draft',
|
|
]);
|
|
|
|
// Create line items
|
|
foreach ($this->line_items as $item) {
|
|
InvoiceLineItem::create([
|
|
'invoice_id' => $invoice->id,
|
|
'type' => $item['type'],
|
|
'description' => $item['description'],
|
|
'quantity' => $item['quantity'],
|
|
'unit_price' => $item['unit_price'],
|
|
'part_id' => $item['part_id'] ?: null,
|
|
'part_number' => $item['part_number'] ?: null,
|
|
'technical_notes' => $item['technical_notes'] ?: null,
|
|
]);
|
|
}
|
|
|
|
session()->flash('success', 'Invoice created successfully.');
|
|
|
|
return $this->redirect(route('invoices.show', $invoice));
|
|
}
|
|
|
|
#[Layout('components.layouts.app.sidebar')]
|
|
public function render()
|
|
{
|
|
return view('livewire.invoices.create');
|
|
}
|
|
}
|