Entering payments is one of the most frequent tasks in an invoicing app. Forcing users to navigate away from the invoice list to a separate Payment creation form interrupts the workflow. An inline modal keeps the user in context and reduces clicks.
Sprint 5.
// Modules/Invoices/Tests/Feature/InvoicesTest.php
#[Test]
public function it_opens_enter_payment_modal_with_outstanding_balance_prefilled(): void
{
/* Arrange */
$invoice = Invoice::factory()->for($this->company)->create([
'total' => 200.00,
'status' => 'sent',
]);
// No payments yet — outstanding = 200.00
/* Act */
$component = Livewire::actingAs($this->user)
->test(ListInvoices::class, ['tenant' => Str::lower($this->company->search_code)])
->mountTableAction('enter_payment', $invoice);
/* Assert */
$component->assertSuccessful();
$component->assertTableActionDataSet([
'amount' => 200.00,
]);
}
#[Test]
public function it_creates_a_payment_record_when_enter_payment_is_submitted(): void
{
/* Arrange */
$invoice = Invoice::factory()->for($this->company)->create([
'total' => 150.00,
'status' => 'sent',
]);
/* Act */
$component = Livewire::actingAs($this->user)
->test(ListInvoices::class, ['tenant' => Str::lower($this->company->search_code)])
->mountTableAction('enter_payment', $invoice)
->setTableActionData(['amount' => 150.00])
->callMountedTableAction();
/* Assert */
$component->assertSuccessful();
$this->assertDatabaseHas('payments', [
'invoice_id' => $invoice->id,
'amount' => 150.00,
]);
$this->assertDatabaseHas('invoices', [
'id' => $invoice->id,
'status' => 'paid',
]);
}
SMART Plan — [Payments] Implement payments options on invoice list
Specific
Add an "Enter Payment" inline table action to
InvoicesTablethat opens a Filament modal pre-filled with the invoice's ID and its outstanding balance (total minus already-recorded payments). Submitting the modal creates a newPaymentrecord linked to the invoice.Measurable
InvoicesTable.invoice_idfixed and theamountfield defaulting to the invoice's outstanding balance.Paymentrecord with the correctinvoice_idandamount.statusis updated (e.g.paidif fully settled,partialotherwise).Achievable
Action::make('enter_payment')with aform([TextInput::make('amount')->default(fn (Invoice $record) => $record->outstanding_balance)])and a modal toModules/Invoices/Filament/Company/Resources/Invoices/Tables/InvoicesTable.php.action()callback, delegate toModules/Payments/Services/PaymentService.phpto create the payment and update the invoice status.outstanding_balanceaccessor toModules/Invoices/Models/Invoice.phpif not already present.Modules/Invoices/Tests/Feature/InvoicesTest.php.Relevant
Entering payments is one of the most frequent tasks in an invoicing app. Forcing users to navigate away from the invoice list to a separate Payment creation form interrupts the workflow. An inline modal keeps the user in context and reduces clicks.
Time-bound
Sprint 5.
Arrange · Act · Assert