docs: add comprehensive test behavior documentation for all pages
Add behavior documentation covering all SSR and legacy SPA pages: - Testing strategy and type definitions (unit/integration/UI) - Dashboard, Invoice, Payment, Transaction, Ledger pages - Company/Settings, POS, Admin, Search, Auth pages - Legacy SPA behavior docs (no UI tests until migrated) - Edge cases, test data requirements, and dependencies per subsystem Total: 3,600+ lines of behavior documentation to guide test authorship.
This commit is contained in:
301
docs/testing/behaviors/transaction.md
Normal file
301
docs/testing/behaviors/transaction.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# Transaction Behaviors
|
||||
|
||||
## Overview
|
||||
|
||||
Transactions represent bank account activity imported from external sources (Plaid, Yodlee, Intuit). The SSR transaction pages provide an HTMX-based grid view for browsing, filtering, and exporting transactions, plus an admin insights page for AI-assisted vendor/account coding. The legacy client-side application (`/transactions`) remains the primary interface for day-to-day transaction management; the SSR `transaction2` routes are partially implemented.
|
||||
|
||||
## Routes & Pages
|
||||
|
||||
| Route | Handler | Purpose | Status |
|
||||
|-------|---------|---------|--------|
|
||||
| `GET /transaction2` | `::page` | Main transaction grid with filters | Implemented |
|
||||
| `GET /transaction2/table` | `::table` | HTMX-swappable table body | Implemented |
|
||||
| `GET /transaction2/csv` | `::csv` | CSV export of filtered transactions | Implemented |
|
||||
| `GET /transaction2/bank-account-filter` | `::bank-account-filter` | Dynamic bank account radio cards | Implemented |
|
||||
| `GET /transaction2/new` | `::new` | New transaction form | Route defined, handler not wired |
|
||||
| `POST /transaction2/new` | `::new-submit` | Submit new transaction | Route defined, handler not wired |
|
||||
| `GET /transaction2/external-new` | `::external-page` | External transaction entry | Route defined, handler not wired |
|
||||
| `GET /transaction2/external-import-new` | `::external-import-page` | CSV/text import form | Route defined, handler not wired |
|
||||
| `POST /transaction2/external-import-new/parse` | `::external-import-parse` | Parse import data | Route defined, handler not wired |
|
||||
| `POST /transaction2/external-import-new/import` | `::external-import-import` | Execute import | Route defined, handler not wired |
|
||||
| `GET /transaction/insights` | `:transaction-insights` | Admin AI coding insights page | Implemented |
|
||||
| `GET /transaction/insights/table` | `:transaction-insight-table` | Insights data grid | Implemented |
|
||||
| `GET /transaction/insights/rows/:after` | `:transaction-insight-rows` | Infinite scroll rows | Implemented |
|
||||
| `POST /transaction/insights/code/:id` | `:transaction-insight-code` | Approve & code a transaction | Implemented |
|
||||
| `DELETE /transaction/insights/disapprove/:id` | `:transaction-insight-disapprove` | Reject a recommendation | Implemented |
|
||||
| `GET /transaction/insights/explain/:id` | `:transaction-insight-explain` | Similar transactions modal | Implemented |
|
||||
|
||||
## Behaviors by Page
|
||||
|
||||
### Transaction List
|
||||
|
||||
#### Grid Display
|
||||
- Table displays columns: Client, Vendor, Description, Date, Amount, Links
|
||||
- Client column is hidden when only one client with one location is selected
|
||||
- Description column shows `description-original`; when vendor is missing, vendor column falls back to `description-simple` in italics
|
||||
- Date rendered in `normal-date` format (e.g., `MM/DD/YYYY`)
|
||||
- Amount right-aligned and formatted as `$X,XXX.XX`
|
||||
- Links column shows dropdown with links to associated Payment page or Client Overrides
|
||||
- Table supports checkboxes for bulk selection (`:check-boxes? true`)
|
||||
- When sorted by Vendor, table breaks into grouped sections by vendor name (or "No vendor")
|
||||
- Grid title is "Transaction" and entity name is "register"
|
||||
- Breadcrumb shows "Transactions" linking to the list page
|
||||
|
||||
#### Filters
|
||||
- **Vendor**: Typeahead search against vendor names (fetches from `:vendor-search` endpoint)
|
||||
- **Bank Account**: Radio card selector with "All" plus client's bank accounts; dynamically reloads via `clientSelected` event
|
||||
- **Date Range**: Standard date range picker (start/end dates)
|
||||
- **Description**: Free-text input with 1000ms debounced search on `keyup changed`
|
||||
- **Amount Range**: Two money inputs (gte/lte) with "to" label between them
|
||||
- **Exact Match ID**: Hidden filter rendered as a removable pill/tag when present in query params
|
||||
- All filters trigger table reload via HTMX (`change delay:500ms` or `keyup delay:1000ms`)
|
||||
|
||||
#### Sorting
|
||||
- Sortable columns: Client, Vendor, Date, Amount, Description
|
||||
- Default sort is ascending on the implicit `sort-default` field from `scan-transactions`
|
||||
- Sorting is handled server-side with `add-sorter-fields` generating Datomic query clauses
|
||||
- Vendor sort handles missing vendors by grounding empty string
|
||||
|
||||
#### Pagination
|
||||
- Default 25 rows per page
|
||||
- Pagination controls rendered by grid helper
|
||||
- Total matching count and sum of all matching amounts displayed
|
||||
|
||||
#### Actions
|
||||
- "Add Transaction" button (primary color) links to `::route/new` (not yet implemented)
|
||||
- Row action buttons (edit/delete) are commented out in source
|
||||
|
||||
#### CSV Export
|
||||
- CSV route exports all filtered results (not just current page)
|
||||
- CSV headers: Id, Client, Vendor, Description, Date, Amount
|
||||
- CSV uses raw data values (`:db/id`, `:transaction/amount`, etc.) instead of formatted display
|
||||
|
||||
### New Transaction
|
||||
|
||||
> **Note:** Routes are defined in `auto-ap.routes.transactions` but no handlers are wired in `auto-ap.ssr.transaction`. The "Add Transaction" button in the grid links to this route, which would currently 404. Legacy client-side new transaction functionality exists in the SPA.
|
||||
|
||||
Route definitions include:
|
||||
- `GET /transaction2/new` - New transaction form
|
||||
- `POST /transaction2/new` - Form submission
|
||||
- `GET /transaction2/new/location-select` - Location selector sub-component
|
||||
- `GET /transaction2/new/account-typeahead` - Account typeahead sub-component
|
||||
- `GET /transaction2/new/line-item` - Add line item sub-component
|
||||
|
||||
### External Import
|
||||
|
||||
> **Note:** Routes are defined but handlers are not wired in the SSR transaction namespace. The ledger namespace (`auto-ap.ssr.ledger`) has a fully implemented external import flow that these routes may mirror.
|
||||
|
||||
Route definitions include:
|
||||
- `GET /transaction2/external-new` - External transaction entry page
|
||||
- `GET /transaction2/external-import-new` - Import form (CSV/text paste)
|
||||
- `POST /transaction2/external-import-new/parse` - Parse pasted data
|
||||
- `POST /transaction2/external-import-new/import` - Execute import
|
||||
|
||||
The import system (from `auto-ap.import.transactions`) supports:
|
||||
- Deduplication via SHA-256 synthetic keys
|
||||
- Auto-matching to existing pending payments by check number or amount
|
||||
- Auto-matching to expected deposits
|
||||
- Auto-coding via transaction rules
|
||||
- Categorization: `:import`, `:extant`, `:suppressed`, `:error`, `:not-ready`
|
||||
|
||||
### Admin Insights / Coding
|
||||
|
||||
#### Insights Page
|
||||
- Admin-only page at `/transaction/insights`
|
||||
- Title: "Transaction Insights"
|
||||
- Breadcrumbs: Transactions > Insights
|
||||
- Displays data grid card with title "Transaction Insights"
|
||||
- Grid headers: Client, Account, Date, Description, Amount, Actions
|
||||
- No pagination; shows up to 50 recommendations at a time
|
||||
- Infinite scroll via `hx-trigger="intersect once"` on last row
|
||||
- When no more recommendations, shows "That's the last of 'em!"
|
||||
|
||||
#### Recommendation Rows
|
||||
- Shows unapproved transactions from last 300 days that have `outcome-recommendation` data
|
||||
- Each row displays: client code, bank account code, date, description, amount
|
||||
- Amount displayed as rounded dollar tag (green for positive, red for negative)
|
||||
- Up to 3 recommendation buttons per row, sorted by frequency (highest count first)
|
||||
- Each button shows: `Vendor Name | Account Name` with a count badge
|
||||
- Button color is `:primary` if the recommendation was seen by client, `:secondary` otherwise
|
||||
|
||||
#### Coding Actions
|
||||
- **Approve (Code)**: `POST /transaction/insights/code/:id`
|
||||
- Sets approval status to `approved`
|
||||
- Assigns vendor and account
|
||||
- Distributes amount across valid locations using `spread-cents`
|
||||
- Row re-renders with `live-added` class and Alpine.js disappear animation
|
||||
- **Reject (Disapprove)**: `DELETE /transaction/insights/disapprove/:id`
|
||||
- Clears `outcome-recommendation` on transaction
|
||||
- Row re-renders with `live-removed` class and disappear animation
|
||||
- **Explain**: `GET /transaction/insights/explain/:id`
|
||||
- Opens modal showing similar transactions from Pinecone vector search
|
||||
- Displays: Date, Description, Amount, Vendor, Account, Similarity Score
|
||||
- Similarity threshold: score > 0.95
|
||||
- Shows top 10 similar transactions plus the target transaction highlighted
|
||||
|
||||
#### Pinecone Integration
|
||||
- Fetches vector embedding for transaction ID from Pinecone index
|
||||
- Queries for top 100 similar vectors
|
||||
- Filters to scores > 0.95
|
||||
- Enriches matches with vendor name and account numeric code from Datomic
|
||||
|
||||
## Cross-Cutting Behaviors
|
||||
|
||||
### Approval Workflow
|
||||
- Approval statuses: `unapproved`, `approved`, `requires-feedback`, `excluded`, `suppressed`
|
||||
- Transactions start as `unapproved` on import
|
||||
- `suppressed` transactions are excluded from all list queries (including GraphQL)
|
||||
- `requires-feedback` appears in dashboard tasks card
|
||||
- Bulk status changes available via GraphQL mutation `bulk_change_transaction_status` (admin only)
|
||||
- Locked transactions (before `client/locked-until` or `bank-account/start-date`) cannot be modified
|
||||
|
||||
### Coding Transactions to Accounts
|
||||
- Transactions can be coded with one or more expense accounts
|
||||
- Account total must equal 100% of transaction amount (validated server-side)
|
||||
- Location must match account's fixed location if one is set
|
||||
- Location "Shared" distributes amount proportionally across all client locations
|
||||
- Location "A" is reserved for liabilities/equities/assets
|
||||
- Bulk coding available via GraphQL mutation `bulk_code_transactions` (admin only)
|
||||
|
||||
### Bank Account Filtering
|
||||
- Bank account filter only appears when a client is selected
|
||||
- Filter dynamically updates via HTMX when `clientSelected` event fires
|
||||
- Validates that selected bank account belongs to current client (`wrap-ensure-bank-account-belongs`)
|
||||
- Defaults to "All" if selected account doesn't belong to current client
|
||||
|
||||
### CSV Export
|
||||
- CSV route uses same filters as table view
|
||||
- Exports all matching rows (bypasses pagination)
|
||||
- Includes ID column in CSV but not in HTML view
|
||||
|
||||
### Payments & Linking
|
||||
- Transactions can be linked to payments (auto-matched on import by check number or amount)
|
||||
- Linking creates a cleared payment and sets transaction to `approved` with Accounts Payable account
|
||||
- Unlinking a transaction reverts it to `unapproved` and clears payment/accounts
|
||||
- Autopay invoices: transaction can pay multiple invoices, creating a payment that clears them all
|
||||
- Unpaid invoices: similar flow for outstanding balances
|
||||
|
||||
### Permissions
|
||||
- View transactions: `:activity :view :subject :transaction`
|
||||
- Insights page: `:activity :insights :subject :transaction`
|
||||
- Bulk status change: admin only (`assert-admin`)
|
||||
- Bulk coding: admin only
|
||||
- Edit transaction: power user, with client visibility check
|
||||
- Match/unlink transactions: power user
|
||||
- All SSR routes redirect unauthenticated users to `/login`
|
||||
|
||||
### Import Processing
|
||||
- Transactions imported with `transaction->txs` pipeline:
|
||||
1. Assign client and bank account
|
||||
2. Set initial status to `unapproved`
|
||||
3. Extract check number from description if present
|
||||
4. Attempt auto-match to pending payment
|
||||
5. Attempt auto-match to expected deposit
|
||||
6. Apply transaction rules for auto-coding
|
||||
7. Apply default vendor if set
|
||||
- Deduplication via SHA-256 of `date-bank-account-description-amount-index-client`
|
||||
- Suppressed transactions are skipped on re-import
|
||||
|
||||
## Edge Cases
|
||||
|
||||
### Empty Filter Results
|
||||
- Table renders empty state when no transactions match filters
|
||||
- Sum amount shows `$0.00`
|
||||
- Pagination controls still render but show 0 results
|
||||
|
||||
### Invalid Bank Account Selection
|
||||
- If user selects a bank account then switches to a different client that doesn't own that account, filter resets to "All"
|
||||
- `wrap-ensure-bank-account-belongs` middleware enforces this
|
||||
|
||||
### Exact Match ID
|
||||
- When `exact-match-id` is present, all other filters are ignored
|
||||
- Query directly pulls the single transaction by ID (must belong to a visible client)
|
||||
- Renders as a removable pill in the filter area
|
||||
|
||||
### No Client Selected
|
||||
- Bank account filter area renders empty (no radio cards)
|
||||
- All other filters still function
|
||||
- Query uses all visible clients
|
||||
|
||||
### Insights with No Recommendations
|
||||
- Insights page shows empty grid when no unapproved transactions have recommendations
|
||||
- "That's the last of 'em!" message appears at bottom when infinite scroll exhausts results
|
||||
|
||||
### Disappearing Recommendations
|
||||
- After coding or rejecting, the row animates out via Alpine.js (`x-data` with `mount-then-disappear`)
|
||||
- The table does not auto-refresh; user must reload page to see new recommendations
|
||||
|
||||
### Pinecone Unavailable
|
||||
- If Pinecone API fails, the explain modal will show only the target transaction with no similar matches
|
||||
- No explicit error handling for Pinecone HTTP failures
|
||||
|
||||
## Test Data Requirements
|
||||
|
||||
### Users
|
||||
- Admin user with `:user/role "admin"`
|
||||
- Power user with access to specific clients
|
||||
- Regular user with client visibility restrictions
|
||||
- Unauthenticated user (for redirect tests)
|
||||
|
||||
### Clients
|
||||
- Minimum 2 clients with different locations
|
||||
- Client with multiple bank accounts
|
||||
- Client with single location (to test client column hiding)
|
||||
|
||||
### Bank Accounts
|
||||
- Bank account with `bank-account/name` and `bank-account/numeric-code`
|
||||
- Bank account with `bank-account/start-date` (to test locked transactions)
|
||||
- Multiple bank accounts under same client
|
||||
|
||||
### Transactions
|
||||
- Transactions with all approval statuses
|
||||
- Transactions with and without vendors
|
||||
- Transactions with positive and negative amounts
|
||||
- Transactions linked to payments
|
||||
- Transactions with `outcome-recommendation` (for insights)
|
||||
- Transactions with `exact-match-id` parameter
|
||||
- Transactions dated before and after `client/locked-until`
|
||||
|
||||
### Vendors
|
||||
- Vendor with name for typeahead matching
|
||||
- Vendor linked to transactions
|
||||
|
||||
### Accounts
|
||||
- Account with fixed location
|
||||
- Account without fixed location
|
||||
- Account with numeric code (for insights display)
|
||||
|
||||
### Payments
|
||||
- Pending payment with matching check number and amount
|
||||
- Cleared payment linked to transaction
|
||||
|
||||
## Dependencies
|
||||
|
||||
### External Services
|
||||
- **Datomic**: All transaction data stored and queried via Datomic
|
||||
- **Pinecone**: Vector similarity search for transaction insights (explain feature)
|
||||
- **Solr**: Index updated on transaction changes (`solr/touch-with-ledger`)
|
||||
|
||||
### Frontend Libraries
|
||||
- **HTMX**: All SSR interactions (filtering, sorting, pagination, insights coding)
|
||||
- **Alpine.js**: Filter state (exact match pill), row disappear animations
|
||||
- **Chart.js**: Not used on transaction pages
|
||||
|
||||
### Middleware Stack
|
||||
- `wrap-must {:activity :view :subject :transaction}`: Enforces view permission
|
||||
- `wrap-client-redirect-unauthenticated`: Auth redirects
|
||||
- `wrap-schema-enforce`: Validates query params against Malli schema
|
||||
- `wrap-apply-sort`: Applies server-side sorting
|
||||
- `wrap-ensure-bank-account-belongs`: Validates bank account filter
|
||||
- `wrap-copy-qp-pqp`: Copies query params to parsed query params
|
||||
|
||||
### GraphQL Mutations (Legacy/Client-Side)
|
||||
- `bulk_change_transaction_status`: Admin bulk approval status changes
|
||||
- `bulk_code_transactions`: Admin bulk coding with vendor/accounts
|
||||
- `edit_transaction`: Single transaction edit
|
||||
- `match_transaction`: Link to existing payment
|
||||
- `match_transaction_autopay_invoices`: Create payment for autopay invoices
|
||||
- `match_transaction_unpaid_invoices`: Create payment for unpaid invoices
|
||||
- `unlink_transaction`: Remove payment link
|
||||
- `delete_transactions`: Soft delete (suppress) or hard delete
|
||||
- `match_transaction_rules`: Apply transaction rules to matching transactions
|
||||
Reference in New Issue
Block a user