test(invoice): implement unit tests for invoice behaviors

Add comprehensive unit tests for pure invoice business logic:
- assert-invoice-amounts-add-up (behaviors 9.4, 11.4)
- does-amount-exceed-outstanding? (behavior 13.4)
- assert-percentages-add-up (behavior 15.3)
- stack-rank and deduplicate (behaviors 24.1, 24.4, 24.5)
- clientize-vendor (behavior 8.4)
- location-select* (behavior 9.3)
- maybe-code-accounts with Shared location spreading (behavior 15.6)
- can-undo-autopayment (behaviors 19.2-19.4)
- due date / scheduled payment calculations (behaviors 8.2, 8.3)
- can-handwrite? and credit-only? (pay wizard behaviors)
- due date display logic (behavior 1.7)

Also fixes:
- user.clj: add missing datomic.api alias (d) used in sample functions
- new_invoice_wizard_test.clj: fix sut8 -> sut9 typo

Marks completed unit-test behaviors with [x] in invoice.md
This commit is contained in:
2026-05-04 21:29:40 -07:00
parent d627e3c5d0
commit da7897c0d6
5 changed files with 511 additions and 130 deletions

View File

@@ -55,7 +55,7 @@ Every mutating operation checks:
| 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 | [ ] |
| 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 | [ ] |
@@ -141,9 +141,9 @@ Every mutating operation checks:
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.1 | It should require client, vendor, date, invoice number, and total | Integration | [ ] |
| 8.2 | It should auto-calculate the due date from vendor terms when client, date, and vendor are selected | Unit | [ ] |
| 8.3 | It should auto-calculate the scheduled payment date from vendor autopay settings | Unit | [ ] |
| 8.4 | It should suggest the vendor's default expense account | Unit | [ ] |
| 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 | [ ] |
| 8.6 | It should allow editing all fields when creating a new invoice | UI | [ ] |
@@ -153,8 +153,8 @@ Every mutating operation checks:
|---|----------|---------------|--------|
| 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 | [ ] |
| 9.4 | It should validate that expense account amounts sum to the invoice total | Unit + Integration | [ ] |
| 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] |
@@ -177,7 +177,7 @@ Every mutating operation checks:
| 11.1 | It should allow editing unpaid and paid invoices | Integration | [ ] |
| 11.2 | It should disable the vendor field when editing | UI | [ ] |
| 11.3 | It should allow modifying expense account amounts, adding/removing accounts | Integration | [ ] |
| 11.4 | It should validate that modified amounts still sum to the total | Unit + Integration | [ ] |
| 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 | [ ] |
@@ -202,7 +202,7 @@ Every mutating operation checks:
| 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 | [ ] |
| 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 | [ ] |
| 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 | [ ] |
| 13.6 | It should block payment if the invoice date is before the client's locked-until date | Integration | [ ] |
| 13.7 | Given the user submits a check payment, when successful, then a PDF download link should be provided | Integration | [ ] |
@@ -223,10 +223,10 @@ Every mutating operation checks:
|---|----------|---------------|--------|
| 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 | [ ] |
| 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 | [ ] |
| 15.5 | It should exclude invoices with dates before the client's locked-until date | Integration | [ ] |
| 15.6 | It should spread "Shared" locations across all client locations, rounding cents correctly | Unit | [ ] |
| 15.6 | It should spread "Shared" locations across all client locations, rounding cents correctly | Unit | [x] |
---
@@ -271,9 +271,9 @@ Every mutating operation checks:
| # | 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 | [ ] |
| 19.2 | It should block undoing autopay for invoices without scheduled payments | Unit + Integration | [ ] |
| 19.3 | It should block undoing autopay for invoices with linked payments | Unit + Integration | [ ] |
| 19.4 | It should block undoing autopay for invoices that are not paid | Unit + Integration | [ ] |
| 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 | [ ] |
---
@@ -324,11 +324,11 @@ Every mutating operation checks:
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 24.1 | It should extract total from AMOUNT_DUE or TOTAL fields | Unit | [ ] |
| 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 | [ ] |
| 24.5 | It should extract invoice number from INVOICE_RECEIPT_ID or PO_NUMBER | Unit | [ ] |
| 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