From a25430ae50e94b11f5150892e6036fa218e09081 Mon Sep 17 00:00:00 2001 From: Matt Young Date: Wed, 28 Jan 2026 15:36:08 -0600 Subject: [PATCH] Invoice management complete. --- app/Models/Invoice.php | 7 +- app/Models/InvoiceLine.php | 1 + .../views/components/⚡edit-invoice.blade.php | 2 + .../components/⚡invoice-lines.blade.php | 262 ++++++++++++++++++ resources/views/invoices/edit.blade.php | 2 + 5 files changed, 268 insertions(+), 6 deletions(-) create mode 100644 resources/views/components/⚡invoice-lines.blade.php diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 6b280ef..97e7666 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -72,14 +72,9 @@ class Invoice extends Model return $this->hasMany(InvoiceLine::class); } - public function formattedTotal(): string - { - return '$'.number_format($this->total, 2); - } - public function recalculateTotal(): void { - $this->total = $this->lines()->sum('amount'); + $this->attributes['total'] = $this->lines()->sum('amount'); $this->saveQuietly(); } diff --git a/app/Models/InvoiceLine.php b/app/Models/InvoiceLine.php index caf7a23..797c389 100644 --- a/app/Models/InvoiceLine.php +++ b/app/Models/InvoiceLine.php @@ -12,6 +12,7 @@ class InvoiceLine extends Model { protected $fillable = [ 'invoice_id', + 'product_id', 'sku', 'name', 'description', diff --git a/resources/views/components/⚡edit-invoice.blade.php b/resources/views/components/⚡edit-invoice.blade.php index dd01271..d288565 100644 --- a/resources/views/components/⚡edit-invoice.blade.php +++ b/resources/views/components/⚡edit-invoice.blade.php @@ -68,6 +68,8 @@ new class extends Component { 'due_date' => now()->addDays(30), ]); } + + $this->dispatch('invoice-status-changed'); } #[Computed] diff --git a/resources/views/components/⚡invoice-lines.blade.php b/resources/views/components/⚡invoice-lines.blade.php new file mode 100644 index 0000000..ebaf9df --- /dev/null +++ b/resources/views/components/⚡invoice-lines.blade.php @@ -0,0 +1,262 @@ +invoice = $invoice; + $this->school_year = $this->defaultSchoolYear(); + } + + private function defaultSchoolYear(): int + { + $now = now(); + return $now->month <= 6 ? $now->year : $now->year + 1; + } + + #[On('invoice-status-changed')] + public function refreshInvoice(): void + { + $this->invoice->refresh(); + } + + #[Computed] + public function lines() + { + return $this->invoice->lines()->with('product')->orderBy('id')->get(); + } + + #[Computed] + public function products() + { + return Product::where('active', true)->orderBy('name')->get(); + } + + public function updatedProductId($value): void + { + if (!$value) { + return; + } + + $product = Product::find($value); + if ($product) { + $this->sku = $product->sku; + $this->name = $product->name; + $this->description = $product->description; + $this->unit_price = $product->price; + } + } + + public function addLine(): void + { + if ($this->invoice->isLocked()) { + return; + } + + $this->validate(); + + $this->invoice->lines()->create([ + 'product_id' => $this->product_id, + 'sku' => $this->sku, + 'name' => $this->name, + 'description' => $this->description, + 'school_year' => $this->school_year, + 'quantity' => $this->quantity, + 'unit_price' => $this->unit_price, + ]); + + $this->invoice->refresh(); + $this->resetForm(); + $this->dispatch('lines-updated'); + } + + public function editLine(int $lineId): void + { + if ($this->invoice->isLocked()) { + return; + } + + $line = $this->invoice->lines()->find($lineId); + if (!$line) return; + + $this->editingLineId = $lineId; + $this->product_id = $line->product_id; + $this->sku = $line->sku; + $this->name = $line->name; + $this->description = $line->description; + $this->school_year = $line->school_year; + $this->quantity = $line->quantity; + $this->unit_price = $line->unit_price; + } + + public function updateLine(): void + { + if ($this->invoice->isLocked() || !$this->editingLineId) { + return; + } + + $this->validate(); + + $line = $this->invoice->lines()->find($this->editingLineId); + if (!$line) return; + + $line->update([ + 'product_id' => $this->product_id, + 'sku' => $this->sku, + 'name' => $this->name, + 'description' => $this->description, + 'school_year' => $this->school_year, + 'quantity' => $this->quantity, + 'unit_price' => $this->unit_price, + ]); + + $this->invoice->refresh(); + $this->cancelEdit(); + $this->dispatch('lines-updated'); + } + + public function cancelEdit(): void + { + $this->editingLineId = null; + $this->resetForm(); + } + + public function deleteLine(int $lineId): void + { + if ($this->invoice->isLocked()) { + return; + } + + $line = $this->invoice->lines()->find($lineId); + $line?->delete(); + $this->invoice->refresh(); + $this->dispatch('lines-updated'); + } + + private function resetForm(): void + { + $this->product_id = null; + $this->sku = null; + $this->name = ''; + $this->description = null; + $this->school_year = $this->defaultSchoolYear(); + $this->quantity = 1; + $this->unit_price = 0; + } +}; +?> + +
+ +
+ Invoice Lines + Total: {{ formatMoney($invoice->total) }} +
+ + + + Product + SKU + Name + Description + School Year + Qty + Unit Price + Amount + @unless($invoice->isLocked()) + + @endunless + + + @forelse($this->lines as $line) + + {{ $line->product?->name }} + {{ $line->sku }} + {{ $line->name }} + {{ $line->description }} + {{ $line->school_year_formatted }} + {{ $line->quantity }} + ${{ number_format($line->unit_price, 2) }} + ${{ number_format($line->amount, 2) }} + @unless($invoice->isLocked()) + +
+ Edit + Delete +
+
+ @endunless +
+ @empty + + + No lines yet. + + + @endforelse +
+
+ + @unless($invoice->isLocked()) +
+ {{ $editingLineId ? 'Edit Line' : 'Add Line' }} +
+ + -- None -- + @foreach($this->products as $product) + {{ $product->name }} + @endforeach + + + + +
+
+ + + +
+
+ + {{ $editingLineId ? 'Update Line' : 'Add Line' }} + + @if($editingLineId) + Cancel + @endif +
+
+ @endunless +
+
diff --git a/resources/views/invoices/edit.blade.php b/resources/views/invoices/edit.blade.php index 5f27226..bc9c413 100644 --- a/resources/views/invoices/edit.blade.php +++ b/resources/views/invoices/edit.blade.php @@ -1,5 +1,7 @@
+ +