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.
368 lines
14 KiB
Markdown
368 lines
14 KiB
Markdown
# Ledger Behaviors
|
|
|
|
## Overview
|
|
|
|
The Ledger module is the core accounting interface of Integreat. It provides server-side rendered (HTMX) pages for viewing journal entries, creating manual journal entries, and generating financial reports (Profit & Loss, Balance Sheet, Cash Flows). All ledger pages are permission-gated and client-scoped.
|
|
|
|
## Routes & Pages
|
|
|
|
| Route | Method | Handler | Description |
|
|
|-------|--------|---------|-------------|
|
|
| `/ledger` | GET | `::all-page` | Main ledger entries list (internal) |
|
|
| `/ledger/external-new` | GET | `::external-page` | External ledger entries list |
|
|
| `/ledger/new` | GET | `::new` | Modal form for new journal entry |
|
|
| `/ledger/new` | POST | `::new-submit` | Submit new journal entry |
|
|
| `/ledger/new/location-select` | GET | `::location-select` | Location dropdown for line item |
|
|
| `/ledger/new/account-typeahead` | GET | `::account-typeahead` | Account search for line item |
|
|
| `/ledger/new/line-item` | GET | `::new-line-item` | Add new line item row |
|
|
| `/ledger/external-import-new` | GET | `::external-import-page` | External TSV import page |
|
|
| `/ledger/external-import-new/parse` | POST | `::external-import-parse` | Parse clipboard TSV data |
|
|
| `/ledger/external-import-new/import` | POST | `::external-import-import` | Import validated entries |
|
|
| `/ledger/investigate` | GET | `::investigate` | Investigation modal for report drill-down |
|
|
| `/ledger/investigate/results` | GET | `::investigate-results` | Investigation results table |
|
|
| `/ledger/table` | GET | `::table` | HTMX table fragment for ledger entries |
|
|
| `/ledger/csv` | GET | `::csv` | CSV export of ledger entries |
|
|
| `/ledger/bank-account-filter` | GET | `::bank-account-filter` | Bank account filter widget |
|
|
| `/ledger/reports/profit-and-loss` | GET | `::profit-and-loss` | P&L report page |
|
|
| `/ledger/reports/profit-and-loss/run` | PUT | `::run-profit-and-loss` | Run P&L report |
|
|
| `/ledger/reports/profit-and-loss/export` | POST | `::export-profit-and-loss` | Export P&L to PDF |
|
|
| `/ledger/reports/balance-sheet` | GET | `::balance-sheet` | Balance sheet page |
|
|
| `/ledger/reports/balance-sheet/run` | GET | `::run-balance-sheet` | Run balance sheet |
|
|
| `/ledger/reports/balance-sheet/export` | POST | `::export-balance-sheet` | Export balance sheet to PDF |
|
|
| `/ledger/reports/cash-flows` | GET | `::cash-flows` | Cash flows page |
|
|
| `/ledger/reports/cash-flows/run` | PUT | `::run-cash-flows` | Run cash flows report |
|
|
| `/ledger/reports/cash-flows/export` | POST | `::export-cash-flows` | Export cash flows to PDF |
|
|
|
|
## Behaviors by Page
|
|
|
|
### Ledger Entries List
|
|
|
|
**Page load**
|
|
- Displays a paginated, sortable data grid of journal entries
|
|
- Shows client selection sidebar if user has access to multiple clients
|
|
- Title reflects status filter: e.g. "Unpaid Register" or "Paid Register"
|
|
- Includes an "Add journal entry" button (hidden on external ledger page)
|
|
|
|
**Columns displayed**
|
|
- Client (hidden if only 1 client with 1 location)
|
|
- Vendor (falls back to `alternate-description` if no vendor)
|
|
- Source (hidden on internal ledger)
|
|
- External ID (hidden on internal ledger, truncated to max-width)
|
|
- Date
|
|
- Amount (formatted as currency)
|
|
- Debit lines (account + location + amount)
|
|
- Credit lines (account + location + amount)
|
|
- Links dropdown (links to original invoice, source file, transaction, or memo)
|
|
|
|
**Row actions**
|
|
- Void button for unpaid invoices (requires `:delete :invoice` permission)
|
|
- Edit button for unpaid/paid invoices (requires `:edit :invoice` permission)
|
|
- Unvoid button for voided invoices (requires `:edit :invoice` permission)
|
|
- Trash icon with confirmation for delete operations
|
|
|
|
**Sorting**
|
|
- Available sorts: Client, Vendor, Source, External ID, Date, Amount, Account
|
|
- Default sort: Date ascending
|
|
- Sorting by Vendor or Source groups rows with break headers
|
|
|
|
**Filtering (left sidebar)**
|
|
- Vendor typeahead search
|
|
- Account typeahead search
|
|
- Bank account radio filter (refreshes on client change)
|
|
- Date range picker
|
|
- Invoice number text search
|
|
- Account code range (gte/lte inputs)
|
|
- Amount range (gte/lte inputs)
|
|
- "Show unbalanced" checkbox (filters to entries where debits ≠ credits)
|
|
- Exact match ID pill (clears on click)
|
|
|
|
**Pagination**
|
|
- Default 25 entries per page
|
|
- Configurable per-page
|
|
- Pagination controls at bottom
|
|
|
|
**CSV Export**
|
|
- Exports all matching entries with line-item-level rows
|
|
- Columns: ID, Client, Vendor, Source, External ID, Date, Amount, Account, Debit, Credit
|
|
|
|
### New Journal Entry
|
|
|
|
**Modal form**
|
|
- Opens as modal dialog (750px wide)
|
|
- Client typeahead (pre-filled if client already selected on parent page)
|
|
- Date input (default today, format MM/DD/YYYY)
|
|
- Vendor typeahead (disabled if editing existing entry)
|
|
- Total amount input (must be ≥ $0.01)
|
|
- Memo text input (optional)
|
|
- Line items grid with columns: Account, Location, Debit, Credit
|
|
|
|
**Line item behaviors**
|
|
- Account typeahead searches accounts scoped to selected client
|
|
- Location dropdown updates based on selected account's required location
|
|
- If account has a fixed location, dropdown is locked to that location
|
|
- If account has no location restriction, shows client's locations
|
|
- New line item rows added via HTMX request
|
|
- Line items can be removed with X button
|
|
|
|
**Validation**
|
|
- Client is required
|
|
- Date is required and must be valid
|
|
- Vendor is required
|
|
- Amount must be ≥ $0.01
|
|
- Each line item must have an allowed account
|
|
- Each line item must have a location belonging to the account
|
|
- Debits must sum to total amount
|
|
- Credits must sum to total amount
|
|
- Debits and credits must each equal the journal entry amount
|
|
|
|
**On save**
|
|
- Generates external ID: `manual-<uuid>`
|
|
- Updates client's `ledger-last-change` timestamp
|
|
- On POST: prepends new row to table, triggers modal close
|
|
- On PUT: replaces existing row in table, triggers modal close
|
|
|
|
### External Import
|
|
|
|
**Clipboard paste**
|
|
- User clicks "Load from clipboard" button
|
|
- Reads TSV data from browser clipboard
|
|
- Parses tab-separated values with columns: Id, Client, Source, Vendor, Date, Account Code, Location, Debit, Credit
|
|
|
|
**Parse validation**
|
|
- Validates all rows have required fields
|
|
- Dates must be parseable
|
|
- Account codes must be numeric or bank account strings
|
|
- Locations must be 1-2 characters
|
|
- Debits/Credits must be valid money amounts
|
|
|
|
**Import validation**
|
|
- Client code must exist
|
|
- Vendor must exist (creates hidden vendor if missing)
|
|
- Client must not be locked for the entry date
|
|
- Debits and credits must balance per entry
|
|
- Entry cannot total $0.00 (warning)
|
|
- Location must belong to client
|
|
- Account code must exist
|
|
- Bank account code must belong to client
|
|
- Account location requirements must be satisfied
|
|
|
|
**Import results**
|
|
- Successful entries are imported
|
|
- Entries with warnings are ignored (removed if previously existed)
|
|
- Entries with errors block import and show error counts
|
|
- Retracts existing entries by external ID before importing
|
|
- Indexes imported entries in Solr asynchronously
|
|
|
|
### P&L Report
|
|
|
|
**Form**
|
|
- Customer multi-select typeahead (max 20, defaults to first 5 if "all")
|
|
- Periods dropdown (default: year-to-date)
|
|
- "Column per location" toggle
|
|
- "Include deltas" toggle
|
|
- Run button (HTMX PUT)
|
|
- Export PDF button
|
|
|
|
**Report generation**
|
|
- Computes running balances before generating
|
|
- Queries detailed account snapshots for each client/period end date
|
|
- Amounts calculated as: debits - credits for assets/dividends/expenses, credits - debits for others
|
|
- Groups data by client, location, and period
|
|
|
|
**Report output**
|
|
- Summary table: Sales, COGS, Payroll, Gross Profits, Overhead, Net Income
|
|
- Detail table: Account-level breakdown within each category
|
|
- Percent of sales calculated for each row
|
|
- Deltas shown between periods if enabled
|
|
- Column-per-location mode shows each location as separate columns
|
|
|
|
**Warnings**
|
|
- Warns if >20 clients selected (truncates to 20)
|
|
- Warns about unresolved ledger entries (missing numeric codes)
|
|
- Shows sample links to admin history for invalid entries (if user has `:view :history` permission)
|
|
|
|
**PDF Export**
|
|
- Generates PDF with Calibri Light font, 6pt
|
|
- Uploads to S3 at `reports/profit-and-loss/<uuid>/<name>.pdf`
|
|
- Persists report record in Datomic
|
|
- Returns modal with download link
|
|
|
|
### Balance Sheet
|
|
|
|
**Form**
|
|
- Customer multi-select typeahead (max 20, defaults to first 5 if "all")
|
|
- Date dropdown (single date, default today)
|
|
- "Include deltas" toggle
|
|
- Run button (HTMX GET)
|
|
- Export PDF button
|
|
|
|
**Report generation**
|
|
- Computes running balances before generating
|
|
- Queries account snapshot as of each selected date
|
|
- Groups by account categories: Assets, Liabilities, Owner's Equity
|
|
- Includes Retained Earnings (net income across all P&L categories)
|
|
|
|
**Report output**
|
|
- Assets section with account detail and subtotal
|
|
- Liabilities section with account detail and subtotal
|
|
- Owner's Equity section with account detail and subtotal
|
|
- Retained Earnings line
|
|
- Delta columns between periods if enabled and multiple dates selected
|
|
|
|
**Warnings**
|
|
- Warns if >20 clients selected
|
|
- Warns about unresolved ledger entries
|
|
|
|
**PDF Export**
|
|
- Generates PDF, uploads to S3 at `reports/balance-sheet/<uuid>/<name>.pdf`
|
|
- Persists report record in Datomic
|
|
|
|
### Cash Flows
|
|
|
|
**Form**
|
|
- Customer multi-select typeahead (max 20, defaults to first 5 if "all")
|
|
- Periods dropdown (default: year-to-date)
|
|
- Run button (HTMX PUT)
|
|
- Export PDF button
|
|
|
|
**Report generation**
|
|
- Queries account snapshot as of period end + 1 day
|
|
- Groups accounts into: Operating Activities, Investment Activities, Financing Activities, Cash
|
|
- Calculates cash flow effect: add or subtract based on account code ranges
|
|
|
|
**Report output**
|
|
- Net Income starting point
|
|
- Operating Activities detail (increases, decreases, +/- in cash)
|
|
- Investment Activities detail
|
|
- Financing Activities detail
|
|
- Change in Cash and Cash Equivalents total
|
|
- Bank Accounts / Cash detail
|
|
|
|
**Warnings**
|
|
- Warns if >20 clients selected
|
|
- Warns about unresolved ledger entries
|
|
|
|
**PDF Export**
|
|
- Generates PDF, uploads to S3 at `reports/cash-flows/<uuid>/<name>.pdf`
|
|
- Persists report record in Datomic
|
|
|
|
### Investigation
|
|
|
|
**Modal behavior**
|
|
- Opens as modal dialog from report table cell clicks
|
|
- Shows ledger entries filtered by the clicked cell's filters (account code range, client, location, date range)
|
|
- Displays raw table without checkboxes
|
|
- Max height 600px with scrollable content
|
|
|
|
**Table behavior**
|
|
- Uses same query schema as main ledger list
|
|
- Supports sorting and pagination
|
|
- Does not push URL state
|
|
|
|
## Cross-Cutting Behaviors
|
|
|
|
### Report generation
|
|
- All reports call `upsert-running-balance` before querying to ensure cached balances are current
|
|
- Reports use `detailed-account-snapshot` Datomic query for raw data
|
|
- Account lookups built per-client via `build-account-lookup`
|
|
- Reports skip entries without numeric codes (unassigned accounts) and warn
|
|
|
|
### Export
|
|
- All three reports support PDF export via `clj-pdf`
|
|
- PDFs use Calibri Light 6pt font on letter size
|
|
- Uploaded to S3 data bucket with UUID-based key
|
|
- Report metadata persisted to Datomic (`:report/name`, `:report/client`, `:report/key`, `:report/url`, `:report/creator`, `:report/created`)
|
|
- Export returns modal with S3 download link
|
|
|
|
### Filtering and sorting
|
|
- Ledger list filters applied via HTMX on change (500ms debounce) or keyup (1000ms debounce for hot filters)
|
|
- Bank account filter refreshes when client selection changes
|
|
- Sorting supports multiple sort keys with ascending/descending
|
|
- Default sort is date ascending
|
|
- Exact match ID filter bypasses all other filters
|
|
|
|
### Permissions
|
|
- All ledger pages require authenticated user
|
|
- Main ledger: `:read :ledger`
|
|
- New/Edit journal entry: `:edit :ledger`
|
|
- External import: `:import :ledger` + admin assertion
|
|
- P&L report: `:read :profit-and-loss`
|
|
- Balance sheet: `:read :balance-sheet`
|
|
- Cash flows: `:read :cash-flows`
|
|
- Users can only see clients they have permission for (`assert-can-see-client`)
|
|
- Invoice void/edit/unvoid actions require respective invoice permissions
|
|
|
|
## Edge Cases
|
|
|
|
**Empty states**
|
|
- No entries: empty table with pagination showing 0 results
|
|
- No matching filters: empty table, filter pills remain
|
|
- Report with no data: empty report form, no table rendered
|
|
|
|
**Data locking**
|
|
- Journal entries cannot be created for dates on or before client's `locked-until` date
|
|
- External import rejects entries for locked dates
|
|
|
|
**Unbalanced entries**
|
|
- "Show unbalanced" filter computes debit/credit sums per entry and filters to mismatches
|
|
- Unbalanced entries are still displayed in normal view
|
|
|
|
**Account location mismatches**
|
|
- Account with fixed location rejects other locations
|
|
- Account without location restriction rejects "A" (all) location
|
|
- Validation occurs on both frontend (location select) and backend (schema)
|
|
|
|
**Multi-client reports**
|
|
- Capped at 20 clients for performance
|
|
- "All" clients defaults to first 5 for reports
|
|
- Report names include all selected client names
|
|
|
|
**Running balance cache**
|
|
- `refresh-running-balance-cache` recomputes balances for dirty line items
|
|
- Changing a ledger entry marks its line items and subsequent entries as dirty
|
|
- Non-dirty entries are not recomputed
|
|
|
|
## Test Data Requirements
|
|
|
|
**Clients**
|
|
- At least 2 clients with different locations
|
|
- Client with `locked-until` date in the past
|
|
|
|
**Accounts**
|
|
- Asset accounts (11000-11999)
|
|
- Liability accounts (20000-28999)
|
|
- Equity accounts (30000-39999)
|
|
- Revenue accounts (40000-49999)
|
|
- Expense accounts (50000-98999)
|
|
- Accounts with fixed locations
|
|
- Accounts without location restrictions
|
|
|
|
**Vendors**
|
|
- Existing vendors
|
|
- Hidden vendors (auto-created on import)
|
|
|
|
**Journal entries**
|
|
- Entries with debits = credits (balanced)
|
|
- Entries with debits ≠ credits (unbalanced)
|
|
- Entries with multiple line items
|
|
- Entries linked to invoices
|
|
- Entries with external IDs
|
|
- Entries across multiple dates and locations
|
|
|
|
**Bank accounts**
|
|
- Bank accounts linked to clients
|
|
|
|
## Dependencies
|
|
|
|
- `auto-ap.ssr.ledger.common` - Shared grid page config, query schema, filtering
|
|
- `auto-ap.ledger.reports` - Report aggregation and formatting logic
|
|
- `auto-ap.ledger` - Running balance cache, account lookups
|
|
- `auto-ap.datomic.accounts` - Account querying and clientization
|
|
- `auto-ap.permissions` - Permission checks and middleware
|
|
- `auto-ap.ssr.grid-page-helper` - Generic data grid behaviors
|
|
- `auto-ap.ssr.components` - UI components (typeahead, date inputs, buttons)
|
|
- `auto-ap.ssr.form-cursor` - Form state management
|
|
- `clj-pdf` - PDF generation
|
|
- `amazonica.aws.s3` - S3 upload for exports
|
|
- `datomic.api` - Database queries and transactions
|