refactor(all): rewrite all behavior docs in table format with checkboxes
Rewrite all 11 remaining behavior documents to match the streamlined invoice.md format: - dashboard.md: 250 lines, 62 behaviors - payment.md: 260 lines, behaviors for list, void, check printing, ACH - transaction.md: 310 lines, list, import, admin insights - ledger.md: 519 lines, entries, P&L, balance sheet, cash flows - company.md: 320 lines, profile, 1099s, Plaid/Yodlee, reports - admin.md: 494 lines, clients, accounts, vendors, rules, jobs, history - pos.md: 405 lines, sales, deposits, tenders, refunds, shifts - search-indicators.md: 167 lines, search modal, indicators - auth.md: 184 lines, login, logout, impersonation, sessions - outgoing-invoice.md: 192 lines, create, line items, PDF - legacy-spa.md: 340 lines, all legacy pages (docs only) All documents now use: - Testing Patterns section with reusable abstractions - Numbered tables: # | Behavior | Test Strategy | Status - It should... behavior descriptions - Checkboxes [ ]/[x] for tracking implementation - Cross-Cutting Behaviors for permissions, lock dates, etc. - Test Data Requirements tables - Existing Tests to Preserve sections Total: 3,844 lines of behavior documentation across 12 subsystem docs.
This commit is contained in:
@@ -2,300 +2,309 @@
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
## Routes & Pages
|
||||
**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
|
||||
|
||||
| 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
|
||||
## Testing Patterns
|
||||
|
||||
### Transaction List
|
||||
### 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
|
||||
|
||||
#### 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
|
||||
**Test implications:** Validate the query generation, not the rendering. UI tests only need to verify one filter and one sort work end-to-end.
|
||||
|
||||
#### 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`)
|
||||
### Pattern: Admin Insights Behaviors
|
||||
Insights pages display AI recommendations for coding transactions:
|
||||
1. Fetch unapproved transactions with `outcome-recommendation` data
|
||||
2. Display recommendation buttons sorted by frequency
|
||||
3. Allow approving (coding) or rejecting recommendations inline
|
||||
4. Use infinite scroll instead of pagination
|
||||
|
||||
#### 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
|
||||
**Test implications:** Unit test the recommendation sorting and filtering logic. Integration test the approve/reject endpoints. UI test the infinite scroll and animation behaviors.
|
||||
|
||||
#### Pagination
|
||||
- Default 25 rows per page
|
||||
- Pagination controls rendered by grid helper
|
||||
- Total matching count and sum of all matching amounts displayed
|
||||
### Pattern: Permission Gates
|
||||
Every mutating operation checks:
|
||||
1. `assert-can-see-client` — user has access to the client
|
||||
2. `assert-not-locked` — transaction date >= client locked-until or bank account start-date
|
||||
3. `can?` — user has the specific permission for the activity
|
||||
|
||||
#### Actions
|
||||
- "Add Transaction" button (primary color) links to `::route/new` (not yet implemented)
|
||||
- Row action buttons (edit/delete) are commented out in source
|
||||
**Test implications:** Integration test each gate independently. UI tests only verify the happy path with a permitted user.
|
||||
|
||||
#### 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
|
||||
## Transaction List Page
|
||||
|
||||
### Display Behaviors
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 1.1 | It should display a table with columns: Client, Vendor, Description, Date, Amount, Links | UI | [ ] |
|
||||
| 1.2 | It should hide the Client column when only one client with one location is selected | Integration | [ ] |
|
||||
| 1.3 | It should display the description from `description-original` in the Description column | UI | [ ] |
|
||||
| 1.4 | It should fall back to `description-simple` in italics in the Vendor column when no vendor is assigned | UI | [ ] |
|
||||
| 1.5 | It should render dates in `MM/DD/YYYY` format | UI | [ ] |
|
||||
| 1.6 | It should right-align amounts and format them as `$X,XXX.XX` | UI | [ ] |
|
||||
| 1.7 | It should display a links dropdown with links to associated Payment page or Client Overrides | UI | [ ] |
|
||||
| 1.8 | It should show checkboxes for bulk selection on each row | UI | [ ] |
|
||||
| 1.9 | It should group table rows by vendor name (or "No vendor") when sorted by Vendor | Integration | [ ] |
|
||||
| 1.10 | It should show the grid title "Transaction" and entity name "register" | UI | [ ] |
|
||||
| 1.11 | It should display a breadcrumb showing "Transactions" linking to the list page | UI | [ ] |
|
||||
|
||||
### Filtering Behaviors
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 2.1 | It should filter transactions by vendor typeahead selection | Integration | [ ] |
|
||||
| 2.2 | It should filter transactions by bank account via radio card selector with "All" plus client's bank accounts | Integration | [ ] |
|
||||
| 2.3 | It should dynamically reload the bank account filter when the `clientSelected` event fires | Integration | [ ] |
|
||||
| 2.4 | It should filter transactions by date range (start/end dates) | Integration | [ ] |
|
||||
| 2.5 | It should filter transactions by description with 1000ms debounced search | Integration | [ ] |
|
||||
| 2.6 | It should filter transactions by amount range (min/max) | Integration | [ ] |
|
||||
| 2.7 | It should support exact-match navigation to a specific transaction by ID, bypassing other filters | Integration | [ ] |
|
||||
| 2.8 | It should render the exact-match ID as a removable pill when present in query params | UI | [ ] |
|
||||
| 2.9 | Given multiple filters are applied, when the user changes one filter, then the table should refresh with the combined filter set | Integration | [ ] |
|
||||
|
||||
### Sorting Behaviors
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 3.1 | It should sort by client name ascending/descending | Integration | [ ] |
|
||||
| 3.2 | It should sort by vendor name ascending/descending, handling missing vendors by grounding empty string | Integration | [ ] |
|
||||
| 3.3 | It should sort by description ascending/descending | Integration | [ ] |
|
||||
| 3.4 | It should sort by date ascending/descending | Integration | [ ] |
|
||||
| 3.5 | It should sort by amount ascending/descending | Integration | [ ] |
|
||||
| 3.6 | Given the user clicks a column header twice, then the sort direction should toggle | Integration | [ ] |
|
||||
| 3.7 | It should default to ascending sort on the implicit `sort-default` field | Integration | [ ] |
|
||||
|
||||
### Pagination Behaviors
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 4.1 | It should display 25 transactions per page by default | Integration | [ ] |
|
||||
| 4.2 | It should display the total matching count and sum of all matching amounts | Integration | [ ] |
|
||||
| 4.3 | It should render pagination controls via the grid helper | UI | [ ] |
|
||||
|
||||
### Action Behaviors
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 5.1 | It should display an "Add Transaction" button (primary color) linking to the new transaction route | UI | [ ] |
|
||||
|
||||
### CSV Export Behaviors
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 6.1 | It should export all filtered results (not just the current page) as CSV | Integration | [ ] |
|
||||
| 6.2 | It should include CSV headers: Id, Client, Vendor, Description, Date, Amount | Integration | [ ] |
|
||||
| 6.3 | It should use raw data values instead of formatted display values in the CSV | Integration | [ ] |
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
### Basic Details
|
||||
|
||||
### External Import
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 7.1 | It should display a new transaction form at `GET /transaction2/new` | UI | [ ] |
|
||||
| 7.2 | It should allow selecting a location via location selector sub-component | UI | [ ] |
|
||||
| 7.3 | It should allow selecting an account via account typeahead sub-component | UI | [ ] |
|
||||
| 7.4 | It should allow adding line items via line item sub-component | UI | [ ] |
|
||||
| 7.5 | It should submit the new transaction via `POST /transaction2/new` | Integration | [ ] |
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
### External Transaction Entry
|
||||
|
||||
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`
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 8.1 | It should display an external transaction entry page at `GET /transaction2/external-new` | UI | [ ] |
|
||||
|
||||
### Admin Insights / Coding
|
||||
### CSV/Text Import
|
||||
|
||||
#### 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!"
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 8.2 | It should display an import form for CSV/text paste at `GET /transaction2/external-import-new` | UI | [ ] |
|
||||
| 8.3 | It should parse pasted data via `POST /transaction2/external-import-new/parse` | Integration | [ ] |
|
||||
| 8.4 | It should execute the import via `POST /transaction2/external-import-new/import` | Integration | [ ] |
|
||||
| 8.5 | It should deduplicate transactions via SHA-256 synthetic keys during import | Unit | [ ] |
|
||||
| 8.6 | It should auto-match imported transactions to existing pending payments by check number or amount | Integration | [ ] |
|
||||
| 8.7 | It should auto-match imported transactions to expected deposits | Integration | [ ] |
|
||||
| 8.8 | It should auto-code imported transactions via transaction rules | Integration | [ ] |
|
||||
| 8.9 | It should categorize imports as `:import`, `:extant`, `:suppressed`, `:error`, or `:not-ready` | Integration | [ ] |
|
||||
|
||||
#### 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
|
||||
## Admin Insights / Coding
|
||||
|
||||
#### 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
|
||||
### Insights Page Display
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 9.1 | It should display the insights page at `/transaction/insights` for admin users | UI | [ ] |
|
||||
| 9.2 | It should show the title "Transaction Insights" and breadcrumbs: Transactions > Insights | UI | [ ] |
|
||||
| 9.3 | It should display a data grid with headers: Client, Account, Date, Description, Amount, Actions | UI | [ ] |
|
||||
| 9.4 | It should show up to 50 recommendations at a time with no pagination | Integration | [ ] |
|
||||
| 9.5 | It should implement infinite scroll via `hx-trigger="intersect once"` on the last row | UI | [ ] |
|
||||
| 9.6 | It should display "That's the last of 'em!" when no more recommendations are available | UI | [ ] |
|
||||
| 9.7 | It should show an empty grid when no unapproved transactions have recommendations | UI | [ ] |
|
||||
|
||||
### Recommendation Rows
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 10.1 | It should show unapproved transactions from the last 300 days that have `outcome-recommendation` data | Integration | [ ] |
|
||||
| 10.2 | It should display each row with: client code, bank account code, date, description, amount | UI | [ ] |
|
||||
| 10.3 | It should display the amount as a rounded dollar tag (green for positive, red for negative) | UI | [ ] |
|
||||
| 10.4 | It should show up to 3 recommendation buttons per row, sorted by frequency (highest count first) | Integration | [ ] |
|
||||
| 10.5 | It should display each recommendation button as `Vendor Name | Account Name` with a count badge | UI | [ ] |
|
||||
| 10.6 | It should render recommendation buttons in `:primary` color if seen by client, `:secondary` otherwise | UI | [ ] |
|
||||
|
||||
### Coding Actions
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 11.1 | It should approve and code a transaction via `POST /transaction/insights/code/:id` | Integration | [ ] |
|
||||
| 11.2 | It should set the approval status to `approved` and assign vendor and account when coding | Integration | [ ] |
|
||||
| 11.3 | It should distribute the amount across valid locations using `spread-cents` when coding | Unit | [ ] |
|
||||
| 11.4 | It should re-render the row with `live-added` class and Alpine.js disappear animation after coding | UI | [ ] |
|
||||
| 11.5 | It should reject a recommendation via `DELETE /transaction/insights/disapprove/:id` | Integration | [ ] |
|
||||
| 11.6 | It should clear `outcome-recommendation` on the transaction when rejecting | Integration | [ ] |
|
||||
| 11.7 | It should re-render the row with `live-removed` class and disappear animation after rejecting | UI | [ ] |
|
||||
| 11.8 | It should open an explain modal via `GET /transaction/insights/explain/:id` | UI | [ ] |
|
||||
| 11.9 | It should display similar transactions from Pinecone vector search in the explain modal | Integration | [ ] |
|
||||
| 11.10 | It should display Date, Description, Amount, Vendor, Account, and Similarity Score for similar transactions | UI | [ ] |
|
||||
| 11.11 | It should filter similar transactions to scores > 0.95 | Unit | [ ] |
|
||||
| 11.12 | It should show the top 10 similar transactions plus the target transaction highlighted | UI | [ ] |
|
||||
| 11.13 | It should show only the target transaction with no similar matches if Pinecone API fails | Integration | [ ] |
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
### Approval Workflow Behaviors
|
||||
|
||||
### 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)
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 12.1 | It should set transactions to `unapproved` status on import | Integration | [ ] |
|
||||
| 12.2 | It should exclude `suppressed` transactions from all list queries including GraphQL | Integration | [ ] |
|
||||
| 12.3 | It should show `requires-feedback` transactions in the dashboard tasks card | Integration | [ ] |
|
||||
| 12.4 | It should allow admin-only bulk status changes via GraphQL mutation `bulk_change_transaction_status` | Integration | [ ] |
|
||||
| 12.5 | It should block modifying locked transactions (before `client/locked-until` or `bank-account/start-date`) | Integration | [ ] |
|
||||
|
||||
### 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
|
||||
### Coding Behaviors
|
||||
|
||||
### 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
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 13.1 | It should allow coding transactions with one or more expense accounts | Integration | [ ] |
|
||||
| 13.2 | It should validate that account totals equal 100% of the transaction amount server-side | Unit + Integration | [ ] |
|
||||
| 13.3 | It should require the location to match the account's fixed location if one is set | Integration | [ ] |
|
||||
| 13.4 | It should distribute amounts proportionally across all client locations when location is "Shared" | Unit | [ ] |
|
||||
| 13.5 | It should reserve location "A" for liabilities/equities/assets | Integration | [ ] |
|
||||
| 13.6 | It should allow admin-only bulk coding via GraphQL mutation `bulk_code_transactions` | Integration | [ ] |
|
||||
|
||||
### 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
|
||||
### Bank Account Filtering Behaviors
|
||||
|
||||
### 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`
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 14.1 | It should show the bank account filter only when a client is selected | UI | [ ] |
|
||||
| 14.2 | It should dynamically update the bank account filter via HTMX when `clientSelected` event fires | Integration | [ ] |
|
||||
| 14.3 | It should validate that the selected bank account belongs to the current client | Integration | [ ] |
|
||||
| 14.4 | It should default to "All" if the selected account doesn't belong to the current client | Integration | [ ] |
|
||||
|
||||
### 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
|
||||
### CSV Export Behaviors
|
||||
|
||||
## Edge Cases
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 15.1 | It should use the same filters for CSV export as the table view | Integration | [ ] |
|
||||
| 15.2 | It should export all matching rows bypassing pagination | Integration | [ ] |
|
||||
| 15.3 | It should include the ID column in CSV but not in the HTML view | Integration | [ ] |
|
||||
|
||||
### 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
|
||||
### Payments & Linking Behaviors
|
||||
|
||||
### 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
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 16.1 | It should auto-match transactions to payments by check number or amount on import | Integration | [ ] |
|
||||
| 16.2 | It should create a cleared payment and set the transaction to `approved` with Accounts Payable account when linking | Integration | [ ] |
|
||||
| 16.3 | It should revert the transaction to `unapproved` and clear payment/accounts when unlinking | Integration | [ ] |
|
||||
| 16.4 | It should allow a transaction to pay multiple autopay invoices, creating a payment that clears them all | Integration | [ ] |
|
||||
| 16.5 | It should allow a transaction to pay multiple unpaid invoices for outstanding balances | Integration | [ ] |
|
||||
|
||||
### 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
|
||||
### Permission Behaviors
|
||||
|
||||
### No Client Selected
|
||||
- Bank account filter area renders empty (no radio cards)
|
||||
- All other filters still function
|
||||
- Query uses all visible clients
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 17.1 | It should require `:activity :view :subject :transaction` permission to view transactions | Integration | [ ] |
|
||||
| 17.2 | It should require `:activity :insights :subject :transaction` permission to access the insights page | Integration | [ ] |
|
||||
| 17.3 | It should restrict bulk status changes to admin only | Integration | [ ] |
|
||||
| 17.4 | It should restrict bulk coding to admin only | Integration | [ ] |
|
||||
| 17.5 | It should require power user role with client visibility check to edit transactions | Integration | [ ] |
|
||||
| 17.6 | It should require power user role to match/unlink transactions | Integration | [ ] |
|
||||
| 17.7 | It should redirect unauthenticated users to `/login` for all SSR routes | Integration | [ ] |
|
||||
|
||||
### 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
|
||||
### Import Processing Behaviors
|
||||
|
||||
### 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
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 18.1 | It should assign client and bank account during import | Integration | [ ] |
|
||||
| 18.2 | It should set initial status to `unapproved` on import | Integration | [ ] |
|
||||
| 18.3 | It should extract check number from description if present during import | Unit | [ ] |
|
||||
| 18.4 | It should attempt auto-match to pending payment during import | Integration | [ ] |
|
||||
| 18.5 | It should attempt auto-match to expected deposit during import | Integration | [ ] |
|
||||
| 18.6 | It should apply transaction rules for auto-coding during import | Integration | [ ] |
|
||||
| 18.7 | It should apply default vendor if set during import | Integration | [ ] |
|
||||
| 18.8 | It should deduplicate via SHA-256 of `date-bank-account-description-amount-index-client` | Unit | [ ] |
|
||||
| 18.9 | It should skip suppressed transactions on re-import | Integration | [ ] |
|
||||
|
||||
### 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
|
||||
### Empty State Behaviors
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 19.1 | It should render an empty state when no transactions match filters | UI | [ ] |
|
||||
| 19.2 | It should show `$0.00` for sum amount when no transactions match | UI | [ ] |
|
||||
| 19.3 | It should render pagination controls showing 0 results when no transactions match | UI | [ ] |
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
| Entity | Requirements |
|
||||
|--------|-------------|
|
||||
| **Users** | Admin user with `:user/role "admin"`; power user with access to specific clients; regular user with client visibility restrictions; unauthenticated user |
|
||||
| **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 (`unapproved`, `approved`, `requires-feedback`, `excluded`, `suppressed`); 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 |
|
||||
|
||||
### Clients
|
||||
- Minimum 2 clients with different locations
|
||||
- Client with multiple bank accounts
|
||||
- Client with single location (to test client column hiding)
|
||||
## Existing Tests to Preserve
|
||||
|
||||
### 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
|
||||
- `test/clj/auto_ap/ssr/transaction_test.clj` — Transaction SSR routes and behaviors
|
||||
- `test/clj/auto_ap/integration/routes/transaction_test.clj` — Transaction import routes
|
||||
- `test/clj/auto_ap/integration/graphql/transactions.clj` — GraphQL transaction operations
|
||||
|
||||
## 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
|
||||
- Datomic (primary store, history for unvoid)
|
||||
- Pinecone (vector similarity search for transaction insights explain feature)
|
||||
- Solr (index updated on transaction changes via `solr/touch-with-ledger`)
|
||||
- HTMX (all SSR interactions: filtering, sorting, pagination, insights coding)
|
||||
- Alpine.js (filter state for exact match pill, row disappear animations)
|
||||
|
||||
Reference in New Issue
Block a user