diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php
index 8b6b610..c0801b7 100644
--- a/app/Models/Invoice.php
+++ b/app/Models/Invoice.php
@@ -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]);
diff --git a/app/Models/InvoiceLine.php b/app/Models/InvoiceLine.php
index 797c389..2f968d2 100644
--- a/app/Models/InvoiceLine.php
+++ b/app/Models/InvoiceLine.php
@@ -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;
});
diff --git a/app/Models/Payment.php b/app/Models/Payment.php
index 55b9b71..56d4f48 100644
--- a/app/Models/Payment.php
+++ b/app/Models/Payment.php
@@ -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);
diff --git a/database/migrations/2026_01_29_034256_add_total_payments_and_balance_due_to_invoices_table.php b/database/migrations/2026_01_29_034256_add_total_payments_and_balance_due_to_invoices_table.php
new file mode 100644
index 0000000..30d55fa
--- /dev/null
+++ b/database/migrations/2026_01_29_034256_add_total_payments_and_balance_due_to_invoices_table.php
@@ -0,0 +1,29 @@
+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']);
+ });
+ }
+};
diff --git a/resources/views/components/⚡create-payment.blade.php b/resources/views/components/⚡create-payment.blade.php
index 87d89a7..07e443c 100644
--- a/resources/views/components/⚡create-payment.blade.php
+++ b/resources/views/components/⚡create-payment.blade.php
@@ -104,7 +104,7 @@ new class extends Component {
@foreach ($this->invoices as $invoice)
+ - {{ $invoice->invoice_number }} - Balance: {{ formatMoney($invoice->balance_due) }}
@endforeach
diff --git a/resources/views/components/⚡invoice-list.blade.php b/resources/views/components/⚡invoice-list.blade.php
index 04e5ccc..05777ed 100644
--- a/resources/views/components/⚡invoice-list.blade.php
+++ b/resources/views/components/⚡invoice-list.blade.php
@@ -70,7 +70,15 @@ new class extends Component {
- Total
+ Invoice Total
+
+
+ Total Payments
+
+
+ Balance Due
@@ -104,6 +112,8 @@ new class extends Component {
@endif
{{ formatMoney($invoice->total) }}
+ {{ formatMoney($invoice->total_payments) }}
+ {{ formatMoney($invoice->balance_due) }}