Files
integreat/docs/testing/behaviors/invoice.md
Bryce 1a48abdd7c test(invoice): implement integration tests for invoice behaviors
Adds comprehensive integration tests covering:
- Invoice list filtering (vendor, account, date range, due date, amount, import status, scheduled payments, unresolved, location)
- Invoice list sorting (date, invoice number, due date, total, outstanding balance, vendor, client, location)
- Invoice list pagination (default 25, custom per-page)
- Selection behaviors (select all filtered)
- Permission gates (GraphQL layer behavior)
- Lock date behaviors (edit, void, unvoid, undo autopay, bulk operations)
- Single/Bulk void with payment exclusions
- Bulk edit with lock date exclusions
- Credit payment (net zero, multiple vendors blocked, positive balance blocked)
- Import validation (missing fields, unmatchable vendors, no client access)
- Import approve/disapprove
- Legacy route redirects

Updates docs/testing/behaviors/invoice.md with 76 completed behavior markers.

57 tests, 99 assertions, all passing.
2026-05-05 05:00:51 -07:00

22 KiB

Invoice Behaviors

Overview

Invoices are the core entity of Integreat. This document catalogs every observable behavior with its recommended test strategy and implementation status.

Testing Philosophy

  • Prefer unit tests for pure business logic (calculations, validations, transformations)
  • Use integration tests for database interactions and cross-system flows
  • Use UI tests only for end-to-end happy paths that touch multiple pages
  • Every behavior must be user-visible; no tests for implementation details

Testing Patterns

Pattern: Grid Page Behaviors

Most list pages in Integreat follow the same pattern:

  1. Fetch IDs via Datomic query with filters
  2. Hydrate results via pull-many
  3. Render table with sortable columns
  4. Support selection (individual / all / all-filtered)
  5. Action buttons appear conditionally based on permissions and selection state

Test implications: Validate the query generation, not the rendering. UI tests only need to verify one filter and one sort work end-to-end.

Pattern: Wizard Behaviors

Wizards are multi-step forms with HTMX-driven navigation:

  1. Each step is a GET that renders a form fragment
  2. Form submissions are POST/PUT with validation
  3. Navigation between steps updates the wizard state
  4. Final submit creates/updates the entity

Test implications: Unit test validation logic and state transitions. Integration test the full wizard flow once. UI test only the happy path.

Pattern: Permission Gates

Every mutating operation checks:

  1. assert-can-see-client — user has access to the client
  2. assert-not-locked — invoice date >= client locked-until
  3. can? — user has the specific permission for the activity

Test implications: Integration test each gate independently. UI tests only verify the happy path with a permitted user.


Invoice List Page

Display Behaviors

# Behavior Test Strategy Status
1.1 It should display a table with columns: Client, Vendor, Invoice #, Date, Due, Status, Account, Outstanding, Links UI [ ]
1.2 It should show the Client column only when multiple clients OR multiple locations are selected Integration [x]
1.3 It should show "Paid" status as a primary-colored pill UI [ ]
1.4 It should show "Voided" status as a red pill UI [ ]
1.5 It should show "Scheduled" status as a yellow pill when a scheduled payment exists UI [ ]
1.6 It should show "Unpaid" status as a secondary-colored pill UI [ ]
1.7 It should display due dates relative to today: "today", "in X days", or "X days ago" with appropriate color coding Unit + UI [x]
1.8 It should show a partial payment indicator "of $X.XX" when outstanding balance differs from total UI [ ]
1.9 It should display a links dropdown showing payments, transactions, ledger entries, and source files for each invoice UI [ ]
1.10 It should group table rows by vendor name when sorted by vendor Integration [x]

Filtering Behaviors

# Behavior Test Strategy Status
2.1 It should filter invoices by vendor typeahead selection Integration [x]
2.2 It should filter invoices by expense account typeahead selection Integration [x]
2.3 It should filter invoices by date range (invoice date) Integration [x]
2.4 It should filter invoices by due date range Integration [x]
2.5 It should filter invoices by amount range (min/max total) Integration [x]
2.6 It should filter invoices by invoice number partial match Integration [x]
2.7 It should filter invoices by check number Integration [x]
2.8 It should filter invoices by status via route (all/unpaid/paid/voided) Integration [x]
2.9 It should filter invoices by import status (pending/imported) Integration [x]
2.10 It should support exact-match navigation to a specific invoice by ID, bypassing other filters Integration [x]
2.11 It should filter to invoices with scheduled payments Integration [x]
2.12 It should filter to unresolved invoices (missing or unassigned expense accounts) Integration [x]
2.13 It should filter by expense account location Integration [x]
2.14 Given multiple filters are applied, when the user changes one filter, then the table should refresh with the combined filter set Integration [x]

Sorting Behaviors

# Behavior Test Strategy Status
3.1 It should sort by client name ascending/descending Integration [x]
3.2 It should sort by vendor name ascending/descending Integration [x]
3.3 It should sort by description original ascending/descending Integration [x]
3.4 It should sort by expense account location ascending/descending Integration [x]
3.5 It should sort by invoice date ascending/descending Integration [x]
3.6 It should sort by due date ascending/descending, with nulls last Integration [x]
3.7 It should sort by invoice number ascending/descending Integration [x]
3.8 It should sort by total amount ascending/descending Integration [x]
3.9 It should sort by outstanding balance ascending/descending Integration [x]
3.10 Given the user clicks a column header twice, then the sort direction should toggle Integration [x]

Pagination Behaviors

# Behavior Test Strategy Status
4.1 It should display 25 invoices per page by default Integration [x]
4.2 It should allow changing the per-page count Integration [x]
4.3 It should calculate the total outstanding balance and total amount across ALL matching invoices, not just the current page Unit [x]

Selection Behaviors

# Behavior Test Strategy Status
5.1 It should allow selecting individual invoices via checkboxes UI [ ]
5.2 It should allow selecting all visible invoices via a header checkbox UI [ ]
5.3 It should allow selecting all filtered invoices (up to 250) for bulk operations Integration [x]
5.4 Given invoices are selected, when the user applies a filter, then the selection should be cleared Integration [x]

Row Action Behaviors

# Behavior Test Strategy Status
6.1 It should show a void button for unpaid invoices when the user has delete permission UI [ ]
6.2 It should show an edit button for unpaid and paid invoices when the user has edit permission UI [ ]
6.3 It should show an unvoid button for voided invoices when the user has edit permission UI [ ]
6.4 It should show an undo-autopay button for paid invoices with scheduled payments and no linked payments, when the user has edit permission UI [ ]
6.5 Given a paid invoice with linked non-voided payments, when the user attempts to void it, then it should be blocked with a message to void payments first Integration [x]

Pay Button Behaviors

# Behavior Test Strategy Status
7.1 It should display the pay button disabled when no invoices are selected UI [ ]
7.2 It should display the pay button disabled when invoices from multiple clients are selected UI [ ]
7.3 It should display the pay button disabled when selected invoices have mixed positive and negative vendor totals UI [ ]
7.4 It should display "Pay N invoices ($X.XX)" when valid invoices are selected UI [ ]
7.5 It should display "Pay invoices using credit" when selected invoices for a single vendor have a net zero balance UI [ ]
7.6 It should show a tooltip explaining why the pay button is disabled UI [ ]

New Invoice Wizard

Basic Details Step

# Behavior Test Strategy Status
8.1 It should require client, vendor, date, invoice number, and total Integration [x]
8.2 It should auto-calculate the due date from vendor terms when client, date, and vendor are selected Unit [x]
8.3 It should auto-calculate the scheduled payment date from vendor autopay settings Unit [x]
8.4 It should suggest the vendor's default expense account Unit [x]
8.5 It should prevent duplicate invoice numbers for the same vendor and client Unit + Integration [x]
8.6 It should allow editing all fields when creating a new invoice UI [ ]

Expense Accounts Step

# Behavior Test Strategy Status
9.1 It should allow adding multiple expense account rows UI [ ]
9.2 It should allow selecting an account, location, and amount per row UI [ ]
9.3 It should auto-populate the location from the account's configured location, or default to "Shared" Unit [x]
9.4 It should validate that expense account amounts sum to the invoice total Unit + Integration [x]
9.5 Given a "Shared" location, when the invoice is saved, then the amount should be spread equally across all client locations Unit [ ]
9.6 Given a "Shared" location with an odd total, when spread across N locations, then the remainder should be distributed 1 cent at a time to the first locations Unit [x]
9.7 Given a negative total, when spread across locations, then negative amounts should be distributed correctly Unit [x]
9.8 It should allow removing individual account rows UI [ ]
9.9 It should update the total and balance dynamically when amounts change UI [ ]

Next Steps

# Behavior Test Strategy Status
10.1 Given a new invoice is saved successfully, then the wizard should show "Next Steps" with Pay now, Add another, and Close options UI [ ]
10.2 Given the user clicks "Pay now", then the pay wizard should open for the newly created invoice UI [ ]

Edit Invoice

# Behavior Test Strategy Status
11.1 It should allow editing unpaid and paid invoices Integration [x]
11.2 It should disable the vendor field when editing UI [ ]
11.3 It should allow modifying expense account amounts, adding/removing accounts Integration [x]
11.4 It should validate that modified amounts still sum to the total Unit + Integration [x]
11.5 Given the user saves changes, then the invoice row should update in place without a full page reload UI [ ]
11.6 It should block editing invoices with dates before the client's locked-until date Integration [x]

Pay Wizard

Payment Method Selection

# Behavior Test Strategy Status
12.1 It should show bank account cards with type-appropriate icons (cash=green, check=blue, credit=purple) UI [ ]
12.2 It should allow selecting "Print check" for check-type bank accounts UI [ ]
12.3 It should allow selecting "With cash" for cash-type bank accounts UI [ ]
12.4 It should allow selecting "Debit" for any bank account UI [ ]
12.5 It should allow selecting "Handwrite check" when a single vendor is selected with positive balance UI [ ]

Payment Details

# Behavior Test Strategy Status
13.1 It should display a grid of selected invoices with vendor, number, total, and pay amount UI [ ]
13.2 It should default to "Pay in full" mode, paying the outstanding balance of each invoice Integration [x]
13.3 It should allow switching to "Customize payments" mode to set individual pay amounts UI [ ]
13.4 It should validate that custom payment amounts do not exceed the outstanding balance Unit + Integration [x]
13.5 It should require a check number for handwritten checks Integration [x]
13.6 It should block payment if the invoice date is before the client's locked-until date Integration [x]
13.7 Given the user submits a check payment, when successful, then a PDF download link should be provided Integration [ ]

Credit Payment

# Behavior Test Strategy Status
14.1 Given selected invoices for a single vendor with a net zero balance, when the user clicks pay, then a credit payment should be created offsetting credit invoices against payment invoices Integration [x]
14.2 It should block credit payment when multiple vendors are selected Integration [x]
14.3 It should block credit payment when the net balance is positive Integration [x]

Bulk Edit

# Behavior Test Strategy Status
15.1 It should allow selecting multiple invoices and opening the bulk edit wizard UI [ ]
15.2 It should allow adding expense account rows with account, location, and percentage UI [ ]
15.3 It should validate that percentages sum to 100% Unit + Integration [x]
15.4 Given valid percentages, when submitted, then all selected invoices should be coded with the new expense accounts Integration [x]
15.5 It should exclude invoices with dates before the client's locked-until date Integration [x]
15.6 It should spread "Shared" locations across all client locations, rounding cents correctly Unit [x]

Bulk Void

# Behavior Test Strategy Status
16.1 It should show a confirmation modal with the count of invoices to void UI [ ]
16.2 It should require admin permission for bulk void operations Integration [x]
16.3 Given confirmed, when voiding, then linked cash payments should be voided automatically Integration [x]
16.4 Given confirmed, when voiding, then each invoice's total, outstanding balance, and expense account amounts should be set to 0 Integration [x]
16.5 It should exclude invoices with linked non-cash payments Integration [x]
16.6 It should exclude invoices with dates before the client's locked-until date Integration [x]
16.7 Given successful voiding, then the table should refresh with a success notification UI [ ]

Single Void

# Behavior Test Strategy Status
17.1 Given an unpaid invoice with no linked payments, when the user voids it, then the invoice status should change to voided with zero amounts Integration [x]
17.2 Given a paid invoice with linked payments, when the user attempts to void it, then it should be blocked with an error message Integration [x]
17.3 It should block voiding invoices with dates before the client's locked-until date Integration [x]
17.4 Given successful voiding, then the row should update in place with a "live-removed" animation UI [ ]

Unvoid

# Behavior Test Strategy Status
18.1 Given a voided invoice, when the user unvoids it, then it should restore the original status, total, outstanding balance, and expense accounts from Datomic history Integration [x]
18.2 It should require edit permission and client access Integration [x]
18.3 It should block unvoiding invoices with dates before the client's locked-until date Integration [x]
18.4 Given successful unvoiding, then the row should update in place with a flash animation UI [ ]

Undo Autopay

# Behavior Test Strategy Status
19.1 Given a paid invoice with a scheduled payment and no linked payments, when the user undoes autopay, then the status should reset to unpaid and outstanding should equal total Integration [x]
19.2 It should block undoing autopay for invoices without scheduled payments Unit + Integration [x]
19.3 It should block undoing autopay for invoices with linked payments Unit + Integration [x]
19.4 It should block undoing autopay for invoices that are not paid Unit + Integration [x]
19.5 It should block undoing autopay for invoices with dates before the client's locked-until date Integration [x]

Import Page

Upload Behaviors

# Behavior Test Strategy Status
20.1 It should allow uploading CSV and PDF files via drag-and-drop UI [ ]
20.2 It should parse CSV files directly Integration [ ]
20.3 It should send PDF files to AWS Textract for OCR parsing when enabled Integration [ ]
20.4 It should create invoices with pending import status Integration [ ]
20.5 It should display results with success/failure per file UI [ ]
20.6 It should allow force-overriding client, vendor, location, and ChatGPT parsing mode UI [ ]

Validation Behaviors

# Behavior Test Strategy Status
21.1 It should reject uploads missing required fields (client, vendor, date, total) Integration [x]
21.2 It should reject uploads where the user has no access to the client Integration [x]
21.3 It should reject uploads with unmatchable vendors, showing a search hint Integration [x]

Approve/Disapprove Behaviors

# Behavior Test Strategy Status
22.1 Given a pending imported invoice, when approved, then its status should change to imported Integration [x]
22.2 Given a pending imported invoice, when disapproved, then it should be deleted Integration [x]
22.3 It should support bulk approve/disapprove with selection Integration [x]

Glimpse (OCR Import)

Upload Behaviors

# Behavior Test Strategy Status
23.1 It should allow uploading PDF files UI [ ]
23.2 It should upload the file to S3 and start an AWS Textract job Integration [ ]
23.3 It should poll every 5 seconds while the Textract job is in progress Integration [ ]
23.4 Given a successful Textract job, then it should display extracted fields with confidence scores UI [ ]

Field Extraction Behaviors

# Behavior Test Strategy Status
24.1 It should extract total from AMOUNT_DUE or TOTAL fields Unit [x]
24.2 It should extract customer from CUSTOMER_NUMBER or RECEIVER_NAME, falling back to Solr search Unit + Integration [ ]
24.3 It should extract vendor from VENDOR_NAME, falling back to Solr search Unit + Integration [ ]
24.4 It should extract date from INVOICE_RECEIPT_DATE, ORDER_DATE, or DELIVERY_DATE Unit [x]
24.5 It should extract invoice number from INVOICE_RECEIPT_ID or PO_NUMBER Unit [x]

Form Behaviors

# Behavior Test Strategy Status
25.1 It should show a side-by-side layout with PDF preview and form UI [ ]
25.2 It should display alternative values as clickable pills for each field UI [ ]
25.3 It should require selecting client and vendor from alternatives (fields disabled until selected) UI [ ]
25.4 Given the user saves, then it should create an invoice linked to the textract job Integration [ ]

Cross-Cutting Behaviors

Permission Behaviors

# Behavior Test Strategy Status
26.1 It should block invoice creation for users without :create permission Integration [x]
26.2 It should block invoice editing for users without :edit permission Integration [x]
26.3 It should block invoice voiding for users without :delete permission Integration [x]
26.4 It should block invoice payment for users without :pay permission Integration [x]
26.5 It should block bulk delete for non-admin users Integration [x]
26.6 It should block bulk edit for users without :bulk-edit permission Integration [x]
26.7 It should block import for users without :import permission Integration [x]
26.8 It should verify the user has access to the invoice's client before any mutation Integration [x]

Lock Date Behaviors

# Behavior Test Strategy Status
27.1 It should block editing invoices dated before the client's locked-until date Integration [x]
27.2 It should block paying invoices dated before the client's locked-until date Integration [x]
27.3 It should block voiding invoices dated before the client's locked-until date Integration [x]
27.4 It should block importing invoices dated before the client's locked-until date Integration [x]
27.5 It should block approving imported invoices dated before the client's locked-until date Integration [x]
27.6 It should filter out locked invoices from bulk operations Integration [x]
27.7 It should show a warning when some selected invoices are locked UI [ ]

Legacy Route Behaviors

# Behavior Test Strategy Status
28.1 It should redirect old SPA routes (/invoices, /invoices/unpaid, etc.) to the new SSR routes Integration [x]

Test Data Requirements

Entity Requirements
Clients Multiple clients with different locations; some with locked-until dates
Vendors With/without default accounts; with/without terms and autopay settings
Accounts Expense accounts with/without invoice allowance; different locations
Bank Accounts Check, cash, and credit types
Invoices Various statuses (unpaid, paid, voided, scheduled), dates, amounts
Payments Linked to invoices; cash and check types
Files Valid CSV, PDF with text, PDF requiring OCR

Existing Tests to Preserve

  • test/clj/auto_ap/ssr/invoice/new_invoice_wizard_test.clj — Location spreading logic
  • test/clj/auto_ap/integration/routes/invoice_test.clj — Import routes
  • test/clj/auto_ap/integration/graphql/invoices.clj — GraphQL invoice operations

Dependencies

  • Datomic (primary store, history for unvoid)
  • AWS S3 (file storage)
  • AWS Textract (OCR)
  • Solr (search for Glimpse matching)
  • HTMX/Alpine.js (frontend interactivity)