# SaaS invoicing implementation plan — Filament v5

## 1. Scope and starting point

Build the six requested capabilities around three primary Resources: customers, products, and invoices. Invoice items belong inside the invoice editor; payments belong to an invoice and are recorded through an explicit Action, not unrestricted CRUD.

The supplied application has Filament 5.8.1, Laravel 13.31.0, and Livewire 4.4.4. Its `AdminPanelProvider` already provides login, a dashboard, and resource/page discovery under `app/Filament`. It has only the default User model and framework migrations. Retain this panel and the locked dependencies; add domain functionality rather than rebuilding the application.

**Planning assumptions, not user-approved business rules:** staff act for a business account; customers receive invoices without panel access; sending means email; payments are manually recorded; partial payments are supported; financial content becomes immutable when sending starts. These give the plan a concrete implementation path but need confirmation before the affected work begins.

Online payment collection, recurring invoices, SaaS subscription billing, credit notes, refunds, and a customer portal are not part of the requested first release. Tax jurisdiction, numbering, currency, and corrections require decisions below rather than invented accounting rules.

## 2. Domain and ownership

| Concept          | Proposed data and relationships                                                                                                                    | Integrity rules                                                                                                        |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Business account | `Account`; issuer identity, billing address, currency/timezone settings; associated users                                                          | Tenant boundary, distinct from a customer being invoiced. Membership model needs confirmation.                         |
| Customer         | `Customer` belongs to account; name, billing email/address, active flag; has many invoices                                                         | Archive instead of deleting referenced customers. Later edits must not rewrite issued invoice details.                 |
| Product          | `Product` belongs to account; name, optional SKU, description, default unit price, active flag                                                     | Catalog defaults, not the authoritative source of historical invoice prices. Archive referenced products.              |
| Invoice          | `Invoice` belongs to account/customer; number, issue/due dates, currency, notes, issuer/customer snapshots, totals, issuance timestamps            | Number unique within account; financial content locked once issuance begins. Total is server-calculated.               |
| Invoice item     | `InvoiceItem` belongs to invoice; optional product reference, description, quantity, unit price, position, line total                              | Copy product defaults into independent item fields. Parent determines ownership; reject products from another account. |
| Payment          | `Payment` belongs to invoice; amount, payment date, method, reference, notes, recording user/time, submission token                                | Currency inherited from invoice. Positive amount; no silent overwrite or deletion of posted money.                     |
| Send attempt     | `InvoiceDelivery` belongs to invoice; recipient, queued/accepted/failed timestamps, status, safe error detail, provider identifier where available | Operational history separate from invoice settlement; retries retain the same frozen invoice content.                  |

Store monetary totals in currency minor units, with explicit currency precision. Use exact decimal arithmetic for quantities and intermediate calculations, not binary floats. Agree quantity precision and rounding before implementation; calculate each line using that policy and sum the rounded lines. For example, under a two-decimal, half-up, tax-free policy, 3 × 19.99 plus 2 × 7.45 totals 74.87.

Invoice balances come from the immutable invoice total minus valid recorded payments. Do not make a user-editable `paid` checkbox or accept client-submitted totals. Persist invoice, items, and recalculated totals atomically, including removals and reordering.

## 3. Filament surfaces

Use v5 conventions: resource forms accept `Filament\Schemas\Schema`, tables use `Filament\Tables\Table`, and built-in and custom Actions use `Filament\Actions`.

| Surface                    | Concrete primitives and responsibility                                                                                                                                                                                                                                                                     |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Customers                  | `CustomerResource`: `ListCustomers`, `CreateCustomer`, `EditCustomer`, `ViewCustomer`. `TextInput`/`Textarea` fields, searchable table, active filter, `CreateAction`, `EditAction`, custom `archive` Action.                                                                                              |
| Customer invoice history   | `InvoicesRelationManager` on customer Edit/View pages: number, dates, total, balance, status; `ViewAction` opens the canonical invoice page. A `createInvoice` navigation Action opens `CreateInvoice` with that customer preselected and revalidated. No second invoice editor here.                      |
| Products                   | `ProductResource`: `ListProducts`, `CreateProduct`, `EditProduct`; name/SKU search, price and active columns, `CreateAction`, `EditAction`, custom `archive` Action.                                                                                                                                       |
| Invoice work queue         | `InvoiceResource` / `ListInvoices`: number/customer search; settlement, overdue, delivery, customer, and date filters; total/paid/balance columns; `CreateAction`, `ViewAction`, draft-only `EditAction`.                                                                                                  |
| Draft invoice editor       | `CreateInvoice` and `EditInvoice`: customer `Select`, date fields, notes, currency display, and `Repeater::make('items')->relationship()` containing product `Select`, description, quantity, unit price, and ordering. Live totals are previews only.                                                     |
| Invoice detail and preview | `ViewInvoice`: an infolist showing issuer/customer snapshots, ordered items, dates, totals, balance, and separate settlement/delivery badges. Header Actions: draft-only `EditAction`, `sendInvoice`, `retrySend`/`resendInvoice`, and `recordPayment`, each with explicit authorization and state guards. |
| Payment history            | `PaymentsRelationManager` on invoice View: amount, date, method, reference, recording user/time. Read-only rows; recording happens through the page header Action. Keep Filament's default read-only behavior on View pages rather than disabling it globally.                                             |
| Delivery history           | Read-only `DeliveriesRelationManager` on invoice View: recipient, attempt time, result. Retry/resend uses the page header Actions, not generic record editing.                                                                                                                                             |

No standalone invoice-item Resource or payment Resource is needed for these flows. No custom standalone workflow Page is necessary: resource Pages provide the full workspace. An account settings Page may use `EditTenantProfile` if the confirmed tenancy model uses Filament tenant switching.

## 4. Primary user flows

### A. Manage customers and products

1. Staff sign in and enter their authorized account context.
2. Open Customers → Create, enter billing contact details, save, then inspect the customer detail and invoice history. Editing updates defaults for future invoices, not frozen invoices.
3. Open Products → Create, enter description and price, save. Edits change catalog defaults only.
4. Archive a customer/product when no longer usable for new invoices. Exclude archived records from new selections while still displaying them on existing invoices. An existing draft referencing archived data must be reviewed before sending.

### B. Create and edit an invoice with line items

1. Open Invoices → Create, or use `createInvoice` from a customer. Select a customer belonging to the current account and enter issue/due dates and notes.
2. Add rows in the items repeater. Selecting a product copies its description and price; changing quantity or overriding the draft price updates the preview. Allow custom-description rows if confirmed; these have no product reference.
3. Add, remove, and reorder rows. Validate positive quantities, permitted prices/precision, and required descriptions. Permit an incomplete draft to be saved, but clearly show send-blocking omissions.
4. Save creates a draft and its items in one transaction, assigns ownership server-side, and recalculates totals. Redirect to `ViewInvoice` for review.
5. `EditAction` returns to the same draft editor. Use an optimistic version check so a stale tab cannot overwrite newer edits. Recheck draft state while saving so a concurrent send cannot be undone by an old editor.

### C. Send the invoice

1. On `ViewInvoice`, staff review the complete invoice and choose `sendInvoice`.
2. An Action modal shows the recipient and final amount and explains that financial content will be locked. Validate billing details, at least one valid item, dates, currency, and a positive total. Recipient correction must not silently alter customer master data.
3. On confirmation, reauthorize, lock/reload the invoice, recalculate totals, allocate its account-unique number, freeze issuer/customer/item content, and persist a pending delivery attempt atomically. Repeated submission must not issue another invoice or create another active attempt.
4. Dispatch delivery after commit. The queued Laravel job renders an HTML/text invoice email from the frozen data. The email must include items, totals, dates, and agreed payment instructions, not a staff-only panel URL. A PDF is an additional delivery-format decision, not an assumed dependency.
5. Show “Queued” immediately, not “Sent.” On mail transport acceptance, set the first `sent_at` and mark the attempt accepted. Refresh/poll the relevant detail status so staff can see the outcome. Acceptance is not proof of inbox delivery or reading.
6. On failure, retain the frozen invoice and show a safe failure summary with `retrySend`. A later explicit `resendInvoice` creates another attempt without changing invoice number, totals, or payment state. Confirm recipient and warn about duplicate delivery when the previous outcome is uncertain.

The baseline mailer is `log`, and the queue is database-backed. Production delivery requires configured mail transport and a supervised queue worker. Persist pending attempts so enqueue failures can be recovered. Use stable attempt identifiers and provider idempotency where available; SMTP cannot guarantee exactly-once email after a crash between acceptance and database acknowledgment.

### D. Record and track payments

1. Find an unpaid invoice through the list filters, open it, and choose `recordPayment`.
2. The modal shows invoice currency and outstanding balance; collect amount, payment date, method, optional reference, and notes. Default to the balance but allow a smaller amount.
3. Confirm and submit. The application reauthorizes and locks the invoice, recalculates the balance, validates the amount, inserts the payment with an idempotency token, and commits atomically.
4. Refresh the payment Relation Manager and balance. A payment below the balance produces “Partially paid”; one exactly equal to it produces “Paid.” No email Action is necessary to mark an invoice paid.
5. Reject zero, negative, duplicate submissions, and—under the proposed first-release policy—overpayments. Show a useful conflict when another operator recorded a payment first. A 74.87 invoice with a 20.00 payment leaves 54.87; recording 54.87 settles it, while 54.88 is rejected.

Corrections need an agreed auditable reversal flow before production use. Do not expose generic payment Edit/Delete Actions as a shortcut.

## 5. States and triggering Actions

Treat issuance, settlement, and delivery as separate concerns. A single status column cannot correctly represent “partially paid and resend failed.”

| Transition                           | Trigger                                       | Preconditions and result                                                                                                                      |
| ------------------------------------ | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| New → Draft                          | `CreateInvoice` save                          | Authorized account; save draft and items together.                                                                                            |
| Draft → Draft                        | `EditInvoice` save                            | Still draft, current version; update content and totals atomically.                                                                           |
| Draft → Issued, awaiting send        | `sendInvoice`                                 | Complete valid invoice; freeze content and number; pending delivery created. This technical intermediate state prevents edits during sending. |
| Delivery pending → Accepted          | Send job succeeds                             | Set `sent_at` once. Unpaid invoice is displayed as “Sent.”                                                                                    |
| Delivery pending → Failed            | Send job exhausts retries or definite failure | Invoice stays issued and immutable; UI shows send failure, not Sent.                                                                          |
| Failed → Pending                     | `retrySend`                                   | Authorized retry; no competing active attempt; unchanged financial content.                                                                   |
| Accepted → New pending attempt       | `resendInvoice`                               | Explicit confirmation; existing sent/payment state preserved.                                                                                 |
| Sent/unpaid → Partially paid         | `recordPayment`                               | Proposed rule: first send accepted; positive payment below balance.                                                                           |
| Sent/unpaid or Partially paid → Paid | `recordPayment`                               | Payment equals current balance.                                                                                                               |

“Overdue” is a derived badge/filter: an issued invoice with positive balance and a due date earlier than today in the account timezone. It can coexist with partial payment or send failure. It requires no manual Action and is not a terminal status.

Whether payments may be recorded before a successful send is an open business decision. The proposed guard above is deliberately explicit. Voiding, reopening, zero-total invoices, and payment reversal transitions must be specified before enabling those operations; no generic status selector should bypass this state model.

## 6. SaaS security and application boundaries

Confirm whether users belong to one account or can switch between accounts. For multiple memberships, use `Account` tenancy in `AdminPanelProvider`, implement `HasTenants::getTenants()` and `canAccessTenant()`, and configure account ownership relationships. For single-account users, use mandatory account scoping instead of adding an unnecessary tenant switcher. In either case implement `FilamentUser::canAccessPanel()` for production access.

Add model policies for viewing/creating/updating records and explicit abilities for send, resend, and payment recording. The initial permissions may be identical for account staff; do not invent a role hierarchy without a requirement. Hiding buttons is not authorization: check permissions, ownership, and current state inside every mutation.

Scope customer/product selectors, validation queries, relation managers, and record URLs. Load items and payments through their authorized invoice. Queued jobs must receive and verify account/invoice identity explicitly; they cannot rely on Filament's current tenant. Filament scopes tenant-aware resource models in panel requests, not every model or every background query.

Keep financial calculation and transaction/state rules in focused application operations used by Filament callbacks and jobs. Do not scatter payment or sending logic among page lifecycle hooks. Use database constraints for account invoice-number uniqueness and submission-token uniqueness, and serialization appropriate to the chosen production database for payments and issuance. SQLite is the supplied development baseline, not evidence that production row-lock behavior has been tested.

## 7. Implementation sequence and acceptance checks

1. **Resolve business boundaries:** account membership, currency/tax/rounding, numbering, delivery format, issuance lock, and payment/correction rules. Establish issuer settings and access policies before exposing data.
2. **Domain foundation and master data:** migrations, relationships, account scoping, factories, policies; Customer and Product Resources with archiving. Verify one account cannot view or select another account's data.
3. **Draft invoicing:** invoice Resource Pages, item repeater, calculations and snapshots. Test create/edit, adding/removing/reordering items, stale saves, and catalog changes leaving existing item prices unchanged.
4. **Issuance and sending:** preview, send Actions, number allocation, frozen content, pending-attempt persistence, job and email template, retries and delivery history. Test double-clicks, concurrent edit/send, enqueue failure, transport rejection, and retry/resend without renumbering or altering totals.
5. **Payments and tracking:** recording Action, read-only history, settlement and overdue filters. Test partial/exact/overpayment boundaries, duplicate tokens, concurrent payments, and due-date boundaries in account timezones. Run concurrency checks against the intended production database.
6. **End-to-end acceptance:** staff create a customer/product, save and edit a multi-item invoice, send through a mail sandbox, record partial then final payment, and locate it using filters. Exercise draft, queued, failed-send, sent, partial, overdue, and paid screens in a browser; verify visible feedback and server-side denial of forged Actions. Check the email's content against the frozen invoice. Existing baseline tests passing is not coverage of these new flows.

## 8. Decisions to confirm

- **Account access:** single or multiple business memberships? How are accounts/staff provisioned? Self-service signup and invitations are not specified.
- **Accounting:** supported currencies, fractional quantities, tax-inclusive/exclusive pricing, discounts, rounding, statutory invoice fields, and numbering sequence/gap requirements. Do not infer these from the user's timezone.
- **Issuance:** approve draft-only editing and locking on send initiation? What is the approved correction/void process for an issued invoice or a definitively failed send?
- **Delivery:** email body sufficient, or PDF required? Which provider, sender identity, recipient override rules, and payment instructions?
- **Payments:** approve manual entry, partial payments, rejecting overpayments, and requiring a successful send first? How should mistaken entries be reversed, and by whom?

These decisions gate the affected implementation steps; the proposed defaults above are a coherent starting design, not evidence of user approval.