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:
2026-05-04 13:48:51 -07:00
parent e14a23ff54
commit d627e3c5d0
11 changed files with 2837 additions and 2500 deletions

View File

@@ -4,244 +4,402 @@
The POS (Point of Sale) module provides SSR (HTMX-driven) grid pages for viewing sales data imported from payment processors. All pages share a common grid layout with filters, sortable columns, and pagination. Data is read-only except for the admin Sales Summaries edit wizard.
## 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
| Page | Route (GET) | Table Route (GET) | Source File |
|------|-------------|-------------------|-------------|
| Sales Orders | `/pos/sales` | `/pos/sales/table` | `src/clj/auto_ap/ssr/pos/sales_orders.clj` |
| Expected Deposits | `/pos/expected-deposit` | `/pos/expected-deposit/table` | `src/clj/auto_ap/ssr/pos/expected_deposits.clj` |
| Tenders | `/pos/tenders` | `/pos/tenders/table` | `src/clj/auto_ap/ssr/pos/tenders.clj` |
| Refunds | `/pos/refunds` | `/pos/refunds/table` | `src/clj/auto_ap/ssr/pos/refunds.clj` |
| Cash Drawer Shifts | `/pos/cash-drawer-shifts` | `/pos/cash-drawer-shifts/table` | `src/clj/auto_ap/ssr/pos/cash_drawer_shifts.clj` |
| Sales Summaries (Admin) | `/pos/summaries` | `/pos/summaries/table` | `src/clj/auto_ap/ssr/admin/sales_summaries.clj` |
---
All POS pages appear under the **Sales** section in the main sidebar navigation. Sales Summaries uses the admin sidebar and is restricted to admin users.
## Testing Patterns
## Behaviors by Page
### 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
### Sales Orders
**Test implications:** Validate the query generation, not the rendering. UI tests only need to verify one filter and one sort work end-to-end.
**Purpose**: Display individual sales orders with line items, charges, and payment breakdowns.
### Pattern: Wizard Behaviors
Wizards are multi-step forms with HTMX-driven navigation:
1. Each step is a GET that renders a form fragment
2. Form submissions are POST/PUT with validation
3. Navigation between steps updates the wizard state
4. Final submit creates/updates the entity
**Filters**:
- Date range (start date / end date)
- Total amount range (min / max)
- Payment Method radio cards: All, Cash, Card, Gift Card, Other
- Processor radio cards: All, Square, Doordash, Uber Eats, Grubhub, Koala, EZCater, No Processor
- Category text input (filters by order line item category)
**Test implications:** Unit test validation logic and state transitions. Integration test the full wizard flow once. UI test only the happy path.
**Columns**:
- Client (hidden when viewing a single client)
- Date
- Source (rendered as a pill badge)
- Total
- Tax
- Tip
- Payment Methods (pills for each unique charge type: cash, card, gift card, other)
### Pattern: Permission Gates
Every mutating operation checks:
1. `assert-can-see-client` — user has access to the client
2. `assert-not-locked` — invoice date >= client locked-until
3. `can?` — user has the specific permission for the activity
**Sortable by**: client, date, total, tax, tip, source, processor
**Test implications:** Integration test each gate independently. UI tests only verify the happy path with a permitted user.
**Action Buttons** (above grid): Total $ and Tax $ pills summarizing the currently filtered result set.
---
**Row Buttons**: External link icon when `:sales-order/reference-link` exists.
## Sales Orders
**Special Behaviors**:
- Filter by payment method matches against `charge/type-name` on the order's charges.
- Filter by processor matches against `charge/processor`.
- Filter by category matches against `order-line-item/category`.
- Summation in action buttons uses `auto-ap.datomic.sales-orders/summarize-orders`.
### Display Behaviors
### Expected Deposits
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 1.1 | It should display a table with columns: Client, Date, Source, Total, Tax, Tip, Payment Methods | UI | [ ] |
| 1.2 | It should show the Client column only when multiple clients are in scope | Integration | [ ] |
| 1.3 | It should render the Source column as a pill badge | UI | [ ] |
| 1.4 | It should render each unique payment method as a pill in the Payment Methods column (cash, card, gift card, other) | UI | [ ] |
| 1.5 | It should display action buttons above the grid showing Total $ and Tax $ pills summarizing the currently filtered result set | UI | [ ] |
| 1.6 | It should show an external link icon row button when the sales order has a reference link | UI | [ ] |
**Purpose**: Display expected deposit records from payment processors, with links to transactions.
### Filtering Behaviors
**Filters**:
- Date range
- Exact match ID (shows a removable pill when active)
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 2.1 | It should filter sales orders by date range (start date / end date) | Integration | [ ] |
| 2.2 | It should filter sales orders by total amount range (min / max) | Integration | [ ] |
| 2.3 | It should filter sales orders by payment method via radio cards: All, Cash, Card, Gift Card, Other | Integration | [ ] |
| 2.4 | It should filter sales orders by processor via radio cards: All, Square, Doordash, Uber Eats, Grubhub, Koala, EZCater, No Processor | Integration | [ ] |
| 2.5 | It should filter sales orders by category text input matching order line item category | Integration | [ ] |
| 2.6 | Given multiple filters are applied, when the user changes one filter, then the table should refresh with the combined filter set | Integration | [ ] |
**Columns**:
- Client (hidden when viewing a single client)
- Date
- Sales Date
- Total
- Fee
### Sorting Behaviors
**Sortable by**: client, location, date, total, fee
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 3.1 | It should sort by client name ascending/descending | Integration | [ ] |
| 3.2 | It should sort by date ascending/descending | Integration | [ ] |
| 3.3 | It should sort by total amount ascending/descending | Integration | [ ] |
| 3.4 | It should sort by tax amount ascending/descending | Integration | [ ] |
| 3.5 | It should sort by tip amount ascending/descending | Integration | [ ] |
| 3.6 | It should sort by source ascending/descending | Integration | [ ] |
| 3.7 | It should sort by processor ascending/descending | Integration | [ ] |
| 3.8 | Given the user clicks a column header twice, then the sort direction should toggle | Integration | [ ] |
**Row Buttons**:
- External link icon when `:expected-deposit/reference-link` exists
- "Transaction" button linking to the associated transaction (if `transaction/_expected-deposit` exists)
### Pagination Behaviors
**Special Behaviors**:
- Hydration computes a `:totals` breakdown per expected deposit, aggregating charges by sales date with count and amount.
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 4.1 | It should display 25 sales orders per page by default | Integration | [ ] |
| 4.2 | It should allow changing the per-page count | Integration | [ ] |
| 4.3 | It should calculate the total amount and tax across ALL matching sales orders, not just the current page | Unit | [ ] |
### Tenders
---
**Purpose**: Display individual charge/tender records (payments).
## Expected Deposits
**Filters**:
- Date range
- Processor (same radio options as Sales Orders)
- Total amount range (min / max)
### Display Behaviors
**Columns**:
- Client (hidden when viewing a single client)
- Date
- Total
- Processor (rendered as a pill badge)
- Tip
- Links
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 5.1 | It should display a table with columns: Client, Date, Sales Date, Total, Fee | UI | [ ] |
| 5.2 | It should show the Client column only when multiple clients are in scope | Integration | [ ] |
| 5.3 | It should show a totals breakdown per expected deposit, aggregating charges by sales date with count and amount | UI | [ ] |
| 5.4 | It should show an external link icon row button when the expected deposit has a reference link | UI | [ ] |
| 5.5 | It should show a "Transaction" button linking to the associated transaction when one exists | UI | [ ] |
**Sortable by**: client, date, total, tip, processor
### Filtering Behaviors
**Row Buttons**: External link icon when `:charge/reference-link` exists.
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 6.1 | It should filter expected deposits by date range | Integration | [ ] |
| 6.2 | It should support exact match ID to jump to a specific record, showing a removable pill when active | Integration | [ ] |
| 6.3 | Given multiple filters are applied, when the user changes one filter, then the table should refresh with the combined filter set | Integration | [ ] |
**Special Behaviors**:
- Links column shows an "expected deposit" pill linking to the associated expected deposit if one exists.
### Sorting Behaviors
### Refunds
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 7.1 | It should sort by client name ascending/descending | Integration | [ ] |
| 7.2 | It should sort by location ascending/descending | Integration | [ ] |
| 7.3 | It should sort by date ascending/descending | Integration | [ ] |
| 7.4 | It should sort by total amount ascending/descending | Integration | [ ] |
| 7.5 | It should sort by fee amount ascending/descending | Integration | [ ] |
| 7.6 | Given the user clicks a column header twice, then the sort direction should toggle | Integration | [ ] |
**Purpose**: Display refund records.
### Pagination Behaviors
**Filters**:
- Date range
- Total amount range (min / max)
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.1 | It should display 25 expected deposits per page by default | Integration | [ ] |
| 8.2 | It should allow changing the per-page count | Integration | [ ] |
**Columns**:
- Client (hidden when viewing a single client)
- Date
- Total
- Type
- Fee
---
**Sortable by**: client, date, total, fee, type
## Tenders
**Row Buttons**: None.
### Display Behaviors
### Cash Drawer Shifts
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 9.1 | It should display a table with columns: Client, Date, Total, Processor, Tip, Links | UI | [ ] |
| 9.2 | It should show the Client column only when multiple clients are in scope | Integration | [ ] |
| 9.3 | It should render the Processor column as a pill badge | UI | [ ] |
| 9.4 | It should show an external link icon row button when the tender has a reference link | UI | [ ] |
| 9.5 | It should show an "expected deposit" pill in the Links column when an associated expected deposit exists | UI | [ ] |
**Purpose**: Display cash drawer shift records.
### Filtering Behaviors
**Filters**:
- Date range
- Total amount range (min / max)
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 10.1 | It should filter tenders by date range | Integration | [ ] |
| 10.2 | It should filter tenders by processor via radio cards: All, Square, Doordash, Uber Eats, Grubhub, Koala, EZCater, No Processor | Integration | [ ] |
| 10.3 | It should filter tenders by total amount range (min / max) | Integration | [ ] |
| 10.4 | Given multiple filters are applied, when the user changes one filter, then the table should refresh with the combined filter set | Integration | [ ] |
**Columns**:
- Client (hidden when viewing a single client)
- Date
- Paid in
- Paid out
- Expected cash
- Opened cash
### Sorting Behaviors
**Sortable by**: client, date, paid-in, paid-out, expected-cash, opened-cash
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 11.1 | It should sort by client name ascending/descending | Integration | [ ] |
| 11.2 | It should sort by date ascending/descending | Integration | [ ] |
| 11.3 | It should sort by total amount ascending/descending | Integration | [ ] |
| 11.4 | It should sort by tip amount ascending/descending | Integration | [ ] |
| 11.5 | It should sort by processor ascending/descending | Integration | [ ] |
| 11.6 | Given the user clicks a column header twice, then the sort direction should toggle | Integration | [ ] |
**Row Buttons**: None.
### Pagination Behaviors
### Sales Summaries
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 12.1 | It should display 25 tenders per page by default | Integration | [ ] |
| 12.2 | It should allow changing the per-page count | Integration | [ ] |
**Purpose**: Admin-only daily sales summary view with ledger-mapped debit/credit breakdowns. Supports editing account mappings and manual adjustments.
---
**Access**: Admin sidebar, wrapped with `wrap-admin` middleware.
## Refunds
**Filters**:
- Date range (currently commented out in UI but schema supports it)
### Display Behaviors
**Columns**:
- Client (hidden when viewing a single client)
- Date
- Debits (list of debit-line items with category, amount, and "missing account" warning pill if unmapped)
- Credits (list of credit-line items with category, amount, and "missing account" warning pill if unmapped)
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 13.1 | It should display a table with columns: Client, Date, Total, Type, Fee | UI | [ ] |
| 13.2 | It should show the Client column only when multiple clients are in scope | Integration | [ ] |
**Sortable by**: client, date, debits, credits
### Filtering Behaviors
**Row Buttons**: Edit (pencil) icon opens the edit wizard modal.
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 14.1 | It should filter refunds by date range | Integration | [ ] |
| 14.2 | It should filter refunds by total amount range (min / max) | Integration | [ ] |
| 14.3 | Given multiple filters are applied, when the user changes one filter, then the table should refresh with the combined filter set | Integration | [ ] |
**Edit Wizard Behaviors**:
- Modal dialog titled "New invoice" (legacy title).
- Displays a data grid of summary items with columns: Category, Account, Debits, Credits.
- **Auto items** (non-manual): Category and amount are read-only; only Account is editable via typeahead.
- **Manual items**: Category (text input), Account (typeahead), Debit amount, Credit amount are all editable.
- New manual items can be added via "New Summary Item" row.
- Manual items can be removed via X button.
- Validation: an item cannot have both credit and debit amounts.
- **Total row**: shows running totals for debits and credits.
- **Unbalanced row**: shows the difference when debits ≠ credits.
- Account typeahead searches accounts scoped to the client with purpose "invoice".
- Items missing an account mapping display a red "missing account" pill in the grid view.
- Balanced summaries show a green "Total" pill; unbalanced show a red "Total" pill.
- Save submits an upsert of `:sales-summary/items`, persisting only `ledger-mapped/account` for auto items and full ledger mapping for manual items.
- After save, the row flashes and the modal closes.
### Sorting Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 15.1 | It should sort by client name ascending/descending | Integration | [ ] |
| 15.2 | It should sort by date ascending/descending | Integration | [ ] |
| 15.3 | It should sort by total amount ascending/descending | Integration | [ ] |
| 15.4 | It should sort by fee amount ascending/descending | Integration | [ ] |
| 15.5 | It should sort by type ascending/descending | Integration | [ ] |
| 15.6 | Given the user clicks a column header twice, then the sort direction should toggle | Integration | [ ] |
### Pagination Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 16.1 | It should display 25 refunds per page by default | Integration | [ ] |
| 16.2 | It should allow changing the per-page count | Integration | [ ] |
---
## Cash Drawer Shifts
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 17.1 | It should display a table with columns: Client, Date, Paid in, Paid out, Expected cash, Opened cash | UI | [ ] |
| 17.2 | It should show the Client column only when multiple clients are in scope | Integration | [ ] |
### Filtering Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 18.1 | It should filter cash drawer shifts by date range | Integration | [ ] |
| 18.2 | It should filter cash drawer shifts by total amount range (min / max) | Integration | [ ] |
| 18.3 | 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 |
|---|----------|---------------|--------|
| 19.1 | It should sort by client name ascending/descending | Integration | [ ] |
| 19.2 | It should sort by date ascending/descending | Integration | [ ] |
| 19.3 | It should sort by paid-in amount ascending/descending | Integration | [ ] |
| 19.4 | It should sort by paid-out amount ascending/descending | Integration | [ ] |
| 19.5 | It should sort by expected-cash amount ascending/descending | Integration | [ ] |
| 19.6 | It should sort by opened-cash amount ascending/descending | Integration | [ ] |
| 19.7 | Given the user clicks a column header twice, then the sort direction should toggle | Integration | [ ] |
### Pagination Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 20.1 | It should display 25 cash drawer shifts per page by default | Integration | [ ] |
| 20.2 | It should allow changing the per-page count | Integration | [ ] |
---
## Sales Summaries
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 21.1 | It should display a table with columns: Client, Date, Debits, Credits | UI | [ ] |
| 21.2 | It should show the Client column only when multiple clients are in scope | Integration | [ ] |
| 21.3 | It should display debit-line items with category and amount | UI | [ ] |
| 21.4 | It should display credit-line items with category and amount | UI | [ ] |
| 21.5 | It should show a red "missing account" warning pill for unmapped items | UI | [ ] |
| 21.6 | It should show a green "Total" pill for balanced summaries (debits equal credits) | UI | [ ] |
| 21.7 | It should show a red "Total" pill for unbalanced summaries (debits do not equal credits) | UI | [ ] |
| 21.8 | It should show an edit (pencil) icon row button opening the edit wizard modal | UI | [ ] |
### Filtering Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 22.1 | It should filter sales summaries by date range | Integration | [ ] |
| 22.2 | 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 |
|---|----------|---------------|--------|
| 23.1 | It should sort by client name ascending/descending | Integration | [ ] |
| 23.2 | It should sort by date ascending/descending | Integration | [ ] |
| 23.3 | It should sort by debits ascending/descending | Integration | [ ] |
| 23.4 | It should sort by credits ascending/descending | Integration | [ ] |
| 23.5 | Given the user clicks a column header twice, then the sort direction should toggle | Integration | [ ] |
### Pagination Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 24.1 | It should display 25 sales summaries per page by default | Integration | [ ] |
| 24.2 | It should allow changing the per-page count | Integration | [ ] |
### Edit Wizard Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 25.1 | It should open a modal dialog when the edit button is clicked | UI | [ ] |
| 25.2 | It should display a data grid of summary items with columns: Category, Account, Debits, Credits | UI | [ ] |
| 25.3 | It should render auto items (non-manual) with read-only Category and amount, editable Account via typeahead | UI | [ ] |
| 25.4 | It should render manual items with editable Category (text input), Account (typeahead), Debit amount, and Credit amount | UI | [ ] |
| 25.5 | It should allow adding new manual items via a "New Summary Item" row | UI | [ ] |
| 25.6 | It should allow removing manual items via an X button | UI | [ ] |
| 25.7 | It should validate that an item cannot have both credit and debit amounts | Unit + Integration | [ ] |
| 25.8 | It should display a total row with running totals for debits and credits | UI | [ ] |
| 25.9 | It should display an unbalanced row showing the difference when debits do not equal credits | UI | [ ] |
| 25.10 | It should search accounts scoped to the client with purpose "invoice" in the account typeahead | Integration | [ ] |
| 25.11 | It should flash the row and close the modal after successful save | UI | [ ] |
---
## Cross-Cutting Behaviors
### Filtering, Sorting, Pagination
### HTMX Live Filtering Behaviors
**HTMX Live Filtering**:
- All filter forms use `hx-trigger="change delay:500ms, keyup changed from:.hot-filter delay:1000ms"`.
- Form changes POST to the table route and target the table container.
- The table route swaps the grid contents and updates the browser URL via `hx-push-url`.
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 26.1 | It should trigger table refresh on filter form change with a 500ms debounce | Integration | [ ] |
| 26.2 | It should trigger table refresh on hot-filter keyup with a 1000ms debounce | Integration | [ ] |
| 26.3 | It should POST to the table route and swap the grid contents | Integration | [ ] |
| 26.4 | It should update the browser URL via hx-push-url when filters change | Integration | [ ] |
**Date Range**:
- All pages support `start-date` and `end-date` query params.
- The date range filter is rendered by `auto-ap.ssr.pos.common/date-range-field*`.
### Date Range Behaviors
**Total Range**:
- Most pages support `total-gte` and `total-lte` (money inputs).
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 27.1 | It should support start-date and end-date query params on all pages | Integration | [ ] |
| 27.2 | It should render the date range filter consistently across all pages | UI | [ ] |
| 27.3 | Given a date range with no start or end date, then the query should use scan functions with nil boundaries | Integration | [ ] |
**Exact Match ID**:
- Most pages support `exact-match-id` to jump to a specific record.
### Total Range Behaviors
**Sorting**:
- Click a sortable column header to toggle ascending/descending.
- Multi-sort is supported; active sorts appear as removable pills above the grid.
- Remove a sort by clicking the X on its pill.
- Default sort is by date descending for most pages.
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 28.1 | It should support total-gte and total-lte query params on pages with amount filters | Integration | [ ] |
| 28.2 | It should render money inputs for the total range filter | UI | [ ] |
**Pagination**:
- Default 25 rows per page.
- Controls include first/previous/next/last and per-page selector.
- Total count is displayed above the grid.
### Exact Match ID Behaviors
**Client Scoping**:
- All queries are scoped to the user's accessible clients (trimmed to max 20).
- Client column is automatically hidden when only one client is in scope.
- URL may include `client-id` or `client-code` params.
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 29.1 | It should support exact-match-id to jump to a specific record on applicable pages | Integration | [ ] |
| 29.2 | It should show a removable pill when exact-match-id is active | UI | [ ] |
### Permissions
### Sorting Behaviors
- POS pages require `(can? identity {:subject :sales :activity :read})`.
- Sales Summaries requires admin access (`wrap-admin`).
- All routes redirect unauthenticated users.
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 30.1 | It should toggle ascending/descending sort when a sortable column header is clicked | Integration | [ ] |
| 30.2 | It should support multi-sort with active sorts appearing as removable pills above the grid | Integration | [ ] |
| 30.3 | It should remove a sort when the X on its pill is clicked | Integration | [ ] |
| 30.4 | It should default to sort by date descending for most pages | Integration | [ ] |
## Edge Cases
### Pagination Behaviors
- **No results**: Grid displays "Total X: 0" with empty rows.
- **Single client view**: Client column is hidden; filters and summaries apply to that client's data only.
- **Missing reference links**: External link buttons are omitted when no URL exists.
- **Missing expected deposit link on Tenders**: Links cell is empty when no associated expected deposit exists.
- **Missing transaction on Expected Deposits**: Transaction button is omitted when no linked transaction exists.
- **Sales Summaries missing accounts**: Red "missing account" pills appear; totals show red when debits ≠ credits.
- **Manual item credit/debit both filled**: Form validation rejects with "Must choose one of credit/debit".
- **Date range with no start or end**: Query uses `scan-*` ion functions with nil boundaries.
- **Category filter on Sales Orders**: Empty string or nil category param is treated as no filter.
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 31.1 | It should display first/previous/next/last pagination controls | UI | [ ] |
| 31.2 | It should display the total count above the grid | UI | [ ] |
### Client Scoping Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 32.1 | It should scope all queries to the user's accessible clients (trimmed to max 20) | Integration | [ ] |
| 32.2 | It should hide the Client column when only one client is in scope | Integration | [ ] |
| 32.3 | It should support client-id and client-code URL params | Integration | [ ] |
### Permission Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 33.1 | It should require `(can? identity {:subject :sales :activity :read})` to access POS pages | Integration | [ ] |
| 33.2 | It should require admin access (`wrap-admin`) to access Sales Summaries | Integration | [ ] |
| 33.3 | It should redirect unauthenticated users | Integration | [ ] |
---
## Test Data Requirements
- Clients with `db/id`, `client/name`, `client/code`.
- Sales orders with: `:sales-order/date`, `:sales-order/total`, `:sales-order/tax`, `:sales-order/tip`, `:sales-order/source`, `:sales-order/charges` (with `charge/type-name`, `charge/processor`), `:sales-order/line-items` (with `order-line-item/category`).
- Expected deposits with: `:expected-deposit/date`, `:expected-deposit/total`, `:expected-deposit/fee`, `:expected-deposit/client`, optional `transaction/_expected-deposit`.
- Tenders (charges) with: `:charge/date`, `:charge/total`, `:charge/tip`, `:charge/processor`, optional `expected-deposit/_charges`.
- Refunds with: `:sales-refund/date`, `:sales-refund/total`, `:sales-refund/fee`, `:sales-refund/type`.
- Cash drawer shifts with: `:cash-drawer-shift/date`, `:cash-drawer-shift/paid-in`, `:cash-drawer-shift/paid-out`, `:cash-drawer-shift/expected-cash`, `:cash-drawer-shift/opened-cash`.
- Sales summaries with: `:sales-summary/date`, `:sales-summary/client`, `:sales-summary/items` (with `ledger-mapped/ledger-side`, `ledger-mapped/amount`, `sales-summary-item/category`, optional `ledger-mapped/account`).
| Entity | Requirements |
|--------|-------------|
| **Clients** | Multiple clients with `db/id`, `client/name`, `client/code`; some with locked-until dates |
| **Sales Orders** | With `:sales-order/date`, `:sales-order/total`, `:sales-order/tax`, `:sales-order/tip`, `:sales-order/source`, `:sales-order/charges` (with `charge/type-name`, `charge/processor`), `:sales-order/line-items` (with `order-line-item/category`) |
| **Expected Deposits** | With `:expected-deposit/date`, `:expected-deposit/total`, `:expected-deposit/fee`, `:expected-deposit/client`, optional `transaction/_expected-deposit` |
| **Tenders (Charges)** | With `:charge/date`, `:charge/total`, `:charge/tip`, `:charge/processor`, optional `expected-deposit/_charges` |
| **Refunds** | With `:sales-refund/date`, `:sales-refund/total`, `:sales-refund/fee`, `:sales-refund/type` |
| **Cash Drawer Shifts** | With `:cash-drawer-shift/date`, `:cash-drawer-shift/paid-in`, `:cash-drawer-shift/paid-out`, `:cash-drawer-shift/expected-cash`, `:cash-drawer-shift/opened-cash` |
| **Sales Summaries** | With `:sales-summary/date`, `:sales-summary/client`, `:sales-summary/items` (with `ledger-mapped/ledger-side`, `ledger-mapped/amount`, `sales-summary-item/category`, optional `ledger-mapped/account`) |
| **Accounts** | With purpose "invoice", scoped to clients, for Sales Summaries edit wizard |
## Existing Tests to Preserve
- `test/clj/auto_ap/ssr/pos/sales_orders_test.clj` — Sales orders grid behaviors
- `test/clj/auto_ap/ssr/pos/expected_deposits_test.clj` — Expected deposits grid behaviors
- `test/clj/auto_ap/ssr/pos/tenders_test.clj` — Tenders grid behaviors
- `test/clj/auto_ap/ssr/pos/refunds_test.clj` — Refunds grid behaviors
- `test/clj/auto_ap/ssr/pos/cash_drawer_shifts_test.clj` — Cash drawer shifts grid behaviors
- `test/clj/auto_ap/ssr/admin/sales_summaries_test.clj` — Sales summaries admin behaviors
## Dependencies
- **Grid system**: `auto-ap.ssr.grid-page-helper` provides `build`, `page-route`, `table-route`, `row*`, `table*`.
- **Components**: `auto-ap.ssr.components` for data grids, pills, buttons, inputs.
- **Querying**: `auto-ap.datomic` for `query2`, `pull-many`, `apply-pagination`, `apply-sort-3`.
- **Ions**: `iol-ion.query/scan-sales-orders`, `scan-expected-deposits`, `scan-charges`, `scan-sales-refunds`, `scan-cash-drawer-shifts`.
- **Permissions**: `auto-ap.permissions/can?`.
- **Time**: `auto-ap.time` for date formatting and localization.
- **Schema validation**: Malli schemas enforce query params on every request.
- Datomic (primary store)
- HTMX/Alpine.js (frontend interactivity)
- Grid system: `auto-ap.ssr.grid-page-helper` provides `build`, `page-route`, `table-route`, `row*`, `table*`
- Components: `auto-ap.ssr.components` for data grids, pills, buttons, inputs
- Querying: `auto-ap.datomic` for `query2`, `pull-many`, `apply-pagination`, `apply-sort-3`
- Ions: `iol-ion.query/scan-sales-orders`, `scan-expected-deposits`, `scan-charges`, `scan-sales-refunds`, `scan-cash-drawer-shifts`
- Permissions: `auto-ap.permissions/can?`
- Time: `auto-ap.time` for date formatting and localization
- Schema validation: Malli schemas enforce query params on every request