Skip to content

Payment create hides fully paid invoices — Production Readiness Audit ​

FieldValue
Date2026-10-18
StatusGo for production — feature Pest green; headed payments Playwright 10/10
ScopePayment create allocation picker omits fully paid (and cancelled) invoices; edit keeps already-allocated invoices
CatalogNo bump (picker/API query filter only)
CompanionPayments user guide · Payments developer guide · Invoices API · CHANGELOG

Executive summary ​

When recording a new customer payment, the invoice allocation picker must only offer invoices that can still accept money. Fully paid invoices (status=paid or balance_due = 0) and cancelled invoices are excluded. Draft and unpaid invoices with a remaining balance remain selectable. Editing a draft payment still shows invoices already allocated on that payment so existing rows do not disappear from the select.

Go / No-Go: Go. Deploy Backend + Frontend together. No migrate. No new permission.

GateResult
Platform freezePass
GET /invoices?allocatable=1 returns only balance_due > 0 and excludes paid / cancelledPass (Pest)
Create payment SPA requests allocatable=1Pass (code review)
Client filter hides paid/cancelled and keeps balance_due > 0Pass (Vitest)
Edit keeps already-allocated invoice ids even when paidPass (Vitest)
Create form still validates empty amountPass (headed Playwright)
Paid invoice number absent from picker; open invoice presentPass (headed Playwright)
Existing payments workflow (allocate, post, refund validation, void)Pass (headed Playwright suite)

Security summary ​

ControlStatus
Allocatable list still requires invoices.view / viewAnyPass
Assignee scope on invoice list unchangedPass
Post still rejects paid/cancelled allocations server-sidePass (existing CustomerPaymentService::post)
No new endpoints or permissionsPass

Findings disposition ​

IDSeverityItemDisposition
F1MediumCreate payment allocation picker listed fully paid invoicesFixed — API allocatable=1 + SPA filter
I1InfoEdit mode loads unfiltered per_page=100 and keeps allocated ids client-sideAccepted — draft edits are rare; post still rejects invalid allocations
I2InfoLocal headed run may need CI=1 so Playwright starts Vite when port 5173 is downAccepted — operator note only

Headed browser coverage ​

CI=1 npm run test:e2e:payments:headed -- --workers=1 on 2026-10-18. Result: 10 passed.

Authz and Stripe specs each use one demo login (shared session within the file). The payments workflow uses one demo login for the serial path:

  1. Create form: submit without amount → Amount is required (field error under #main-content)
  2. Allocation picker: fully paid invoice number → no options / empty; open unpaid invoice still listed
  3. Create payment, allocate to open invoice, post
  4. Refund dialog validation (empty / over-balance), receipt PDF + email, void
  5. Stripe dialog enable/save/live-fail/disconnect + portal online payment flag
  6. Staff without payments.send: download receipt allowed, email hidden / API forbidden

Upgrade ​

  1. Deploy Backend (no migrate).
  2. Deploy Frontend SPA and hard-refresh.
  3. Smoke: open Payments → New, allocate — a paid invoice number must not appear; an unpaid invoice with balance must appear.

Official documentation for the EloSync SaaS Platform.