Compare commits
No commits in common. "8b054bcbf00501be7cb21768e709c2f306bb2973" and "f85d4f20bb6af7a92fe932821a95c513fa5b56bf" have entirely different histories.
8b054bcbf0
...
f85d4f20bb
|
|
@ -22,9 +22,9 @@ enum InvoiceStatus: string
|
|||
public function color(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::DRAFT => 'zinc',
|
||||
self::DRAFT => 'gray',
|
||||
self::POSTED => 'green',
|
||||
self::VOID => 'red',
|
||||
self::VOID => 'zinc',
|
||||
self::PAID => 'blue',
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\InvoiceStatus;
|
||||
use App\Models\Invoice;
|
||||
|
||||
class CustomerInvoiceController extends Controller
|
||||
{
|
||||
public function __invoke(Invoice $invoice)
|
||||
{
|
||||
if ($invoice->status === InvoiceStatus::DRAFT) {
|
||||
abort(404);
|
||||
}
|
||||
if ($invoice->status === InvoiceStatus::VOID) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return view('invoices.show', compact('invoice'));
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Invoice extends Model
|
||||
{
|
||||
|
|
@ -17,7 +16,6 @@ class Invoice extends Model
|
|||
{
|
||||
static::creating(function (Invoice $invoice) {
|
||||
$invoice->invoice_number ??= static::generateInvoiceNumber();
|
||||
$invoice->uuid = (string) Str::uuid();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -51,17 +49,6 @@ class Invoice extends Model
|
|||
'sent_at' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the route key for the model.
|
||||
* This tells Laravel to use the 'uuid' column for route model binding.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'uuid';
|
||||
}
|
||||
|
||||
public function client(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Client::class);
|
||||
|
|
@ -72,9 +59,14 @@ class Invoice extends Model
|
|||
return $this->hasMany(InvoiceLine::class);
|
||||
}
|
||||
|
||||
public function formattedTotal(): string
|
||||
{
|
||||
return '$'.number_format($this->total, 2);
|
||||
}
|
||||
|
||||
public function recalculateTotal(): void
|
||||
{
|
||||
$this->attributes['total'] = $this->lines()->sum('amount');
|
||||
$this->total = $this->lines()->sum('amount');
|
||||
$this->saveQuietly();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ class InvoiceLine extends Model
|
|||
{
|
||||
protected $fillable = [
|
||||
'invoice_id',
|
||||
'product_id',
|
||||
'sku',
|
||||
'name',
|
||||
'description',
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('invoices', function (Blueprint $table) {
|
||||
$table->uuid('uuid')->after('id')->unique();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('invoices', function (Blueprint $table) {
|
||||
$table->dropColumn('uuid');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\InvoiceStatus;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
use Livewire\Component;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Attributes\Validate;
|
||||
|
||||
|
||||
new class extends Component {
|
||||
public $invoice;
|
||||
|
||||
#[Validate('required|exists:clients,id')]
|
||||
public ?int $client_id = null;
|
||||
|
||||
#[Validate('nullable|string')]
|
||||
public ?string $notes = null;
|
||||
|
||||
#[Validate('nullable|string')]
|
||||
public ?string $internal_notes = null;
|
||||
|
||||
public function mount($invoice = null): void
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
$this->client_id = $invoice?->client_id;
|
||||
$this->notes = $invoice?->notes;
|
||||
$this->internal_notes = $invoice?->internal_notes;
|
||||
}
|
||||
|
||||
public function updateClient(): void
|
||||
{
|
||||
$this->validate([
|
||||
'client_id' => 'required|exists:clients,id'
|
||||
]);
|
||||
|
||||
$this->invoice->update(['client_id' => $this->client_id]);
|
||||
}
|
||||
|
||||
public function updateNotes(): void
|
||||
{
|
||||
$this->validate([
|
||||
'notes' => 'nullable|string',
|
||||
'internal_notes' => 'nullable|string'
|
||||
]);
|
||||
$this->invoice->update([
|
||||
'notes' => $this->notes,
|
||||
'internal_notes' => $this->internal_notes,
|
||||
]);
|
||||
}
|
||||
|
||||
public function setStatus($newStatus): void
|
||||
{
|
||||
$updatedValue = match ($newStatus) {
|
||||
'posted' => InvoiceStatus::POSTED,
|
||||
'draft' => InvoiceStatus::DRAFT,
|
||||
'void' => InvoiceStatus::VOID,
|
||||
'paid' => InvoiceStatus::PAID,
|
||||
default => $this->invoice->status
|
||||
};
|
||||
$this->invoice->update([
|
||||
'status' => $updatedValue,
|
||||
]);
|
||||
|
||||
if ($newStatus === 'posted') {
|
||||
$this->invoice->update([
|
||||
'invoice_date' => now(),
|
||||
'due_date' => now()->addDays(30),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->dispatch('invoice-status-changed');
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function clients()
|
||||
{
|
||||
return Client::where('status', 'active')->orderBy('abbreviation')->get();
|
||||
}
|
||||
};
|
||||
?>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<flux:heading size="xl" class="mb-3">Manage Invoice</flux:heading>
|
||||
<div>
|
||||
@if($this->invoice->status === InvoiceStatus::DRAFT)
|
||||
<flux:button variant="primary" color="red" wire:click="setStatus('void')">Void Invoice</flux:button>
|
||||
<flux:button variant="primary" color="green" wire:click="setStatus('posted')">Post Invoice</flux:button>
|
||||
@elseif($this->invoice->status === InvoiceStatus::POSTED)
|
||||
<flux:button variant="primary" color="red" wire:click="setStatus('void')">Void Invoice</flux:button>
|
||||
<flux:button variant="primary" color="amber" wire:click="setStatus('draft')">Un-Post Invoice</flux:button>
|
||||
@elseif($this->invoice->status === InvoiceStatus::VOID)
|
||||
<flux:button variant="primary" color="blue" wire:click="setStatus('draft')">Restore Invoice</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<flux:card class="bg-gray-50">
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<div class="flex gap-3">
|
||||
<flux:heading size="md">ID</flux:heading>
|
||||
<flux:text>{{ $invoice->id }}</flux:text>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<flux:heading size="md">Invoice Number</flux:heading>
|
||||
<flux:text>{{ $invoice->invoice_number }}</flux:text>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<flux:heading size="md">UUID</flux:heading>
|
||||
<flux:text>{{ $invoice->uuid }}</flux:text>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@if($invoice->status !== InvoiceStatus::DRAFT)
|
||||
<div class="flex gap-3">
|
||||
<flux:heading size="md">Client</flux:heading>
|
||||
<flux:text>{{ $invoice->client->name }}</flux:text>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<flux:heading size="md">Invoice Date</flux:heading>
|
||||
<flux:text>{{ $invoice->invoice_date?->format('m/d/Y') }}</flux:text>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<flux:heading size="md">Due Date</flux:heading>
|
||||
<flux:text>{{ $invoice->due_date?->format('m/d/Y') }}</flux:text>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<flux:heading size="md">Sent Date</flux:heading>
|
||||
<flux:text>{{ $invoice->sent_at?->format('m/d/Y') }}</flux:text>
|
||||
</div>
|
||||
@else
|
||||
<flux:select wire:model="client_id" wire:change="updateClient" label="Client"
|
||||
placeholder="Choose client..."
|
||||
:disabled="$invoice->status !== InvoiceStatus::DRAFT">
|
||||
@foreach($this->clients as $client)
|
||||
<flux:select.option :value="$client->id">{{ $client->name }}</flux:select.option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex gap-3">
|
||||
<flux:heading size="md">Status</flux:heading>
|
||||
<flux:badge color="{{ $invoice->status->color() }}" size="sm">
|
||||
{{ $invoice->status->label() }}
|
||||
</flux:badge>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<flux:heading size="md">Created</flux:heading>
|
||||
<flux:text>{{ $invoice->created_at->local()->format('m/d/Y | h:m:s') }}</flux:text>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<flux:heading size="md">Last Updated</flux:heading>
|
||||
<flux:text>{{ $invoice->updated_at->local()->format('m/d/Y | h:m:s') }}</flux:text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<form wire:submit="updateNotes" x-data="{ dirty: false }" x-on:submit="dirty = false">
|
||||
<flux:card class="bg-gray-50 mt-8">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
|
||||
<flux:textarea wire:model="notes" label="Notes" placeholder="Add notes..." rows="5"
|
||||
x-on:input="dirty = true"/>
|
||||
<flux:textarea wire:model="internal_notes" label="Internal Notes" placeholder="Add internal notes..."
|
||||
rows="5" x-on:input="dirty = true"/>
|
||||
|
||||
</div>
|
||||
<div class="text-right" x-show="dirty" x-cloak>
|
||||
<flux:button type="submit" variant="primary" class="mt-4">Save Notes</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceLine;
|
||||
use App\Models\Product;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Attributes\Validate;
|
||||
|
||||
new class extends Component {
|
||||
public Invoice $invoice;
|
||||
|
||||
#[Validate('nullable|exists:products,id')]
|
||||
public ?int $product_id = null;
|
||||
|
||||
#[Validate('nullable|string|max:255')]
|
||||
public ?string $sku = null;
|
||||
|
||||
#[Validate('required|string|max:255')]
|
||||
public string $name = '';
|
||||
|
||||
#[Validate('nullable|string|max:255')]
|
||||
public ?string $description = null;
|
||||
|
||||
#[Validate('nullable|integer')]
|
||||
public ?int $school_year = null;
|
||||
|
||||
#[Validate('required|numeric|min:0.01')]
|
||||
public float $quantity = 1;
|
||||
|
||||
#[Validate('required|numeric|min:0')]
|
||||
public float $unit_price = 0;
|
||||
|
||||
public ?int $editingLineId = null;
|
||||
|
||||
public function mount(Invoice $invoice): void
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
};
|
||||
?>
|
||||
|
||||
<div>
|
||||
<flux:card class="bg-gray-50 mt-8">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<flux:heading size="lg">Invoice Lines</flux:heading>
|
||||
<flux:text class="font-semibold">Total: {{ formatMoney($invoice->total) }}</flux:text>
|
||||
</div>
|
||||
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>Product</flux:table.column>
|
||||
<flux:table.column>SKU</flux:table.column>
|
||||
<flux:table.column>Name</flux:table.column>
|
||||
<flux:table.column>Description</flux:table.column>
|
||||
<flux:table.column>School Year</flux:table.column>
|
||||
<flux:table.column class="text-right">Qty</flux:table.column>
|
||||
<flux:table.column class="text-right">Unit Price</flux:table.column>
|
||||
<flux:table.column class="text-right">Amount</flux:table.column>
|
||||
@unless($invoice->isLocked())
|
||||
<flux:table.column class="w-24"></flux:table.column>
|
||||
@endunless
|
||||
</flux:table.columns>
|
||||
<flux:table.rows>
|
||||
@forelse($this->lines as $line)
|
||||
<flux:table.row>
|
||||
<flux:table.cell>{{ $line->product?->name }}</flux:table.cell>
|
||||
<flux:table.cell>{{ $line->sku }}</flux:table.cell>
|
||||
<flux:table.cell>{{ $line->name }}</flux:table.cell>
|
||||
<flux:table.cell class="text-gray-500">{{ $line->description }}</flux:table.cell>
|
||||
<flux:table.cell>{{ $line->school_year_formatted }}</flux:table.cell>
|
||||
<flux:table.cell class="text-right">{{ $line->quantity }}</flux:table.cell>
|
||||
<flux:table.cell class="text-right">${{ number_format($line->unit_price, 2) }}</flux:table.cell>
|
||||
<flux:table.cell class="text-right">${{ number_format($line->amount, 2) }}</flux:table.cell>
|
||||
@unless($invoice->isLocked())
|
||||
<flux:table.cell>
|
||||
<div class="flex gap-2 justify-end">
|
||||
<flux:button size="xs" variant="ghost" wire:click="editLine({{ $line->id }})">Edit</flux:button>
|
||||
<flux:button size="xs" variant="ghost" color="red" wire:click="deleteLine({{ $line->id }})" wire:confirm="Delete this line?">Delete</flux:button>
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
@endunless
|
||||
</flux:table.row>
|
||||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="{{ $invoice->isLocked() ? 8 : 9 }}" class="text-center text-gray-500">
|
||||
No lines yet.
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforelse
|
||||
</flux:table.rows>
|
||||
</flux:table>
|
||||
|
||||
@unless($invoice->isLocked())
|
||||
<form wire:submit="{{ $editingLineId ? 'updateLine' : 'addLine' }}" class="mt-6 border-t pt-6">
|
||||
<flux:heading size="md" class="mb-4">{{ $editingLineId ? 'Edit Line' : 'Add Line' }}</flux:heading>
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<flux:select wire:model.live="product_id" label="Product" placeholder="Select product...">
|
||||
<flux:select.option :value="null">-- None --</flux:select.option>
|
||||
@foreach($this->products as $product)
|
||||
<flux:select.option :value="$product->id">{{ $product->name }}</flux:select.option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
<flux:input wire:model="sku" label="SKU" placeholder="SKU..." />
|
||||
<flux:input wire:model="name" label="Name" placeholder="Line item name..." />
|
||||
<flux:input wire:model="description" label="Description" placeholder="Optional description..." />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-4 mt-4">
|
||||
<flux:input wire:model="school_year" label="School Year" type="number" placeholder="e.g. 2025" />
|
||||
<flux:input wire:model="quantity" label="Quantity" type="number" step="0.01" min="0.01" />
|
||||
<flux:input wire:model="unit_price" label="Unit Price" type="number" step="0.01" min="0" />
|
||||
</div>
|
||||
<div class="mt-4 flex gap-2">
|
||||
<flux:button type="submit" variant="primary">
|
||||
{{ $editingLineId ? 'Update Line' : 'Add Line' }}
|
||||
</flux:button>
|
||||
@if($editingLineId)
|
||||
<flux:button type="button" variant="ghost" wire:click="cancelEdit">Cancel</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
@endunless
|
||||
</flux:card>
|
||||
</div>
|
||||
|
|
@ -72,7 +72,6 @@ new class extends Component {
|
|||
wire:click="sort('total')">
|
||||
Total
|
||||
</flux:table.column>
|
||||
<flux:table.column></flux:table.column>
|
||||
</flux:table.columns>
|
||||
|
||||
<flux:table.rows>
|
||||
|
|
@ -104,24 +103,6 @@ new class extends Component {
|
|||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>{{ formatMoney($invoice->total) }}</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:dropdown position="bottom" align="start">
|
||||
<flux:button variant="ghost" size="sm" icon="ellipsis-horizontal" inset="top bottom"></flux:button>
|
||||
|
||||
<flux:navmenu>
|
||||
<flux:menu.group heading="{{ $invoice->invoice_number }}">
|
||||
<flux:menu.separator></flux:menu.separator>
|
||||
<flux:navmenu.item
|
||||
href="{{ route('invoices.edit', $invoice) }}"
|
||||
icon="pencil">Edit Invoice</flux:navmenu.item>
|
||||
<flux:navmenu.item
|
||||
href="{{ route('invoices.show', $invoice) }}" target="_blank"
|
||||
icon="document-currency-dollar">Customer Invoice</flux:navmenu.item>
|
||||
</flux:menu.group>
|
||||
</flux:navmenu>
|
||||
</flux:dropdown>
|
||||
|
||||
</flux:table.cell>
|
||||
|
||||
</flux:table.row>
|
||||
@endforeach
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
<x-layouts::app :title="__('Contacts')">
|
||||
<div class="max-w-7xl mx-auto space-y-4">
|
||||
<livewire:edit-invoice :invoice="$invoice" />
|
||||
|
||||
<livewire:invoice-lines :invoice="$invoice" />
|
||||
</div>
|
||||
</x-layouts::app>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<x-layouts::app :title="__('Invoices')">
|
||||
<x-layouts::app :title="__('Contacts')">
|
||||
<div class="max-w-7xl mx-auto space-y-4">
|
||||
<div class="flex justify-end">
|
||||
<livewire:create-invoice />
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Invoice {{ $invoice->invoice_number }}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-white p-8 max-w-4xl mx-auto">
|
||||
<div class="flex justify-between items-start mb-12">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-800">INVOICE</h1>
|
||||
<p class="text-gray-600 mt-1">{{ $invoice->invoice_number }}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="font-semibold text-gray-800">eBandroom</p>
|
||||
<p class="text-gray-600">540 W. Louse Ave.</p>
|
||||
<p class="text-gray-600">Vinita, OK 74301</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-8 mb-12">
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold text-gray-500 uppercase mb-2">Bill To</h2>
|
||||
<p class="text-gray-800 font-medium">{{ $invoice->client->name }}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="mb-2">
|
||||
<span class="text-sm text-gray-500">Invoice Date:</span>
|
||||
<span class="text-gray-800 ml-2">{{ $invoice->invoice_date?->format('F j, Y') }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-gray-500">Due Date:</span>
|
||||
<span class="text-gray-800 ml-2">{{ $invoice->due_date?->format('F j, Y') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="w-full mb-8">
|
||||
<thead>
|
||||
<tr class="border-b-2 border-gray-300">
|
||||
<th class="text-left py-3 text-sm font-semibold text-gray-600">SKU</th>
|
||||
<th class="text-left py-3 text-sm font-semibold text-gray-600">Description</th>
|
||||
<th class="text-left py-3 text-sm font-semibold text-gray-600">School Year</th>
|
||||
<th class="text-right py-3 text-sm font-semibold text-gray-600">Qty</th>
|
||||
<th class="text-right py-3 text-sm font-semibold text-gray-600">Unit Price</th>
|
||||
<th class="text-right py-3 text-sm font-semibold text-gray-600">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($invoice->lines as $line)
|
||||
<tr class="border-b border-gray-200">
|
||||
<td class="py-3 text-gray-600">{{ $line->sku }}</td>
|
||||
<td class="py-3 text-gray-800">{{ $line->name }}</td>
|
||||
<td class="py-3 text-gray-600">{{ $line->school_year_formatted }}</td>
|
||||
<td class="py-3 text-right text-gray-600">{{ $line->quantity }}</td>
|
||||
<td class="py-3 text-right text-gray-600">{{ formatMoney($line->unit_price) }}</td>
|
||||
<td class="py-3 text-right text-gray-800">{{ formatMoney($line->amount) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr class="border-t-2 border-gray-300">
|
||||
<td colspan="5" class="py-4 text-right font-semibold text-gray-800">Total</td>
|
||||
<td class="py-4 text-right font-bold text-gray-800 text-lg">{{ formatMoney($invoice->total) }}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<div class="mb-12 p-4 bg-gray-50 rounded">
|
||||
<h2 class="text-sm font-semibold text-gray-500 uppercase mb-2">Payment</h2>
|
||||
<p class="text-gray-700">Please make payment to:</p>
|
||||
<p class="text-gray-800 font-medium mt-1">eBandroom</p>
|
||||
<p class="text-gray-600">540 W. Louse Ave.</p>
|
||||
<p class="text-gray-600">Vinita, OK 74301</p>
|
||||
</div>
|
||||
|
||||
@if($invoice->notes)
|
||||
<div class="border-t pt-6">
|
||||
<h2 class="text-sm font-semibold text-gray-500 uppercase mb-2">Notes</h2>
|
||||
<p class="text-gray-700 whitespace-pre-line">{{ $invoice->notes }}</p>
|
||||
</div>
|
||||
@endif
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\CustomerInvoiceController;
|
||||
use App\Models\Invoice;
|
||||
use App\Http\Controllers\ClientController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/', function () {
|
||||
|
|
@ -14,9 +13,6 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||
Route::view('contacts', 'contacts.index')->name('contacts');
|
||||
Route::view('products', 'products.index')->name('products');
|
||||
Route::view('invoices', 'invoices.index')->name('invoices');
|
||||
Route::get('invoices/{invoice}/edit',
|
||||
fn (Invoice $invoice) => view('invoices.edit', compact('invoice')))->name('invoices.edit');
|
||||
Route::get('invoices/{invoice}', CustomerInvoiceController::class)->name('invoices.show');
|
||||
});
|
||||
|
||||
// Route::view('dashboard', 'dashboard')
|
||||
|
|
|
|||
Loading…
Reference in New Issue