Show total payments and balance
This commit is contained in:
parent
51ef80b0ba
commit
28288ee722
|
|
@ -14,11 +14,12 @@ use Illuminate\Support\Str;
|
||||||
class Invoice extends Model
|
class Invoice extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
public static function booted(): void
|
public static function booted(): void
|
||||||
{
|
{
|
||||||
static::creating(function (Invoice $invoice) {
|
static::creating(function (Invoice $invoice) {
|
||||||
$invoice->invoice_number ??= static::generateInvoiceNumber();
|
$invoice->invoice_number ??= static::generateInvoiceNumber();
|
||||||
$invoice->uuid = (string) Str::uuid();
|
$invoice->uuid = (string) Str::uuid();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,11 +46,13 @@ class Invoice extends Model
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'total' => MoneyCast::class,
|
'total' => MoneyCast::class,
|
||||||
'status' => InvoiceStatus::class,
|
'total_payments' => MoneyCast::class,
|
||||||
'invoice_date' => 'date',
|
'balance_due' => MoneyCast::class,
|
||||||
'due_date' => 'date',
|
'status' => InvoiceStatus::class,
|
||||||
'sent_at' => 'date',
|
'invoice_date' => 'date',
|
||||||
|
'due_date' => 'date',
|
||||||
|
'sent_at' => 'date',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -84,6 +87,22 @@ class Invoice extends Model
|
||||||
$this->saveQuietly();
|
$this->saveQuietly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function recalculateTotalPayments(): void
|
||||||
|
{
|
||||||
|
$this->attributes['total_payments'] = $this->payments()->sum('amount');
|
||||||
|
$this->saveQuietly();
|
||||||
|
|
||||||
|
$this->refresh();
|
||||||
|
|
||||||
|
if ($this->status === InvoiceStatus::POSTED && $this->balance_due == 0) {
|
||||||
|
$this->status = InvoiceStatus::PAID;
|
||||||
|
$this->saveQuietly();
|
||||||
|
} elseif ($this->status === InvoiceStatus::PAID && $this->balance_due != 0) {
|
||||||
|
$this->status = InvoiceStatus::POSTED;
|
||||||
|
$this->saveQuietly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function isLocked(): bool
|
public function isLocked(): bool
|
||||||
{
|
{
|
||||||
return in_array($this->status, [InvoiceStatus::POSTED, InvoiceStatus::PAID, InvoiceStatus::VOID]);
|
return in_array($this->status, [InvoiceStatus::POSTED, InvoiceStatus::PAID, InvoiceStatus::VOID]);
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,10 @@ class InvoiceLine extends Model
|
||||||
throw new InvoiceLockedException;
|
throw new InvoiceLockedException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($line->exists && $line->isDirty('invoice_id')) {
|
||||||
|
throw new \RuntimeException('Cannot move invoice line to another invoice');
|
||||||
|
}
|
||||||
|
|
||||||
$line->amount = $line->unit_price * $line->quantity;
|
$line->amount = $line->unit_price * $line->quantity;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,25 @@ class Payment extends Model
|
||||||
'payment_method' => PaymentMethod::class,
|
'payment_method' => PaymentMethod::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public static function booted(): void
|
||||||
|
{
|
||||||
|
static::saved(function (Payment $payment) {
|
||||||
|
// If invoice_id changed, recalculate the old invoice too
|
||||||
|
if ($payment->wasChanged('invoice_id')) {
|
||||||
|
$originalInvoiceId = $payment->getOriginal('invoice_id');
|
||||||
|
if ($originalInvoiceId) {
|
||||||
|
Invoice::find($originalInvoiceId)?->recalculateTotalPayments();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$payment->invoice->recalculateTotalPayments();
|
||||||
|
});
|
||||||
|
|
||||||
|
static::deleted(function (Payment $payment) {
|
||||||
|
$payment->invoice->recalculateTotalPayments();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function invoice(): BelongsTo
|
public function invoice(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Invoice::class);
|
return $this->belongsTo(Invoice::class);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?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->bigInteger('total_payments')->default(0)->after('total');
|
||||||
|
$table->bigInteger('balance_due')->storedAs('total - total_payments')->after('total_payments');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoices', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['balance_due', 'total_payments']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -104,7 +104,7 @@ new class extends Component {
|
||||||
<option value="">Select an invoice...</option>
|
<option value="">Select an invoice...</option>
|
||||||
@foreach ($this->invoices as $invoice)
|
@foreach ($this->invoices as $invoice)
|
||||||
<option value="{{ $invoice->id }}">{{ $invoice->client->abbreviation }}
|
<option value="{{ $invoice->id }}">{{ $invoice->client->abbreviation }}
|
||||||
- {{ $invoice->invoice_number }}</option>
|
- {{ $invoice->invoice_number }} - Balance: {{ formatMoney($invoice->balance_due) }}</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</flux:select>
|
</flux:select>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,15 @@ new class extends Component {
|
||||||
</flux:table.column>
|
</flux:table.column>
|
||||||
<flux:table.column sortable :sorted="$sortBy === 'total'" :direction="$sortDirection"
|
<flux:table.column sortable :sorted="$sortBy === 'total'" :direction="$sortDirection"
|
||||||
wire:click="sort('total')">
|
wire:click="sort('total')">
|
||||||
Total
|
Invoice Total
|
||||||
|
</flux:table.column>
|
||||||
|
<flux:table.column sortable :sorted="$sortBy === 'total_payments'" :direction="$sortDirection"
|
||||||
|
wire:click="sort('total_payments')">
|
||||||
|
Total Payments
|
||||||
|
</flux:table.column>
|
||||||
|
<flux:table.column sortable :sorted="$sortBy === 'balance_due'" :direction="$sortDirection"
|
||||||
|
wire:click="sort('balance_due')">
|
||||||
|
Balance Due
|
||||||
</flux:table.column>
|
</flux:table.column>
|
||||||
<flux:table.column></flux:table.column>
|
<flux:table.column></flux:table.column>
|
||||||
</flux:table.columns>
|
</flux:table.columns>
|
||||||
|
|
@ -104,6 +112,8 @@ new class extends Component {
|
||||||
@endif
|
@endif
|
||||||
</flux:table.cell>
|
</flux:table.cell>
|
||||||
<flux:table.cell>{{ formatMoney($invoice->total) }}</flux:table.cell>
|
<flux:table.cell>{{ formatMoney($invoice->total) }}</flux:table.cell>
|
||||||
|
<flux:table.cell>{{ formatMoney($invoice->total_payments) }}</flux:table.cell>
|
||||||
|
<flux:table.cell>{{ formatMoney($invoice->balance_due) }}</flux:table.cell>
|
||||||
<flux:table.cell>
|
<flux:table.cell>
|
||||||
<flux:dropdown position="bottom" align="start">
|
<flux:dropdown position="bottom" align="start">
|
||||||
<flux:button variant="ghost" size="sm" icon="ellipsis-horizontal" inset="top bottom"></flux:button>
|
<flux:button variant="ghost" size="sm" icon="ellipsis-horizontal" inset="top bottom"></flux:button>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue