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.
14 KiB
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-descriptionif 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 :invoicepermission) - Edit button for unpaid/paid invoices (requires
:edit :invoicepermission) - Unvoid button for voided invoices (requires
:edit :invoicepermission) - 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-changetimestamp - 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 :historypermission)
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-balancebefore querying to ensure cached balances are current - Reports use
detailed-account-snapshotDatomic 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-untildate - 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-cacherecomputes 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-untildate 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, filteringauto-ap.ledger.reports- Report aggregation and formatting logicauto-ap.ledger- Running balance cache, account lookupsauto-ap.datomic.accounts- Account querying and clientizationauto-ap.permissions- Permission checks and middlewareauto-ap.ssr.grid-page-helper- Generic data grid behaviorsauto-ap.ssr.components- UI components (typeahead, date inputs, buttons)auto-ap.ssr.form-cursor- Form state managementclj-pdf- PDF generationamazonica.aws.s3- S3 upload for exportsdatomic.api- Database queries and transactions