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
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public static function booted(): void
|
||||
{
|
||||
static::creating(function (Invoice $invoice) {
|
||||
$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 = [
|
||||
'total' => MoneyCast::class,
|
||||
'status' => InvoiceStatus::class,
|
||||
'invoice_date' => 'date',
|
||||
'due_date' => 'date',
|
||||
'sent_at' => 'date',
|
||||
'total' => MoneyCast::class,
|
||||
'total_payments' => MoneyCast::class,
|
||||
'balance_due' => MoneyCast::class,
|
||||
'status' => InvoiceStatus::class,
|
||||
'invoice_date' => 'date',
|
||||
'due_date' => 'date',
|
||||
'sent_at' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -84,6 +87,22 @@ class Invoice extends Model
|
|||
$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
|
||||
{
|
||||
return in_array($this->status, [InvoiceStatus::POSTED, InvoiceStatus::PAID, InvoiceStatus::VOID]);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ class InvoiceLine extends Model
|
|||
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;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,25 @@ class Payment extends Model
|
|||
'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
|
||||
{
|
||||
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>
|
||||
@foreach ($this->invoices as $invoice)
|
||||
<option value="{{ $invoice->id }}">{{ $invoice->client->abbreviation }}
|
||||
- {{ $invoice->invoice_number }}</option>
|
||||
- {{ $invoice->invoice_number }} - Balance: {{ formatMoney($invoice->balance_due) }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,15 @@ new class extends Component {
|
|||
</flux:table.column>
|
||||
<flux:table.column sortable :sorted="$sortBy === 'total'" :direction="$sortDirection"
|
||||
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.columns>
|
||||
|
|
@ -104,6 +112,8 @@ new class extends Component {
|
|||
@endif
|
||||
</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:dropdown position="bottom" align="start">
|
||||
<flux:button variant="ghost" size="sm" icon="ellipsis-horizontal" inset="top bottom"></flux:button>
|
||||
|
|
|
|||
Loading…
Reference in New Issue