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,526 +2,319 @@
|
||||
|
||||
## Overview
|
||||
|
||||
These pages are rendered client-side via Reagent/Re-frame. They use GraphQL for data fetching. They are being migrated to HTMX SSR. Behavior docs exist for reference but **NO UI tests should be written for these pages until migrated**.
|
||||
These pages are rendered client-side via Reagent/Re-frame and use GraphQL for data fetching. They are being migrated to HTMX SSR. **No UI tests should be written for these pages until migrated.**
|
||||
|
||||
### Architecture
|
||||
- **Routing**: Bidi client-side routes (`src/cljc/auto_ap/client_routes.cljc`)
|
||||
- **State**: Re-frame subscriptions and events
|
||||
- **Data**: GraphQL via custom `graphql` effect (`auto-ap.effects`)
|
||||
- **Permissions**: `auto-ap.permissions/can?` with role-based checks
|
||||
|
||||
### Role Permissions Summary
|
||||
| Role | Transaction Page | Ledger Page | Vendor Create/Edit |
|
||||
|------|-----------------|-------------|-------------------|
|
||||
| admin | full | full | full |
|
||||
| power-user | full | full | full |
|
||||
| manager | full | blocked | full |
|
||||
| user | full | full | full |
|
||||
| read-only | blocked | full | blocked |
|
||||
**Testing Philosophy**
|
||||
- Prefer unit tests for pure business logic (calculations, validations, transformations)
|
||||
- Use integration tests for GraphQL queries, mutations, and data flows
|
||||
- Use UI tests only after migration to SSR; until then, mark as "UI (when migrated)"
|
||||
- Every behavior must be user-visible; no tests for implementation details
|
||||
|
||||
---
|
||||
|
||||
## Pages
|
||||
## Testing Patterns
|
||||
|
||||
### Home (`/`)
|
||||
### Pattern: GraphQL Data Fetching
|
||||
All data fetching uses re-frame `graphql` effect with JWT token from `:user` in app-db:
|
||||
1. Queries use `owns-state` to track loading status per page
|
||||
2. Results flow through `::data-page/received` event which stores data and syncs URL query params
|
||||
3. Filter changes are debounced (800ms) via `dispatch-debounce`
|
||||
|
||||
#### Purpose
|
||||
Dashboard showing financial overview for the selected client: top expense categories (pie chart), upcoming bills (bar chart), and cash flow projection (interactive bar chart with table).
|
||||
**Test implications:** Integration test the GraphQL query resolution and response shape. Do not test the re-frame effect machinery.
|
||||
|
||||
#### User Flows
|
||||
1. User lands on `/`
|
||||
2. Page loads data for currently selected client
|
||||
3. If user has access to multiple clients, shows note: "these reports are for [client]. Please choose a specific customer for their report."
|
||||
4. User can switch cash flow range: 7/30/60/90/120/150/180 days
|
||||
5. Cash flow bars are clickable and redirect to unpaid invoices for that date
|
||||
### Pattern: Re-frame State Management
|
||||
- **Data pages**: `data-page` namespace provides reusable pagination/filtering state
|
||||
- `::data-page/params` — merged filters + table params + query params
|
||||
- `::data-page/data` — GraphQL response data
|
||||
- `::data-page/checked` — selected rows for bulk operations
|
||||
- **Forms**: `forms` namespace manages edit dialogs with `start-form`, `change-handler`, `save-succeeded`
|
||||
- **Status**: `status` namespace tracks async operation states (`:loading`, `:complete`, `:error`)
|
||||
|
||||
#### GraphQL Queries Used
|
||||
```graphql
|
||||
query HomeDashboard($clientId: id) {
|
||||
expense_account_stats(client_id: $clientId) {
|
||||
account { id name }
|
||||
total
|
||||
}
|
||||
invoice_stats(client_id: $clientId) {
|
||||
name
|
||||
paid
|
||||
unpaid
|
||||
}
|
||||
cash_flow(client_id: $clientId) {
|
||||
beginning_balance
|
||||
outstanding_payments
|
||||
invoices_due_soon { due outstanding_balance invoice_number vendor { id name } }
|
||||
upcoming_credits { date amount identifier }
|
||||
upcoming_debits { date amount identifier }
|
||||
}
|
||||
}
|
||||
```
|
||||
**Test implications:** Test via integration tests that verify the correct data appears after state transitions. Do not test subscription internals.
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
- **Happy path**: Dashboard loads with all three chart sections
|
||||
- **Multi-client note**: Displays when user hasn't selected a specific client
|
||||
- **Cash flow ranges**: Switching between 7-180 day views updates chart and table
|
||||
- **Cash flow table**: Shows invoices, upcoming debits/credits with days-until
|
||||
- **Edge case**: No data for client shows empty charts gracefully
|
||||
- **Error states**: GraphQL errors show loading state appropriately
|
||||
### Pattern: Client-Side Routing
|
||||
- Navbar uses Bidi `path-for` with `routes/routes` for legacy SPA links
|
||||
- Some navbar items (Payments, POS, Invoices) now link to `ssr-routes/only-routes` instead
|
||||
- Page components dispatch `::mounted` on mount and `::unmounted` on unmount
|
||||
|
||||
**Test implications:** After migration, integration test route redirects from old SPA routes to new SSR routes.
|
||||
|
||||
---
|
||||
|
||||
### Login (`/login`)
|
||||
## Home
|
||||
|
||||
#### Purpose
|
||||
Authentication page with Google OAuth login.
|
||||
### Dashboard Display
|
||||
|
||||
#### User Flows
|
||||
1. User visits `/login`
|
||||
2. Shows "Login with Google" button
|
||||
3. Button links to Google OAuth with optional `redirect-to` query param
|
||||
4. After auth failure/logout, may show logout reason notification
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
None.
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
- **Happy path**: Shows login button linking to Google OAuth
|
||||
- **Redirect**: Preserves `redirect-to` query parameter in OAuth URL
|
||||
- **Logout reason**: Displays warning notification when `logout-reason` is set
|
||||
- **Edge case**: Already authenticated user visiting login page
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 1.1 | It should display a dashboard with three chart sections: top expense categories, upcoming bills, and cash flow projection | UI (when migrated) | [ ] |
|
||||
| 1.2 | It should load data for the currently selected client on page load | Integration | [ ] |
|
||||
| 1.3 | It should display a note: "these reports are for [client]. Please choose a specific customer for their report." when the user has access to multiple clients and has not selected a specific one | UI (when migrated) | [ ] |
|
||||
| 1.4 | It should display an interactive bar chart for cash flow projection | UI (when migrated) | [ ] |
|
||||
| 1.5 | It should display a table below the cash flow chart showing invoices, upcoming debits, and upcoming credits with days-until due | UI (when migrated) | [ ] |
|
||||
| 1.6 | It should allow switching the cash flow range between 7, 30, 60, 90, 120, 150, and 180 days | UI (when migrated) | [ ] |
|
||||
| 1.7 | Given the user switches the cash flow range, then the chart and table should update to reflect the selected range | Integration | [ ] |
|
||||
| 1.8 | Given the user clicks a cash flow bar, then it should redirect to the unpaid invoices page for that date | UI (when migrated) | [ ] |
|
||||
| 1.9 | It should show empty charts gracefully when there is no data for the selected client | UI (when migrated) | [ ] |
|
||||
| 1.10 | It should show a loading state while GraphQL data is being fetched | UI (when migrated) | [ ] |
|
||||
| 1.11 | It should handle GraphQL errors by showing the loading state appropriately | UI (when migrated) | [ ] |
|
||||
|
||||
---
|
||||
|
||||
### Needs Activation (`/needs-activation`)
|
||||
## Login
|
||||
|
||||
#### Purpose
|
||||
Shown when authenticated user's account is not yet activated.
|
||||
### Authentication
|
||||
|
||||
#### User Flows
|
||||
1. User logs in but account is inactive
|
||||
2. Shows message: "Sorry, your user is not activated yet. Please have Ben Skinner enable your account."
|
||||
3. "here" link clears user state and redirects to login
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 2.1 | It should display a "Login with Google" button on the login page | UI (when migrated) | [ ] |
|
||||
| 2.2 | It should link the login button to Google OAuth | UI (when migrated) | [ ] |
|
||||
| 2.3 | It should preserve the `redirect-to` query parameter in the Google OAuth URL | Integration | [ ] |
|
||||
| 2.4 | It should display a warning notification with the logout reason when the `logout-reason` query parameter is set | UI (when migrated) | [ ] |
|
||||
| 2.5 | It should redirect an already authenticated user away from the login page | Integration | [ ] |
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
None.
|
||||
### Needs Activation
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
- **Happy path**: Shows activation required message
|
||||
- **Relogin link**: Clears user state and redirects to login
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 2.6 | It should display the message: "Sorry, your user is not activated yet. Please have Ben Skinner enable your account." when the user's account is inactive | UI (when migrated) | [ ] |
|
||||
| 2.7 | It should provide a "here" link that clears user state and redirects to the login page | Integration | [ ] |
|
||||
|
||||
---
|
||||
|
||||
### Transactions (`/transactions/`, `/transactions/unapproved`, `/transactions/approved`, `/transactions/requires-feedback`, `/transactions/excluded`)
|
||||
## Transactions
|
||||
|
||||
#### Purpose
|
||||
Transaction list with filtering, editing, and bulk admin operations. Different routes show different approval statuses.
|
||||
### List Display
|
||||
|
||||
#### User Flows
|
||||
1. User navigates to transactions page
|
||||
2. Default date filter: last 1 month
|
||||
3. Side bar allows filtering by: vendor, account, bank account, date range, amount range, location, import batch, description, linked status
|
||||
4. Table shows transactions with checkbox selection (admin only)
|
||||
5. Clicking row opens edit side bar
|
||||
6. Admin can: bulk code, bulk delete, suppress, or manual Yodlee import
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 3.1 | It should display a table of transactions with columns for amount, memo, location, approval status, vendor, accounts, date, and description | UI (when migrated) | [ ] |
|
||||
| 3.2 | It should load the transaction list with a default date filter of the last 1 month | Integration | [ ] |
|
||||
| 3.3 | It should allow filtering by vendor, account, bank account, date range, amount range, location, import batch, description, and linked status via a sidebar | UI (when migrated) | [ ] |
|
||||
| 3.4 | It should debounce sidebar filter changes by 800ms before refreshing data | Integration | [ ] |
|
||||
| 3.5 | It should support pagination with start and per-page parameters | Integration | [ ] |
|
||||
| 3.6 | It should display checkboxes for row selection when the user is an admin | UI (when migrated) | [ ] |
|
||||
| 3.7 | It should not display checkboxes or bulk action buttons for non-admin users | UI (when migrated) | [ ] |
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
```graphql
|
||||
query TransactionPage($filters: transaction_filters) {
|
||||
transaction_page(filters: $filters) {
|
||||
data {
|
||||
id amount memo location approval_status check_number is_locked
|
||||
matched_rule { note id }
|
||||
vendor { name id }
|
||||
accounts { id amount location account { name id location numeric_code } }
|
||||
date yodlee_merchant { name yodlee_id id } plaid_merchant { name id }
|
||||
post_date expected_deposit { id date } forecast_match { id identifier }
|
||||
status description_original
|
||||
payment { check_number s3_url id date }
|
||||
client { name id }
|
||||
bank_account { name yodlee_account_id current_balance }
|
||||
}
|
||||
total start end count
|
||||
}
|
||||
}
|
||||
### Transaction Edit
|
||||
|
||||
mutation EditTransaction($transaction: edit_transaction) {
|
||||
edit_transaction(transaction: $transaction) { ... }
|
||||
}
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 3.8 | It should open an edit sidebar when the user clicks a transaction row | UI (when migrated) | [ ] |
|
||||
| 3.9 | It should allow updating the vendor, approval status, memo, and expense accounts | UI (when migrated) | [ ] |
|
||||
| 3.10 | It should validate that the sum of expense account amounts equals the transaction amount | Unit + Integration | [ ] |
|
||||
| 3.11 | It should validate that locations are valid for the selected accounts | Unit + Integration | [ ] |
|
||||
| 3.12 | It should block editing locked transactions | Integration | [ ] |
|
||||
| 3.13 | It should display validation errors inline in the edit form | UI (when migrated) | [ ] |
|
||||
|
||||
mutation DeleteTransactions($filters: transaction_filters, $ids: [id], $suppress: Boolean) {
|
||||
delete_transactions(filters: $filters, ids: $ids, suppress: $suppress) { message }
|
||||
}
|
||||
### Bulk Operations (Admin)
|
||||
|
||||
mutation BulkCodeTransactions($filters: transaction_filters, $ids: [id], $vendor: id, $approval_status: transaction_approval_status, $accounts: [edit_percentage_account]) {
|
||||
bulk_code_transactions(filters: $filters, ids: $ids, vendor: $vendor, approval_status: $approval_status, accounts: $accounts) { message }
|
||||
}
|
||||
```
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 3.14 | It should allow deleting selected transactions or all visible transactions | Integration | [ ] |
|
||||
| 3.15 | It should allow suppressing selected transactions instead of deleting them | Integration | [ ] |
|
||||
| 3.16 | It should allow bulk coding by applying vendor, account, approval status, and account rules to multiple transactions | Integration | [ ] |
|
||||
| 3.17 | It should allow importing transactions via a manual Yodlee import dialog | Integration | [ ] |
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
- **Happy path**: Loads transaction list with default 1-month filter
|
||||
- **Route variants**: Each approval-status route applies correct default filter
|
||||
- **Filtering**: Side bar filters trigger debounced data refresh (800ms)
|
||||
- **Pagination**: Start/per-page params work correctly
|
||||
- **Edit transaction**: Update vendor, approval status, memo, expense accounts
|
||||
- **Edit validation**: Account total must equal transaction amount; locations must be valid
|
||||
- **Bulk delete (admin)**: Delete selected transactions or all visible transactions
|
||||
- **Bulk suppress (admin)**: Mark transactions as suppressed instead of deleting
|
||||
- **Bulk code (admin)**: Apply vendor/account rules to multiple transactions
|
||||
- **Manual import (admin)**: Import transactions via manual Yodlee import dialog
|
||||
- **Error states**: Locked transactions cannot be edited/deleted; validation errors display inline
|
||||
- **Permissions**: Non-admin users don't see checkboxes or action buttons
|
||||
### Route Variants
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 3.18 | It should apply the correct default approval status filter for each route variant: all, unapproved, approved, requires-feedback, excluded | Integration | [ ] |
|
||||
|
||||
---
|
||||
|
||||
### Ledger (`/ledger/`)
|
||||
## Ledger
|
||||
|
||||
#### Purpose
|
||||
General ledger showing journal entries with line items. Supports filtering and CSV export.
|
||||
### General Ledger
|
||||
|
||||
#### User Flows
|
||||
1. User navigates to `/ledger/`
|
||||
2. Default date filter: last 1 month
|
||||
3. Side bar filters: vendor, account, bank account, date range, amount range, location
|
||||
4. Table shows journal entries with expandable line items
|
||||
5. Admin can export to CSV
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 4.1 | It should display a table of journal entries with expandable line items | UI (when migrated) | [ ] |
|
||||
| 4.2 | It should load the ledger with a default date filter of the last 1 month | Integration | [ ] |
|
||||
| 4.3 | It should allow filtering by vendor, account, bank account, date range, amount range, and location via a sidebar | UI (when migrated) | [ ] |
|
||||
| 4.4 | It should support virtual pagination controls | Integration | [ ] |
|
||||
| 4.5 | It should allow admin users to export filtered results as a CSV download | Integration | [ ] |
|
||||
| 4.6 | It should display "Not authorized" for users with the manager role | UI (when migrated) | [ ] |
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
```graphql
|
||||
query LedgerPage($filters: ledger_filters) {
|
||||
ledger_page(filters: $filters) {
|
||||
journal_entries {
|
||||
id source original_entity amount note cleared_against alternate_description
|
||||
vendor { name id } client { name id }
|
||||
line_items { id debit credit location running_balance account { id name } }
|
||||
date
|
||||
}
|
||||
total start end
|
||||
}
|
||||
}
|
||||
### Profit and Loss
|
||||
|
||||
query LedgerCsv($filters: ledger_filters) {
|
||||
ledger_csv(filters: $filters) { csv_content_b64 }
|
||||
}
|
||||
```
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 4.7 | It should generate a profit and loss report for a single client with a default period | Integration | [ ] |
|
||||
| 4.8 | It should allow selecting multiple companies via a multi-select typeahead and generating a combined report | UI (when migrated) | [ ] |
|
||||
| 4.9 | It should provide period preset buttons: 13 periods, 12 months, last week, week-to-date, last month, month-to-date, year-to-date, last calendar year, and full year | UI (when migrated) | [ ] |
|
||||
| 4.10 | It should populate the correct date ranges when a period preset is selected | Unit | [ ] |
|
||||
| 4.11 | It should allow custom period selection via start and end date pickers in advanced mode | UI (when migrated) | [ ] |
|
||||
| 4.12 | It should optionally include period-over-period deltas | UI (when migrated) | [ ] |
|
||||
| 4.13 | It should optionally break out the report by location, which is mutually exclusive with including deltas | Integration | [ ] |
|
||||
| 4.14 | It should allow admin users to generate and download a PDF export | Integration | [ ] |
|
||||
| 4.15 | It should provide an email composition link for single-client PDF exports | UI (when migrated) | [ ] |
|
||||
| 4.16 | It should open a ledger detail sidebar when the user clicks a report cell | UI (when migrated) | [ ] |
|
||||
| 4.17 | It should display "Not authorized" for users with the manager role | UI (when migrated) | [ ] |
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
- **Happy path**: Loads ledger with default 1-month filter
|
||||
- **Filtering**: Date range, vendor, account, location filters work
|
||||
- **CSV export**: Admin can export filtered results as base64 CSV download
|
||||
- **Pagination**: Virtual pagination controls
|
||||
- **Permissions**: Manager role sees "Not authorized"
|
||||
### Cash Flows
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 4.18 | It should generate a cash flows statement report | Integration | [ ] |
|
||||
| 4.19 | It should use the same company, period, delta, and location column controls as the profit and loss report | Integration | [ ] |
|
||||
| 4.20 | It should allow admin users to export the report to PDF | Integration | [ ] |
|
||||
| 4.21 | It should open a ledger detail sidebar when the user clicks a report cell | UI (when migrated) | [ ] |
|
||||
| 4.22 | It should display "Not authorized" for users with the manager role | UI (when migrated) | [ ] |
|
||||
|
||||
### Profit and Loss Detail
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 4.23 | It should generate a detailed journal entry report with a default 2-week date range | Integration | [ ] |
|
||||
| 4.24 | It should group journal entries by category: sales, COGS, payroll, controllable, fixed overhead, and ownership controllable | Integration | [ ] |
|
||||
| 4.25 | It should display Gross Profit, Overhead, and Net Profit summaries | UI (when migrated) | [ ] |
|
||||
| 4.26 | It should filter journal entries by the selected start and end dates | Integration | [ ] |
|
||||
| 4.27 | It should allow admin users to export the report to PDF with an email link for single client | Integration | [ ] |
|
||||
| 4.28 | It should display "Not authorized" for users with the manager role | UI (when migrated) | [ ] |
|
||||
|
||||
### Balance Sheet
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 4.29 | It should generate a balance sheet report as of a specific date | Integration | [ ] |
|
||||
| 4.30 | It should allow selecting multiple companies and combining them into a single report | Integration | [ ] |
|
||||
| 4.31 | It should optionally include a prior-year comparison with a side-by-side view | UI (when migrated) | [ ] |
|
||||
| 4.32 | It should allow admin users to export the report to PDF with an email composition link for single client | Integration | [ ] |
|
||||
| 4.33 | It should open ledger entries filtered by account and date range when the user clicks a cell | UI (when migrated) | [ ] |
|
||||
| 4.34 | It should display "Not authorized" for users with the manager role | UI (when migrated) | [ ] |
|
||||
|
||||
### External Ledger
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 4.35 | It should display only externally-imported journal entries with an external_id | Integration | [ ] |
|
||||
| 4.36 | It should allow admin users to delete selected entries, with a maximum of 1000 at once | Integration | [ ] |
|
||||
| 4.37 | It should allow admin users to export to CSV | Integration | [ ] |
|
||||
| 4.38 | It should display "Not authorized" for non-admin users | UI (when migrated) | [ ] |
|
||||
|
||||
### External Import
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 4.39 | It should allow admin users to paste tab-separated data into a textarea | UI (when migrated) | [ ] |
|
||||
| 4.40 | It should provide a checkbox to indicate whether the first row is a header | UI (when migrated) | [ ] |
|
||||
| 4.41 | It should parse the pasted data into a table with columns: Id, Client, Source, Vendor, Date, Account, Location, Debit, Credit, Note, and Cleared against | Integration | [ ] |
|
||||
| 4.42 | It should validate that the client code exists | Unit + Integration | [ ] |
|
||||
| 4.43 | It should validate that the vendor exists | Unit + Integration | [ ] |
|
||||
| 4.44 | It should validate that the date is in MM/dd/yyyy format | Unit + Integration | [ ] |
|
||||
| 4.45 | It should validate that total debits equal total credits | Unit + Integration | [ ] |
|
||||
| 4.46 | It should validate that all amounts are greater than 0 | Unit + Integration | [ ] |
|
||||
| 4.47 | It should validate that entries are dated after the client's locked-until date | Integration | [ ] |
|
||||
| 4.48 | It should validate that the location belongs to the client or is "A" | Unit + Integration | [ ] |
|
||||
| 4.49 | It should validate that the account exists and the location matches the account's required location | Unit + Integration | [ ] |
|
||||
| 4.50 | It should display errors per row with a dropdown explanation | UI (when migrated) | [ ] |
|
||||
| 4.51 | It should show status icons indicating success, ignored, or existing per row after import | UI (when migrated) | [ ] |
|
||||
| 4.52 | It should display the total success count, ignored count, and error count after import | UI (when migrated) | [ ] |
|
||||
| 4.53 | It should provide an "Only show errors" filter | UI (when migrated) | [ ] |
|
||||
| 4.54 | It should display "Not authorized" for non-admin users | UI (when migrated) | [ ] |
|
||||
|
||||
---
|
||||
|
||||
### Profit and Loss (`/ledger/profit-and-loss`)
|
||||
## Payments
|
||||
|
||||
#### Purpose
|
||||
Financial report showing revenue and expenses over configurable periods. Supports multi-company and PDF export.
|
||||
|
||||
#### User Flows
|
||||
1. User selects companies (multi-select typeahead)
|
||||
2. User selects period preset (13 periods, 12 months, last week, week-to-date, last month, month-to-date, year-to-date, last calendar year, full year)
|
||||
3. Optional: Include deltas, Column per location
|
||||
4. Click "Run" to generate report
|
||||
5. Admin can "Export" to PDF
|
||||
6. Clicking report cells opens ledger detail side bar
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
```graphql
|
||||
query ProfitAndLoss($clientIds: [id], $periods: [date_range], $includeDeltas: Boolean, $columnPerLocation: Boolean) {
|
||||
profit_and_loss(client_ids: $clientIds, periods: $periods, include_deltas: $includeDeltas, column_per_location: $columnPerLocation) {
|
||||
periods { accounts { name amount client_id account_type id count numeric_code location } }
|
||||
}
|
||||
}
|
||||
|
||||
query ProfitAndLossPdf($clientIds: [id], $periods: [date_range], $includeDeltas: Boolean, $columnPerLocation: Boolean) {
|
||||
profit_and_loss_pdf(...) { url name }
|
||||
}
|
||||
```
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
- **Happy path**: Generate P&L for single client with default period
|
||||
- **Multi-company**: Select multiple clients and generate combined report
|
||||
- **Period presets**: All preset buttons populate correct date ranges
|
||||
- **Advanced mode**: Custom period date pickers
|
||||
- **Include deltas**: Shows period-over-period changes
|
||||
- **Column per location**: Breaks out by location (mutually exclusive with deltas)
|
||||
- **PDF export**: Admin can generate and download PDF; single-client shows email link
|
||||
- **Ledger drill-down**: Clicking cells opens ledger entries side bar for that account/period
|
||||
- **Permissions**: Manager role sees "Not authorized"
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 5.1 | It should be fully migrated to SSR at `/payment/`; the legacy client route exists only for navbar highlighting | N/A | [x] |
|
||||
|
||||
---
|
||||
|
||||
### Cash Flows (`/ledger/cash-flows`)
|
||||
## Reports
|
||||
|
||||
#### Purpose
|
||||
Statement of cash flows report. Same control structure as P&L but different report format.
|
||||
|
||||
#### User Flows
|
||||
Same as Profit and Loss but generates cash flow statement.
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
```graphql
|
||||
query CashFlows($clientIds: [id], $periods: [date_range], $includeDeltas: Boolean, $columnPerLocation: Boolean) {
|
||||
profit_and_loss(client_ids: $clientIds, periods: $periods, include_deltas: $includeDeltas, column_per_location: $columnPerLocation) {
|
||||
periods { accounts { name amount debits credits client_id account_type id count numeric_code location } }
|
||||
}
|
||||
}
|
||||
|
||||
query CashFlowsPdf($clientIds: [id], $periods: [date_range], $includeDeltas: Boolean, $columnPerLocation: Boolean) {
|
||||
cash_flows_pdf(...) { url name }
|
||||
}
|
||||
```
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
- **Happy path**: Generate cash flows report
|
||||
- **Same controls as P&L**: Companies, periods, deltas, location columns
|
||||
- **PDF export**: Admin-only export button
|
||||
- **Ledger drill-down**: Clicking cells opens ledger detail
|
||||
- **Permissions**: Manager role sees "Not authorized"
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 6.1 | It should be fully migrated to SSR at `/company/reports`; the legacy client route exists only for navbar highlighting | N/A | [x] |
|
||||
|
||||
---
|
||||
|
||||
### Profit and Loss Detail (`/ledger/profit-and-loss-detail`)
|
||||
## Vendors
|
||||
|
||||
#### Purpose
|
||||
Detailed journal entry report broken down by category (sales, COGS, payroll, controllable, fixed overhead, ownership controllable).
|
||||
### Admin Vendor Management
|
||||
|
||||
#### User Flows
|
||||
1. Select companies
|
||||
2. Select date range (start/end date pickers)
|
||||
3. Click "Run" to generate detail report
|
||||
4. Shows journal entries grouped by category and account with running balances
|
||||
5. Admin can export to PDF
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 7.1 | It should be fully migrated to SSR at `/admin/vendor`; the legacy client route exists only for navbar highlighting | N/A | [x] |
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
```graphql
|
||||
query JournalDetailReport($clientIds: [id], $dateRange: date_range, $categories: [category]) {
|
||||
journal_detail_report(client_ids: $clientIds, date_range: $dateRange, categories: $categories) {
|
||||
categories {
|
||||
category client_id location
|
||||
account { numeric_code name }
|
||||
journal_entries { description date debit credit location running_balance account { id name } }
|
||||
total
|
||||
}
|
||||
}
|
||||
}
|
||||
### New Vendor Dialog
|
||||
|
||||
query JournalDetailReportPdf($clientIds: [id], $dateRange: date_range, $categories: [category]) {
|
||||
journal_detail_report_pdf(...) { url name }
|
||||
}
|
||||
```
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
- **Happy path**: Generate detail report with default 2-week range
|
||||
- **Category breakdown**: Report includes all 6 category sections plus Gross Profit, Overhead, Net Profit summaries
|
||||
- **Date range filtering**: Start/end dates filter journal entries
|
||||
- **PDF export**: Admin can export to PDF with email link for single client
|
||||
- **Permissions**: Manager role sees "Not authorized"
|
||||
|
||||
---
|
||||
|
||||
### Balance Sheet (`/ledger/balance-sheet`)
|
||||
|
||||
#### Purpose
|
||||
Balance sheet report as of a specific date, with optional prior-year comparison.
|
||||
|
||||
#### User Flows
|
||||
1. Select companies
|
||||
2. Select report date
|
||||
3. Optionally enable "Include comparison" and select comparison date
|
||||
4. Click "Run" to generate balance sheet
|
||||
5. Admin can export to PDF
|
||||
6. Clicking cells opens ledger detail side bar
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
```graphql
|
||||
query BalanceSheet($clientIds: [id], $date: iso_date, $comparisonDate: iso_date, $includeComparison: Boolean) {
|
||||
balance_sheet(client_ids: $clientIds, date: $date, comparison_date: $comparisonDate, include_comparison: $includeComparison) {
|
||||
balance_sheet_accounts { name amount account_type id numeric_code client_id }
|
||||
comparable_balance_sheet_accounts { name amount account_type id client_id numeric_code }
|
||||
}
|
||||
}
|
||||
|
||||
query BalanceSheetPdf($clientIds: [id], $date: iso_date, $comparisonDate: iso_date, $includeComparison: Boolean) {
|
||||
balance_sheet_pdf(...) { url name }
|
||||
}
|
||||
```
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
- **Happy path**: Generate balance sheet for current date
|
||||
- **Comparison mode**: Shows prior period side-by-side
|
||||
- **Multi-company**: Combines multiple clients
|
||||
- **PDF export**: Admin-only with email composition link for single client
|
||||
- **Ledger drill-down**: Clicking cells opens ledger entries filtered by account and date range
|
||||
- **Permissions**: Manager role sees "Not authorized"
|
||||
|
||||
---
|
||||
|
||||
### External Ledger (`/ledger/external`)
|
||||
|
||||
#### Purpose
|
||||
Shows only externally-imported journal entries. Admin can delete entries.
|
||||
|
||||
#### User Flows
|
||||
1. Admin navigates to `/ledger/external`
|
||||
2. Shows external journal entries with external_id
|
||||
3. Can select entries and delete them
|
||||
4. Admin can export to CSV
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
```graphql
|
||||
query ExternalLedger($filters: ledger_filters) {
|
||||
ledger_page(filters: $filters) {
|
||||
journal_entries { id external_id source ... }
|
||||
total start end
|
||||
}
|
||||
}
|
||||
|
||||
mutation DeleteExternalLedger($filters: ledger_filters, $ids: [id]) {
|
||||
delete_external_ledger(filters: $filters, ids: $ids) { message }
|
||||
}
|
||||
```
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
- **Happy path**: Loads external-only ledger entries
|
||||
- **Delete**: Admin can delete selected entries (max 1000 at once)
|
||||
- **CSV export**: Same as main ledger
|
||||
- **Permissions**: Admin-only; non-admin sees "Not authorized"
|
||||
|
||||
---
|
||||
|
||||
### External Import (`/ledger/external-import`)
|
||||
|
||||
#### Purpose
|
||||
Manual import of ledger entries via tab-separated text paste.
|
||||
|
||||
#### User Flows
|
||||
1. Admin pastes tab-separated data into textarea
|
||||
2. Optional: indicates whether first row is header
|
||||
3. Click "Parse" to convert to table
|
||||
4. Table shows parsed rows: Id, Client, Source, Vendor, Date, Account, Location, Debit, Credit, Note, Cleared against
|
||||
5. Click "Import" to submit
|
||||
6. Results show: success count, ignored count, error count
|
||||
7. Errors show inline with dropdown explanations
|
||||
8. "Only show errors" filter available
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
```graphql
|
||||
mutation ImportLedger($entries: [ledger_entry_input]) {
|
||||
import_ledger(entries: $entries) {
|
||||
successful { external_id }
|
||||
existing { external_id }
|
||||
ignored { external_id }
|
||||
errors { external_id error }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
- **Happy path**: Paste TSV data, parse, import successfully
|
||||
- **Header row**: Checkbox to skip first row
|
||||
- **Validation**: Client code must exist, vendor must exist, date must be MM/dd/yyyy, debits=credits, amounts > 0
|
||||
- **Lock check**: Entries must be after client's locked-until date
|
||||
- **Location validation**: Location must belong to client or be "A"
|
||||
- **Account validation**: Account must exist and location must match account's required location
|
||||
- **Error display**: Errors show per-row with dropdown explanation
|
||||
- **Success/ignored/existing**: Status icons show import result per row
|
||||
- **Permissions**: Admin-only
|
||||
|
||||
---
|
||||
|
||||
### Payments (`/payments/`)
|
||||
|
||||
#### Purpose
|
||||
**Already migrated to SSR.** Legacy client route exists for navbar highlighting only. Actual payments page is served by `payment` SSR routes at `/payment/`.
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
N/A (SSR page)
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
Already migrated. Test via SSR routes.
|
||||
|
||||
---
|
||||
|
||||
### Reports (`/reports/`)
|
||||
|
||||
#### Purpose
|
||||
**Already migrated to SSR.** Legacy client route exists for navbar highlighting only. Actual reports page is served by `company/reports` SSR routes.
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
N/A (SSR page)
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
Already migrated. Test via SSR routes.
|
||||
|
||||
---
|
||||
|
||||
### Admin Vendors (`/admin/vendors`)
|
||||
|
||||
#### Purpose
|
||||
**Already migrated to SSR.** Legacy client route exists for navbar highlighting only. Actual vendor management is served by `admin/vendor` SSR routes.
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
N/A (SSR page)
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
Already migrated. Test via SSR routes.
|
||||
|
||||
---
|
||||
|
||||
### New Vendor (`/vendor/new`)
|
||||
|
||||
#### Purpose
|
||||
Opens the home dashboard with the vendor creation dialog pre-opened.
|
||||
|
||||
#### User Flows
|
||||
1. User navigates to `/vendor/new`
|
||||
2. Loads home dashboard
|
||||
3. Opens vendor dialog if user has `:vendor :create` permission
|
||||
4. Dialog allows creating new vendor with name, terms, address, contacts, default account, etc.
|
||||
|
||||
#### GraphQL Queries/Mutations Used
|
||||
```graphql
|
||||
mutation UpsertVendor($vendor: add_vendor) {
|
||||
upsert_vendor(vendor: $vendor) { id name ... }
|
||||
}
|
||||
```
|
||||
|
||||
#### Behaviors to Test (when migrated)
|
||||
- **Happy path**: Home loads with vendor dialog open
|
||||
- **Permission check**: Dialog only opens if user can `:vendor :create`
|
||||
- **Vendor creation**: Form submission creates vendor via GraphQL mutation
|
||||
- **Validation**: Only one terms override, schedule payment DOM override, and account override per client
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 7.2 | It should load the home dashboard with the vendor creation dialog pre-opened when navigating to `/vendor/new` | UI (when migrated) | [ ] |
|
||||
| 7.3 | It should only open the vendor dialog if the user has `:vendor :create` permission | Integration | [ ] |
|
||||
| 7.4 | It should allow creating a new vendor with name, terms, address, contacts, and default account | UI (when migrated) | [ ] |
|
||||
| 7.5 | It should validate that only one terms override exists per client | Unit + Integration | [ ] |
|
||||
| 7.6 | It should validate that only one schedule payment DOM override exists per client | Unit + Integration | [ ] |
|
||||
| 7.7 | It should validate that only one account override exists per client | Unit + Integration | [ ] |
|
||||
|
||||
---
|
||||
|
||||
## Cross-Cutting Behaviors
|
||||
|
||||
### GraphQL Query Patterns
|
||||
- All data fetching uses re-frame `graphql` effect with JWT token from `:user` in app-db
|
||||
- Queries use `owns-state` to track loading status per page
|
||||
- Results flow through `::data-page/received` event which stores data and syncs URL query params
|
||||
- Filter changes are debounced (800ms) via `dispatch-debounce`
|
||||
|
||||
### Client-Side State Management (Re-frame)
|
||||
- **Data pages**: `data-page` namespace provides reusable pagination/filtering state
|
||||
- `::data-page/params` - merged filters + table params + query params
|
||||
- `::data-page/data` - GraphQL response data
|
||||
- `::data-page/checked` - selected rows for bulk operations
|
||||
- **Forms**: `forms` namespace manages edit dialogs with `start-form`, `change-handler`, `save-succeeded`
|
||||
- **Status**: `status` namespace tracks async operation states (`:loading`, `:complete`, `:error`)
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 8.1 | It should include the JWT token from the `:user` app-db state in all GraphQL requests | Integration | [ ] |
|
||||
| 8.2 | It should track loading status per page using `owns-state` | Integration | [ ] |
|
||||
| 8.3 | It should store GraphQL response data and sync URL query params via the `::data-page/received` event | Integration | [ ] |
|
||||
| 8.4 | It should debounce filter changes by 800ms before dispatching GraphQL requests | Integration | [ ] |
|
||||
|
||||
### Navigation Between Legacy Pages
|
||||
- Navbar uses Bidi `path-for` with `routes/routes` for legacy SPA links
|
||||
- Some navbar items (Payments, POS, Invoices) now link to `ssr-routes/only-routes` instead
|
||||
- Page components dispatch `::mounted` on mount and `::unmounted` on unmount to set up/tear down:
|
||||
- Data subscriptions
|
||||
- Forward event listeners
|
||||
- Track subscriptions (parameter change watchers)
|
||||
### Re-frame State Management
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 8.5 | It should merge filters, table params, and query params into `::data-page/params` | Integration | [ ] |
|
||||
| 8.6 | It should store GraphQL response data in `::data-page/data` | Integration | [ ] |
|
||||
| 8.7 | It should track selected rows for bulk operations in `::data-page/checked` | Integration | [ ] |
|
||||
| 8.8 | It should manage edit dialog state with `start-form`, `change-handler`, and `save-succeeded` events | Integration | [ ] |
|
||||
| 8.9 | It should track async operation states as `:loading`, `:complete`, or `:error` | Integration | [ ] |
|
||||
|
||||
### Navigation
|
||||
|
||||
| # | Behavior | Test Strategy | Status |
|
||||
|---|----------|---------------|--------|
|
||||
| 8.10 | It should redirect old SPA routes to new SSR routes after migration | Integration | [ ] |
|
||||
| 8.11 | It should set up data subscriptions, forward event listeners, and parameter change watchers on page mount | Integration | [ ] |
|
||||
| 8.12 | It should tear down data subscriptions, forward event listeners, and parameter change watchers on page unmount | Integration | [ ] |
|
||||
|
||||
---
|
||||
|
||||
## Test Data Requirements
|
||||
|
||||
| Entity | Requirements |
|
||||
|--------|-------------|
|
||||
| **Users** | Admin, power-user, manager, user, and read-only roles |
|
||||
| **Clients** | Multiple clients with different locations; some with locked-until dates |
|
||||
| **Vendors** | With/without default accounts, terms, and autopay settings |
|
||||
| **Accounts** | Expense accounts with/without invoice allowance; different locations |
|
||||
| **Bank Accounts** | Check, cash, and credit types |
|
||||
| **Transactions** | Various approval statuses, dates, amounts, locked/unlocked states |
|
||||
| **Journal Entries** | With/without external_id; various categories and accounts |
|
||||
| **Invoices** | Various statuses and due dates for cash flow projections |
|
||||
|
||||
## Existing Tests to Preserve
|
||||
|
||||
- Test GraphQL query resolution for `HomeDashboard`, `TransactionPage`, `LedgerPage`, `ProfitAndLoss`, `CashFlows`, `JournalDetailReport`, `BalanceSheet`, and `ExternalLedger`
|
||||
- Test re-frame event handlers for data page state transitions
|
||||
- Test form validation logic for transaction editing and external ledger import
|
||||
|
||||
## Dependencies
|
||||
|
||||
- GraphQL API (data fetching)
|
||||
- Re-frame/Reagent (client-side state and rendering)
|
||||
- Bidi (client-side routing)
|
||||
- Recharts (chart rendering on Home page)
|
||||
- Server-side PDF generation (P&L, Cash Flows, Balance Sheet, P&L Detail)
|
||||
|
||||
## Migration Notes
|
||||
|
||||
### Actively Migrated / Already SSR
|
||||
### Already Migrated to SSR
|
||||
- **Payments** (`/payments/`) - Fully migrated to `/payment/` SSR routes
|
||||
- **Reports** (`/reports/`) - Fully migrated to `/company/reports` SSR routes
|
||||
- **Admin Vendors** (`/admin/vendors`) - Fully migrated to `/admin/vendor` SSR routes
|
||||
|
||||
Reference in New Issue
Block a user