6 Commits

Author SHA1 Message Date
8528be9faa Add Bonanza Produce multi-invoice statement template
- Added multi-invoice template for Bonanza Produce with :multi and :multi-match? flags
- Template uses keywords for statement header to identify multi-invoice format
- Extracts invoice-number, date, customer-identifier (from RETURN line), and total
- Parses 4 invoices from statement PDF 13595522.pdf
- All tests pass (29 assertions, 0 failures, 0 errors)

- Added test: parse-bonanza-produce-statement-13595522
- Updated invoice-template-creator skill: emphasized test-first approach
2026-02-08 07:33:50 -08:00
9b0382e587 Add invoice-template-creator skill for automated template generation
New repository-based skill at .claude/skills/invoice-template-creator/:
- SKILL.md: Complete guide for creating invoice parsing templates
- references/examples.md: Common patterns and template examples
- Covers vendor identification, regex patterns, field extraction
- Includes testing strategies and common pitfalls

Updated AGENTS.md with reference to the new skill.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-08 07:33:46 -08:00
ef64d5fc41 Extract customer name and address for Bonanza Produce
- customer-identifier field: customer name (e.g., 'NICK THE GREEK')
- account-number field: street address (e.g., '600 VISTA WAY')
- Combined they provide full customer identification with address
- Updated test to verify both fields and their concatenation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-08 07:33:33 -08:00
13ac73c7c5 Add location extraction for Bonanza Produce invoices
- Extract city/state/zip in location field
- Customer address now split across 3 fields:
  - customer-identifier: customer name
  - account-number: street address
  - location: city, state zip
- All components verified in test

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-08 07:33:30 -08:00
f51f4a0b8e Improve Bonanza Produce customer identifier extraction
- Extract customer name in customer-identifier field
- Extract street address in account-number field
- Use non-greedy regex with lookahead to capture clean values
- Update test to verify both name and address extraction

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-08 07:33:10 -08:00
9283f9238f Add Bonanza Produce invoice template
- Add new PDF template for Bonanza Produce vendor
- Template uses phone number 530-544-4136 as unique identifier
- Extracts invoice number, date, customer identifier, and total
- Includes passing test for invoice 03881260

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-08 07:33:05 -08:00
29 changed files with 44 additions and 5542 deletions

View File

@@ -1,174 +0,0 @@
---
name: clojure-eval
description: Evaluate Clojure code via nREPL using clj-nrepl-eval. Use this when you need to test code, check if edited files compile, verify function behavior, or interact with a running REPL session.
---
# Clojure REPL Evaluation
## When to Use This Skill
Use this skill when you need to:
- **Verify that edited Clojure files compile and load correctly**
- Test function behavior interactively
- Check the current state of the REPL
- Debug code by evaluating expressions
- Require or load namespaces for testing
- Validate that code changes work before committing
## How It Works
The `clj-nrepl-eval` command evaluates Clojure code against an nREPL server. **Session state persists between evaluations**, so you can require a namespace in one evaluation and use it in subsequent calls. Each host:port combination maintains its own session file.
## Instructions
### 0. Discover and select nREPL server
First, discover what nREPL servers are running in the current directory:
```bash
clj-nrepl-eval --discover-ports
```
This will show all nREPL servers (Clojure, Babashka, shadow-cljs, etc.) running in the current project directory.
**Then use the AskUserQuestion tool:**
- **If ports are discovered:** Prompt user to select which nREPL port to use:
- **question:** "Which nREPL port would you like to use?"
- **header:** "nREPL Port"
- **options:** Present each discovered port as an option with:
- **label:** The port number
- **description:** The server type and status (e.g., "Clojure nREPL server in current directory")
- Include up to 4 discovered ports as options
- The user can select "Other" to enter a custom port number
- **If no ports are discovered:** Prompt user how to start an nREPL server:
- **question:** "No nREPL servers found. How would you like to start one?"
- **header:** "Start nREPL"
- **options:**
- **label:** "deps.edn alias", **description:** "Find and use an nREPL alias in deps.edn"
- **label:** "Leiningen", **description:** "Start nREPL using 'lein repl'"
- The user can select "Other" for alternative methods or if they already have a server running on a specific port
IMPORTANT: IF you start a REPL do not supply a port let the nREPL start and return the port that it was started on.
### 1. Evaluate Clojure Code
> Evaluation automatically connects to the given port
Use the `-p` flag to specify the port and pass your Clojure code.
**Recommended: Pass code as a command-line argument:**
```bash
clj-nrepl-eval -p <PORT> "(+ 1 2 3)"
```
**For multiple expressions (single line):**
```bash
clj-nrepl-eval -p <PORT> "(def x 10) (+ x 20)"
```
**Alternative: Using heredoc (may require permission approval for multiline commands):**
```bash
clj-nrepl-eval -p <PORT> <<'EOF'
(def x 10)
(+ x 20)
EOF
```
**Alternative: Via stdin pipe:**
```bash
echo "(+ 1 2 3)" | clj-nrepl-eval -p <PORT>
```
### 2. Display nREPL Sessions
**Discover all nREPL servers in current directory:**
```bash
clj-nrepl-eval --discover-ports
```
Shows all running nREPL servers in the current project directory, including their type (clj/bb/basilisp) and whether they match the current working directory.
**Check previously connected sessions:**
```bash
clj-nrepl-eval --connected-ports
```
Shows only connections you have made before (appears after first evaluation on a port).
### 3. Common Patterns
**Require a namespace (always use :reload to pick up changes):**
```bash
clj-nrepl-eval -p <PORT> "(require '[my.namespace :as ns] :reload)"
```
**Test a function after requiring:**
```bash
clj-nrepl-eval -p <PORT> "(ns/my-function arg1 arg2)"
```
**Check if a file compiles:**
```bash
clj-nrepl-eval -p <PORT> "(require 'my.namespace :reload)"
```
**Multiple expressions:**
```bash
clj-nrepl-eval -p <PORT> "(def x 10) (* x 2) (+ x 5)"
```
**Complex multiline code (using heredoc):**
```bash
clj-nrepl-eval -p <PORT> <<'EOF'
(def x 10)
(* x 2)
(+ x 5)
EOF
```
*Note: Heredoc syntax may require permission approval.*
**With custom timeout (in milliseconds):**
```bash
clj-nrepl-eval -p <PORT> --timeout 5000 "(long-running-fn)"
```
**Reset the session (clears all state):**
```bash
clj-nrepl-eval -p <PORT> --reset-session
clj-nrepl-eval -p <PORT> --reset-session "(def x 1)"
```
## Available Options
- `-p, --port PORT` - nREPL port (required)
- `-H, --host HOST` - nREPL host (default: 127.0.0.1)
- `-t, --timeout MILLISECONDS` - Timeout (default: 120000 = 2 minutes)
- `-r, --reset-session` - Reset the persistent nREPL session
- `-c, --connected-ports` - List previously connected nREPL sessions
- `-d, --discover-ports` - Discover nREPL servers in current directory
- `-h, --help` - Show help message
## Important Notes
- **Prefer command-line arguments:** Pass code as quoted strings: `clj-nrepl-eval -p <PORT> "(+ 1 2 3)"` - works with existing permissions
- **Heredoc for complex code:** Use heredoc (`<<'EOF' ... EOF`) for truly multiline code, but note it may require permission approval
- **Sessions persist:** State (vars, namespaces, loaded libraries) persists across invocations until the nREPL server restarts or `--reset-session` is used
- **Automatic delimiter repair:** The tool automatically repairs missing or mismatched parentheses
- **Always use :reload:** When requiring namespaces, use `:reload` to pick up recent changes
- **Default timeout:** 2 minutes (120000ms) - increase for long-running operations
- **Input precedence:** Command-line arguments take precedence over stdin
## Typical Workflow
1. Discover nREPL servers: `clj-nrepl-eval --discover-ports`
2. Use **AskUserQuestion** tool to prompt user to select a port
3. Require namespace:
```bash
clj-nrepl-eval -p <PORT> "(require '[my.ns :as ns] :reload)"
```
4. Test function:
```bash
clj-nrepl-eval -p <PORT> "(ns/my-fn ...)"
```
5. Iterate: Make changes, re-require with `:reload`, test again

View File

@@ -1,82 +0,0 @@
# clj-nrepl-eval Examples
## Discovery
```bash
clj-nrepl-eval --connected-ports
```
## Heredoc for Multiline Code
```bash
clj-nrepl-eval -p 7888 <<'EOF'
(defn greet [name]
(str "Hello, " name "!"))
(greet "Claude")
EOF
```
### Heredoc Simplifies String Escaping
Heredoc avoids shell escaping issues with quotes, backslashes, and special characters:
```bash
# With heredoc - no escaping needed
clj-nrepl-eval -p 7888 <<'EOF'
(def regex #"\\d{3}-\\d{4}")
(def message "She said \"Hello!\" and waved")
(def path "C:\\Users\\name\\file.txt")
(println message)
EOF
# Without heredoc - requires complex escaping
clj-nrepl-eval -p 7888 "(def message \"She said \\\"Hello!\\\" and waved\")"
```
## Working with Project Namespaces
```bash
# Test a function after requiring
clj-nrepl-eval -p 7888 <<'EOF'
(require '[clojure-mcp-light.delimiter-repair :as dr] :reload)
(dr/delimiter-error? "(defn foo [x]")
EOF
```
## Verify Compilation After Edit
```bash
# If this returns nil, the file compiled successfully
clj-nrepl-eval -p 7888 "(require 'clojure-mcp-light.hook :reload)"
```
## Session Management
```bash
# Reset session if state becomes corrupted
clj-nrepl-eval -p 7888 --reset-session
```
## Common Workflow Patterns
### Load, Test, Iterate
```bash
# After editing a file, reload and test in one command
clj-nrepl-eval -p 7888 <<'EOF'
(require '[my.namespace :as ns] :reload)
(ns/my-function test-data)
EOF
```
### Run Tests After Changes
```bash
clj-nrepl-eval -p 7888 <<'EOF'
(require '[my.project.core :as core] :reload)
(require '[my.project.core-test :as test] :reload)
(clojure.test/run-tests 'my.project.core-test)
EOF
```

1
.env
View File

@@ -1 +0,0 @@
OPENROUTER_API_KEY=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

1
.envrc
View File

@@ -1 +0,0 @@
export OPENROUTER_API_KEY=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

3
.gitattributes vendored
View File

@@ -1,3 +0,0 @@
# Use bd merge for beads JSONL files
.beads/issues.jsonl merge=beads

View File

@@ -1,376 +0,0 @@
{
"name": ".opencode",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@opencode-ai/plugin": "1.14.31"
}
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
"integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz",
"integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz",
"integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz",
"integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
"integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz",
"integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@opencode-ai/plugin": {
"version": "1.14.31",
"resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.14.31.tgz",
"integrity": "sha512-ZF7UoNKtZDtgW/2KrcFw5I7R2HRj/NigBuRwKPonvSZS36LnghZ7PYcXYZFGCjEgBmLUMMrLVgxccKLyxsgB0g==",
"license": "MIT",
"dependencies": {
"@opencode-ai/sdk": "1.14.31",
"effect": "4.0.0-beta.57",
"zod": "4.1.8"
},
"peerDependencies": {
"@opentui/core": ">=0.2.0",
"@opentui/solid": ">=0.2.0"
},
"peerDependenciesMeta": {
"@opentui/core": {
"optional": true
},
"@opentui/solid": {
"optional": true
}
}
},
"node_modules/@opencode-ai/sdk": {
"version": "1.14.31",
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.14.31.tgz",
"integrity": "sha512-QaV+ti3NYUITmgIDqtNMqGIYBXJOx2zheN1g+7w4HC8QQsbaW1c7glxXExQHRbdUzcQPP2vUQhnXOcEsTw5CcQ==",
"license": "MIT",
"dependencies": {
"cross-spawn": "7.0.6"
}
},
"node_modules/@standard-schema/spec": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/effect": {
"version": "4.0.0-beta.57",
"resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.57.tgz",
"integrity": "sha512-rg32VgXnLKaPRs9tbRDaZ5jxmzNY7ojXt85gSHGUTwdlbWH5Ik+OCUY2q14TXliygPGoHwCAvNWS4bQJOqf00g==",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.1.0",
"fast-check": "^4.6.0",
"find-my-way-ts": "^0.1.6",
"ini": "^6.0.0",
"kubernetes-types": "^1.30.0",
"msgpackr": "^1.11.9",
"multipasta": "^0.2.7",
"toml": "^4.1.1",
"uuid": "^13.0.0",
"yaml": "^2.8.3"
}
},
"node_modules/fast-check": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz",
"integrity": "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/dubzzz"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fast-check"
}
],
"license": "MIT",
"dependencies": {
"pure-rand": "^8.0.0"
},
"engines": {
"node": ">=12.17.0"
}
},
"node_modules/find-my-way-ts": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz",
"integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==",
"license": "MIT"
},
"node_modules/ini": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz",
"integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==",
"license": "ISC",
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/kubernetes-types": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/kubernetes-types/-/kubernetes-types-1.30.0.tgz",
"integrity": "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==",
"license": "Apache-2.0"
},
"node_modules/msgpackr": {
"version": "1.11.10",
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.10.tgz",
"integrity": "sha512-iCZNq+HszvF+fC3anCm4nBmWEnbeIAfpDs6IStAEKhQ2YSgkjzVG2FF9XJqwwQh5bH3N9OUTUt4QwVN6MLMLtA==",
"license": "MIT",
"optionalDependencies": {
"msgpackr-extract": "^3.0.2"
}
},
"node_modules/msgpackr-extract": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz",
"integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"node-gyp-build-optional-packages": "5.2.2"
},
"bin": {
"download-msgpackr-prebuilds": "bin/download-prebuilds.js"
},
"optionalDependencies": {
"@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3",
"@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3",
"@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3",
"@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3",
"@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3",
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3"
}
},
"node_modules/multipasta": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.7.tgz",
"integrity": "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==",
"license": "MIT"
},
"node_modules/node-gyp-build-optional-packages": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
"integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
"license": "MIT",
"optional": true,
"dependencies": {
"detect-libc": "^2.0.1"
},
"bin": {
"node-gyp-build-optional-packages": "bin.js",
"node-gyp-build-optional-packages-optional": "optional.js",
"node-gyp-build-optional-packages-test": "build-test.js"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/pure-rand": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz",
"integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/dubzzz"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fast-check"
}
],
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/toml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/toml/-/toml-4.1.1.tgz",
"integrity": "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==",
"license": "MIT",
"engines": {
"node": ">=20"
}
},
"node_modules/uuid": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.1.tgz",
"integrity": "sha512-9ezox2roIft6ExBVTVqibSd5dc5/47Sw/uY6b4SjQUT2TzQ0tltNquWA46y4xPQmdZYqvnio22SgWd41M86+jw==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist-node/bin/uuid"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/yaml": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
},
"node_modules/zod": {
"version": "4.1.8",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View File

@@ -1,127 +0,0 @@
---
name: gitea-tea
description: Use tea CLI to create, manage, and checkout Gitea pull requests. Use this when opening a PR, managing PRs, or checking out PRs on the gitea remote (gitea.story-basking.ts.net).
---
# Gitea Tea CLI Skill
This skill covers using `tea` (Gitea's official CLI) for pull request workflows in this project.
## When to Use This Skill
Use this skill when you need to:
- Create a PR from a working branch to master on the gitea remote
- Open, list, or view PRs
- Checkout a PR locally for review or iteration
- Manage PR state (close, reopen, merge)
## Project Setup
The gitea remote is `gitea.story-basking.ts.net` with repo slug `notid/integreat`. The default push remote is **gitea**, NOT origin and NOT deploy.
In this project's environment:
- Gitea login is pre-configured for `gitea.story-basking.ts.net`
- Repo slug: `notid/integreat`
- Target branch for PRs: `master`
- The git remote named `gitea` points to this instance
## Creating a PR
Use `tea pulls create` to open a PR from the current branch to master. Always specify `-r notid/integreat -b master`:
```bash
tea pulls create -r notid/integreat -b master --title "Title" --description "Body"
```
Common flags:
- `-t, --title` - PR title
- `-d, --description` - PR body/description (use heredoc or file for long descriptions)
- `-a, --assignees` - Comma-separated usernames to assign
- `-L, --labels` - Comma-separated labels to apply
- `-m, --milestone` - Milestone to assign
**Writing a multiline description:**
```bash
tea pulls create -r notid/integreat -b master \
-t "feat: add feature" \
-d "$(cat <<'EOF'
## Summary
- Bullet point one
- Bullet point two
EOF
)"
```
Or write the body to a temp file first and reference it.
## Listing PRs
```bash
tea pulls list -r notid/integreat # List open PRs
tea pulls list -r notid/integreat --state all # All PRs
tea pulls list -r notid/integreat --limit 10 -o simple # Limit output, simple format
```
## Opening a PR in Browser
```bash
tea open pr <number> -r notid/integreat
tea open pr create -r notid/integreat # Open web UI to create a PR
```
## Checking Out a PR Locally
```bash
tea pulls checkout <number> -r notid/integreat
```
This fetches and checks out the PR branch locally.
## Managing PR State
**Close a PR:**
```bash
tea pulls close <number> -r notid/integreat --confirm
```
**Reopen a closed PR:**
```bash
tea pulls reopen <number> -r notid/integreat --confirm
```
**Merge a PR:**
```bash
tea pulls merge <number> -r notid/integreat --confirm
```
**Edit a PR (title, description, etc.):**
```bash
tea pulls edit <number> -r notid/integreat --title "New title" --description "New body"
```
## Full PR Creation Workflow
1. Ensure the branch is pushed to gitea:
```bash
git push gitea <branch-name>
```
2. Create the PR with tea:
```bash
tea pulls create -r notid/integreat -b master \
--title "feat: description of change" \
--description "Detailed PR body here"
```
3. Open the PR in browser to verify:
```bash
tea open pr <number> -r notid/integreat
```
## Tips
- Always use `-r notid/integreat` to specify the repo explicitly
- Use `-b master` to set the target branch (default may differ)
- The `--confirm` flag is required for destructive actions (close, merge)
- Use `-o simple`, `-o json`, `-o table`, etc. to control output format

View File

@@ -1,9 +0,0 @@
## Pull Requests on Gitea
This project uses **gitea story-basking.ts net** as the primary remote for PRs. Use `tea` (the Gitea CLI) to create and manage pull requests. The gitea remote is the one you push to, NOT origin and NOT deploy.
**When opening a PR**, load and follow the **gitea-tea** skill. In short:
- Target branch is always `master`
- Use `tea pulls create -r notid/integreat -b master --title "..." --description "..."`
Use 'bd' for task tracking

Binary file not shown.

View File

@@ -1,376 +0,0 @@
---
title: Rebase Invoice Templates, Merge to Master, and Integrate Branches
type: refactor
date: 2026-02-08
---
# Rebase Invoice Templates, Merge to Master, and Integrate Branches
## Overview
This plan outlines a series of git operations to reorganize the branch structure by:
1. Creating a rebase commit with all invoice template changes
2. Applying those changes onto `master`
3. Removing them from the current `clauding` branch
4. Merging `master` back into `clauding`
5. Finally merging `clauding` into `get-transactions2-page-working`
## Current State
### Branch Structure (as of Feb 8, 2026)
```
master (dc021b8c)
├─ deploy/master (dc021b8c)
└─ (other branches)
└─ clauding (0155d91e) - HEAD
├─ 16 commits ahead of master
└─ Contains invoice template work for Bonanza Produce
├─ db1cb194 Add Bonanza Produce invoice template
├─ ec754233 Improve Bonanza Produce customer identifier extraction
├─ af7bc324 Add location extraction for Bonanza Produce invoices
├─ 62107c99 Extract customer name and address for Bonanza Produce
├─ 7ecd569e Add invoice-template-creator skill for automated template generation
└─ 0155d91e Add Bonanza Produce multi-invoice statement template
```
### Merge Base
- **Merge base between `clauding` and `master`**: `dc021b8c`
- **Commits on `clauding` since merge base**: 16 commits
- **Invoice template commits**: 6 commits (db1cb194 through 0155d91e)
## Problem Statement
The current branch structure has:
1. Invoice template work mixed with other feature development in `clauding`
2. No clear separation between invoice template changes and transaction page work
3. A desire to get invoice template changes merged to `master` independently
4. A need to reorganize branches to prepare for merging `get-transactions2-page-working`
## Proposed Solution
Use git rebase and merge operations to create a cleaner branch hierarchy:
1. **Create a new branch** (`invoice-templates-rebased`) with only invoice template commits
2. **Rebase those commits** onto current `master`
3. **Merge** this clean branch to `master`
4. **Remove invoice template commits** from `clauding` branch
5. **Merge `master` into `clauding`** to sync
6. **Merge `clauding` into `get-transactions2-page-working`**
## Implementation Steps
### Phase 1: Extract and Rebase Invoice Templates
#### Step 1.1: Identify Invoice Template Commits
```bash
# From clauding branch, find the range of invoice template commits
git log --oneline --reverse dc021b8c..clauding
```
**Invoice template commits to extract** (6 commits in order):
1. `db1cb194` - Add Bonanza Produce invoice template
2. `ec754233` - Improve Bonanza Produce customer identifier extraction
3. `af7bc324` - Add location extraction for Bonanza Produce invoices
4. `62107c99` - Extract customer name and address for Bonanza Produce
5. `7ecd569e` - Add invoice-template-creator skill for automated template generation
6. `0155d91e` - Add Bonanza Produce multi-invoice statement template
#### Step 1.2: Create Rebased Branch
```bash
# Create a new branch from master with only invoice template commits
git checkout master
git pull origin master # Ensure master is up to date
git checkout -b invoice-templates-rebased
# Cherry-pick the invoice template commits in order
git cherry-pick db1cb194
git cherry-pick ec754233
git cherry-pick af7bc324
git cherry-pick 62107c99
git cherry-pick 7ecd569e
git cherry-pick 0155d91e
# Resolve any conflicts that arise during cherry-pick
# Run tests after each cherry-pick if conflicts occur
```
#### Step 1.3: Verify Rebased Branch
```bash
# Verify the commits are correctly applied
git log --oneline master..invoice-templates-rebased
# Run tests to ensure invoice templates still work
lein test auto-ap.parse.templates-test
```
#### Step 1.4: Merge to Master
```bash
# Merge the clean invoice templates to master
git checkout master
git merge invoice-templates-rebased --no-edit
# Push to remote
git push origin master
```
### Phase 2: Clean Up Clauding Branch
#### Step 2.1: Remove Invoice Template Commits from Clauding
```bash
# From clauding branch, find the commit before the first invoice template
git log --oneline clauding | grep -B1 "db1cb194"
# Suppose that's commit X, rebase clauding to remove invoice templates
git checkout clauding
# Option A: Interactive rebase (recommended for cleanup)
git rebase -i <commit-before-invoice-templates>
# In the editor, delete lines corresponding to invoice template commits:
# db1cb194
# ec754233
# af7bc324
# 62107c99
# 7ecd569e
# 0155d91e
# Save and exit to rebase
# Resolve any conflicts that occur
# Run tests after rebase
```
**OR**
```bash
# Option B: Hard reset to commit before invoice templates
# Identify the commit hash before db1cb194 (let's call it COMMIT_X)
git reset --hard COMMIT_X
# Then add back any non-invoice template commits from clauding
# (commits after the invoice templates that should remain)
git cherry-pick <non-invoice-commits-if-any>
```
#### Step 2.2: Verify Clauding Branch Cleanup
```bash
# Verify invoice template commits are removed
git log --oneline | grep -i "bonanza" # Should be empty
# Verify other commits remain
git log --oneline -20
# Run tests to ensure nothing broke
lein test
```
#### Step 2.3: Force Push Updated Clauding
```bash
# Force push the cleaned branch (use --force-with-lease for safety)
git push --force-with-lease origin clauding
```
### Phase 3: Sync Clauding with Master
#### Step 3.1: Merge Master into Clauding
```bash
git checkout clauding
git merge master --no-edit
# Resolve any conflicts
# Run tests
```
#### Step 3.2: Push Synced Clauding
```bash
git push origin clauding
```
### Phase 4: Final Merge to get-transactions2-page-working
#### Step 4.1: Merge Clauding to get-transactions2-page-working
```bash
git checkout get-transactions2-page-working
git merge clauding --no-edit
# Resolve any conflicts
# Run tests
```
#### Step 4.2: Push Final Branch
```bash
git push origin get-transactions2-page-working
```
## Acceptance Criteria
### Pre-operations Validation
- [ ] All invoice template commits identified correctly (6 commits)
- [ ] Merge base commit (`dc021b8c`) confirmed
- [ ] Current branch state documented
- [ ] Team notified of branch manipulation
### Post-Rebase Validation
- [ ] `invoice-templates-rebased` branch created from `master`
- [ ] All 6 invoice template commits applied correctly
- [ ] All invoice template tests pass
- [ ] No conflicts or unexpected changes during cherry-pick
### Post-Master Validation
- [ ] Invoice templates merged to `master`
- [ ] Changes pushed to remote `master`
- [ ] CI/CD passes on `master`
### Post-Cleanup Validation
- [ ] `clauding` branch has only non-invoice template commits
- [ ] No Bonanza Produce commits remain in `clauding` history
- [ ] All `clauding` tests pass
- [ ] Force push successful
### Post-Sync Validation
- [ ] `clauding` merged with `master`
- [ ] All conflicts resolved
- [ ] Changes pushed to remote
### Final Merge Validation
- [ ] `get-transactions2-page-working` merged with `clauding`
- [ ] All conflicts resolved
- [ ] Final tests pass
- [ ] Changes pushed to remote
## Success Metrics
- **Branch structure**: Invoice templates cleanly separated on `master`
- **Commit history**: Linear, no duplicate invoice template commits
- **Tests passing**: 100% of existing tests pass after each step
- **No data loss**: All work preserved in appropriate branches
- **Branch clarity**: Each branch has a clear, focused purpose
## Dependencies & Risks
### Dependencies
- [ ] All current work on `clauding` should be backed up or committed
- [ ] Team should be aware of branch manipulation to avoid force pushing
- [ ] CI/CD should be monitored during operations
### Risks
1. **Force push risk**: Force pushing `clauding` will rewrite history
- **Mitigation**: Use `--force-with-lease`, notify team beforehand
2. **Conflict resolution**: Multiple merge/conflict resolution points
- **Mitigation**: Test after each step, resolve conflicts carefully
3. **Work loss**: Potential to lose commits if operations go wrong
- **Mitigation**: Create backups, verify each step before proceeding
4. **CI/CD disruption**: Force pushes may affect CI/CD pipelines
- **Mitigation**: Coordinate with team, avoid during active deployments
### Contingency Plan
If something goes wrong:
1. **Recover `clauding` branch**:
```bash
git checkout clauding
git reset --hard origin/clauding # Restore from remote backup
```
2. **Recover master**:
```bash
git checkout master
git reset --hard origin/master # Restore from deploy/master
```
3. **Manual cherry-pick recovery**: If rebasing failed, manually cherry-pick remaining commits
## Alternative Approaches Considered
### Approach 1: Squash and Merge
**Pros**: Single clean commit, simple history
**Cons**: Loses individual commit history and context
**Rejected because**: Team uses merge commits (not squash), and individual commit history is valuable for tracking invoice template development.
### Approach 2: Keep Branches Separate
**Pros**: No branch manipulation needed
**Cons**: Branches remain tangled, harder to track progress
**Rejected because**: Goal is to cleanly separate invoice templates from transaction work.
### Approach 3: Rebase Clauding Onto Master
**Pros**: Linear history
**Cons**: Requires force push, may lose merge context
**Rejected because**: Current team workflow uses merge commits, and merging master into clauding preserves the integration point.
### Approach 4: Create New Branch Instead of Cleanup
**Pros**: Less risky, preserves full history
**Cons**: Accumulates branches, harder to track
**Rejected because**: Goal is cleanup and reorganization, not preservation.
## Related Work
- **Previous invoice template work**: `2026-02-07-feat-add-invoice-template-03881260-plan.md`
- **Current branch structure**: `clauding` has hierarchical relationship with `get-transactions2-page-working`
- **Team git workflow**: Uses merge commits (not rebasing), per repo research
## References & Research
### Internal References
- **Branch management patterns**: Repo research analysis (see `task_id: ses_3c2287be8ffe9icFi5jHEspaqh`)
- **Invoice template location**: `src/clj/auto_ap/parse/templates.clj`
- **Current branch structure**: Git log analysis
### Git Operations Documentation
- **Cherry-pick**: `git cherry-pick <commit>`
- **Interactive rebase**: `git rebase -i <base>`
- **Force push with lease**: `git push --force-with-lease`
- **Merge commits**: `git merge <branch> --no-edit`
### File Locations
- Templates: `src/clj/auto_ap/parse/templates.clj`
- Parser logic: `src/clj/auto_ap/parse.clj`
- Invoice PDF: `dev-resources/INVOICE - 03881260.pdf`
## Testing Plan
### Before Each Major Step
```bash
# Verify current branch state
git branch -vv
git log --oneline -10
# Run all tests
lein test
# Run specific invoice template tests
lein test auto-ap.parse.templates-test
```
### After Each Major Step
- Verify commit count and order
- Run full test suite
- Check for unintended changes
- Verify remote branch state matches local
## Notes
- **Team coordination**: Inform team before force pushing to avoid conflicts
- **Backup strategy**: All commits are preserved in the rebase process
- **Testing**: Verify at each step to catch issues early
- **Safety first**: Use `--force-with-lease` instead of `--force`
- **Documentation**: This plan serves as documentation for the operation

View File

@@ -1,185 +0,0 @@
# Integreat Test Strategy & Behavior Documentation
**Last Updated:** 2026-05-04
## Purpose
This document defines the comprehensive testing strategy for Integreat. The goal is to cover all user-visible behaviors with tests that provide confidence in the system's correctness. We are preserving valuable existing tests and filling gaps with new behavior documentation.
## Test Types
### 1. Unit Tests
- **Scope:** Pure functions, transformations, validations, business logic
- **Characteristics:** No database, no external services, deterministic
- **Location:** `test/clj/auto_ap/<namespace>_test.clj`
- **Examples:** `new_invoice_wizard_test.clj` (location spreading logic)
### 2. Integration Tests
- **Scope:** Database interactions, GraphQL resolvers, route handlers, cross-system behavior
- **Characteristics:** Uses Datomic in-memory database (`datomic:mem://test`), schema created per test, data cleaned up after
- **Location:** `test/clj/auto_ap/integration/`
- **Fixtures:** `wrap-setup` creates schema + functions, runs test, deletes DB
- **Helpers:** `test/clj/auto_ap/integration/util.clj` provides entity creation helpers
### 3. UI / Functional Tests (Playwright)
- **Scope:** End-to-end user flows, page navigation, form interactions, HTMX behaviors
- **Characteristics:** Runs against running application, exercises real HTTP routes
- **Location:** TBD (likely `test/functional/` or similar)
- **Scope Limitation:** Only SSR pages (HTMX-based) get UI tests. Legacy SPA pages get behavior docs only.
## Existing Test Inventory
### Unit Tests
| File | Coverage | Status |
|------|----------|--------|
| `auto_ap/ezcater_test.clj` | EzCater integration logic | Keep |
| `auto_ap/import/plaid_test.clj` | Plaid import | Keep |
| `auto_ap/import/transactions_test.clj` | Transaction import | Keep |
| `auto_ap/import/yodlee_test.clj` | Yodlee import | Keep |
| `auto_ap/import/manual_test.clj` | Manual import | Keep |
| `auto_ap/ledger_test.clj` | Ledger calculations | Keep |
| `auto_ap/parse/templates_test.clj` | PDF template parsing | Keep |
| `auto_ap/ssr/invoice/new_invoice_wizard_test.clj` | Location spreading logic | Keep |
### Integration Tests
| File | Coverage | Status |
|------|----------|--------|
| `auto_ap/integration/graphql.clj` | Transaction page, invoice page, ledger page, vendors, transaction rules | Keep |
| `auto_ap/integration/graphql/accounts.clj` | Account GraphQL | Keep |
| `auto_ap/integration/graphql/checks.clj` | Check GraphQL | Keep |
| `auto_ap/integration/graphql/invoices.clj` | Invoice GraphQL | Keep |
| `auto_ap/integration/graphql/ledger/running_balance.clj` | Ledger balance | Keep |
| `auto_ap/integration/graphql/transaction_rules.clj` | Transaction rules | Keep |
| `auto_ap/integration/graphql/transactions.clj` | Transaction GraphQL | Keep |
| `auto_ap/integration/graphql/users.clj` | User GraphQL | Keep |
| `auto_ap/integration/graphql/vendors.clj` | Vendor GraphQL | Keep |
| `auto_ap/integration/routes/invoice_test.clj` | Invoice import routes | Keep |
| `auto_ap/integration/routes/ezcater_xls.clj` | EzCater XLS routes | Keep |
| `auto_ap/integration/rule_matching.clj` | Rule matching logic | Keep |
| `auto_ap/integration/jobs/ntg.clj` | NTG background job | Keep |
## Page/Subsystem Coverage Map
### SSR Pages (HTMX - Eligible for UI Tests)
1. **Dashboard** - Main dashboard with cards (expenses, tasks, bank accounts, sales, P&L)
2. **Invoices** - List, detail, new wizard, pay wizard, bulk edit, bulk delete, import, glimpse (OCR)
3. **Payments** - List, detail, bulk operations
4. **Transactions** - List, detail, new, external import, coding workflow
5. **Ledger** - Entries, P&L, Balance Sheet, Cash Flows, investigation
6. **Company** - Profile, signature, 1099s, reports, bank accounts, Plaid/Yodlee linking
7. **POS** - Sales, expected deposits, tenders, refunds, cash drawer shifts
8. **Outgoing Invoices** - Create outgoing invoices
9. **Admin** - Clients, accounts, vendors, transaction rules, background jobs, history, import batches, Excel invoices, sales summaries
10. **Search** - Global search dialog
11. **Indicators** - Business indicators
### Legacy SPA Pages (Behavior Docs Only)
1. **Home** - Legacy dashboard
2. **Login** - Authentication
3. **Transactions** - Unapproved, approved, requires feedback, excluded
4. **Ledger** - P&L, Balance Sheet, Cash Flows, external, external import
5. **Payments** - Legacy payments list
6. **Reports** - Reports page
7. **Admin Vendors** - Vendor management (legacy)
8. **New Vendor** - Vendor creation
## Behavior Documentation Structure
Each subsystem gets a markdown file in `docs/testing/behaviors/` with the following structure:
```markdown
# <Subsystem> Behaviors
## Overview
Brief description of what this subsystem does and its user-visible purpose.
## Pages / Routes
List of all routes and their purposes.
## Behaviors by Type
### Unit Test Behaviors
- Pure function behaviors, edge cases
### Integration Test Behaviors
- Database interactions, API behaviors, cross-system flows
### UI Test Behaviors (SSR only)
- End-to-end happy paths
- User interaction flows
- Navigation between pages
## Edge Cases
- Error states
- Empty states
- Permission boundaries
- Concurrent operations
- Large data volumes
## Test Data Requirements
What entities need to exist for meaningful tests.
## Dependencies
What other subsystems this depends on.
```
## Test Data Patterns
The integration test utilities in `test/clj/auto_ap/integration/util.clj` provide:
- `test-client` - Creates a test client entity
- `test-vendor` - Creates a test vendor entity
- `test-bank-account` - Creates a test bank account
- `test-transaction` - Creates a test transaction
- `test-payment` - Creates a test payment
- `test-invoice` - Creates a test invoice
- `test-account` - Creates a test GL account
- `test-transaction-rule` - Creates a test transaction rule
- `setup-test-data` - Convenience function to create standard test data set
- `admin-token` / `user-token` - JWT identity helpers
## Mocks & External Services
- **Solr:** Mocked for search functionality
- **AWS Services:** (Textract, S3, SES, SQS) - Should be mocked in tests
- **Plaid/Yodlee/Intuit:** External financial APIs - Mocked
- **Square/EzCater:** POS integrations - Mocked
## Priorities
1. **Critical:** Invoice pages (core business function)
2. **Critical:** Payment pages (money movement)
3. **High:** Dashboard (first thing users see)
4. **High:** Transaction pages (data entry/coding)
5. **High:** Ledger reports (financial reporting)
6. **Medium:** Admin pages (operations)
7. **Medium:** Company settings (configuration)
8. **Low:** POS pages (ancillary)
9. **Low:** Legacy SPA pages (behavior docs only, no UI tests)
## Running Tests
```bash
# All tests
lein test
# Integration tests only
lein test :integration
# Functional tests only
lein test :functional
```
## Files
- [Dashboard Behaviors](behaviors/dashboard.md)
- [Invoice Behaviors](behaviors/invoice.md)
- [Payment Behaviors](behaviors/payment.md)
- [Transaction Behaviors](behaviors/transaction.md)
- [Ledger Behaviors](behaviors/ledger.md)
- [Company Behaviors](behaviors/company.md)
- [POS Behaviors](behaviors/pos.md)
- [Outgoing Invoice Behaviors](behaviors/outgoing-invoice.md)
- [Admin Behaviors](behaviors/admin.md)
- [Search & Indicators Behaviors](behaviors/search-indicators.md)
- [Auth Behaviors](behaviors/auth.md)
- [Legacy SPA Behaviors](behaviors/legacy-spa.md)

View File

@@ -1,494 +0,0 @@
# Admin Behaviors
## Overview
The Admin section is a server-side rendered (SSR) HTMX interface for superuser operations in Integreat. All admin pages require the `admin` user role. Non-admin users are redirected away. The admin area includes dashboards, entity management grids, background job orchestration, audit history, and bulk import tools.
**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
---
## Testing Patterns
### 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
**Test implications:** Validate the query generation, not the rendering. UI tests only need to verify one filter and one sort work end-to-end.
### 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
**Test implications:** Unit test validation logic and state transitions. Integration test the full wizard flow once. UI test only the happy path.
### Pattern: Admin Permission Gates
Every admin operation checks:
1. `wrap-client-redirect-unauthenticated` — redirects unauthenticated users to login
2. `wrap-admin` — blocks non-admin authenticated users
3. `assert-can-see-client` — when impersonating, user has access to the client
**Test implications:** Integration test each gate independently. UI tests only verify the happy path with an admin user.
---
## Dashboard
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 1.1 | It should display a bar chart showing client count at 2 years ago, 1 year ago, and today | UI | [ ] |
| 1.2 | It should display a line chart showing Datomic transaction counts per hour over the last 24 hours | UI | [ ] |
| 1.3 | It should render the standard admin page layout with the admin-aside-nav sidebar | UI | [ ] |
### Access Control Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 2.1 | It should redirect unauthenticated users to the login page | Integration | [ ] |
| 2.2 | It should show an authorization failure for authenticated non-admin users | Integration | [ ] |
---
## Clients
### Grid Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 3.1 | It should display a table with columns: Name, Code, Locations, Emails, Status | UI | [ ] |
| 3.2 | It should show location values as pills in the Locations column | UI | [ ] |
| 3.3 | It should show email values as pills in the Emails column | UI | [ ] |
| 3.4 | It should show lock status as "Locked <date>" with green color when less than 90 days old | UI | [ ] |
| 3.5 | It should show lock status with yellow color when between 90 days and 1 year old | UI | [ ] |
| 3.6 | It should show lock status with red color when more than 1 year old | UI | [ ] |
| 3.7 | It should show "Not locked" in red when no lock date is set | UI | [ ] |
| 3.8 | It should show bank account integration status pills in red for failed or unauthorized accounts | UI | [ ] |
| 3.9 | It should show a Biweekly Sales PowerQuery button on each row | UI | [ ] |
| 3.10 | It should show an Edit button (pencil icon) on each row | UI | [ ] |
| 3.11 | It should show a "New Client" button that opens the client wizard | UI | [ ] |
### Filtering Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 4.1 | It should filter clients by name using case-insensitive substring match | Integration | [ ] |
| 4.2 | It should filter clients by code using exact match on upper-cased code | Integration | [ ] |
| 4.3 | It should filter clients by group using exact match on upper-cased group | Integration | [ ] |
| 4.4 | It should support an "All" or "Only mine" filter to show only clients assigned to the current user | Integration | [ ] |
| 4.5 | It should trigger HTMX requests with 500ms debounce on filter change and 1000ms debounce on keyup | Integration | [ ] |
### Sorting Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 5.1 | It should sort clients by name ascending/descending | Integration | [ ] |
| 5.2 | It should sort clients by code ascending/descending | Integration | [ ] |
| 5.3 | It should paginate results with 25 clients per page by default | Integration | [ ] |
### Client Wizard Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 6.1 | It should show a multi-step linear wizard with steps: Info, Matches, Contact, Bank Accounts, Integrations, Cash Flow, Other Settings | UI | [ ] |
| 6.2 | It should require Name on the Info step | Integration | [ ] |
| 6.3 | It should prevent editing the Code field when editing an existing client | UI | [ ] |
| 6.4 | It should allow setting a Locked Until date on the Info step | UI | [ ] |
| 6.5 | It should show a dynamic grid for adding and removing locations on the Info step | UI | [ ] |
| 6.6 | It should allow configuring string match patterns and location match patterns on the Matches step | UI | [ ] |
| 6.7 | It should allow entering address fields and email contacts on the Contact step | UI | [ ] |
| 6.8 | It should show a sortable card list of existing bank accounts on the Bank Accounts step | UI | [ ] |
| 6.9 | It should allow adding cash accounts with nickname, code, financial code, start date, include-in-reports, and visible-for-payment fields | UI | [ ] |
| 6.10 | It should allow adding credit card accounts with bank name, account number, and Plaid/Yodlee/Intuit integration selectors | UI | [ ] |
| 6.11 | It should allow adding checking accounts with routing number, bank code, and check number fields | UI | [ ] |
| 6.12 | It should require a financial code when "Include in Reports" is enabled for a bank account | Unit + Integration | [ ] |
| 6.13 | It should allow entering a Square auth token and mapping Square locations to client locations on the Integrations step | UI | [ ] |
| 6.14 | It should show "No locations found" when the Square location refresh times out after 2 seconds | Integration | [ ] |
| 6.15 | It should allow entering Week A/B credits and debits on the Cash Flow step | UI | [ ] |
| 6.16 | It should allow selecting feature flags and entering groups on the Other Settings step | UI | [ ] |
| 6.17 | It should validate that the client code is unique when creating a new client | Unit + Integration | [ ] |
| 6.18 | It should upper-case group values on save | Unit | [ ] |
| 6.19 | It should flash the updated row in the grid and close the modal after a successful save | UI | [ ] |
| 6.20 | It should reindex the client in Solr after a successful save | Integration | [ ] |
### Biweekly Sales PowerQuery Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 7.1 | It should generate 6 saved queries per client (sales summary, category, expected deposits, tenders, refunds, cash drawer shifts) | Integration | [ ] |
| 7.2 | It should open a modal with copy-to-clipboard buttons for Excel Power Query M-code | UI | [ ] |
---
## Accounts
### Grid Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.1 | It should display a table with columns: Code, Name, Type, Location | UI | [ ] |
| 8.2 | It should show the account type as a colored pill | UI | [ ] |
| 8.3 | It should show an Edit button on each row | UI | [ ] |
| 8.4 | It should show a "New Account" button | UI | [ ] |
### Filtering Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 9.1 | It should filter accounts by name using case-insensitive substring match on upper-cased name | Integration | [ ] |
| 9.2 | It should filter accounts by code using exact numeric match | Integration | [ ] |
| 9.3 | It should filter accounts by type: All, Dividend, Asset, Equity, Liability, Expense, Revenue, or None | Integration | [ ] |
### Sorting Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 10.1 | It should sort accounts by code, name, type, or location ascending/descending | Integration | [ ] |
| 10.2 | It should default sort by upper-cased numeric code | Integration | [ ] |
### Account Dialog Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 11.1 | It should show a modal dialog with a live-updating header displaying the numeric code and name | UI | [ ] |
| 11.2 | It should require a numeric code when creating a new account | Integration | [ ] |
| 11.3 | It should hide the numeric code field when editing an existing account | UI | [ ] |
| 11.4 | It should require a name and account type | Integration | [ ] |
| 11.5 | It should allow setting Invoice Allowance, Vendor Allowance, and Applicability as dropdown enums | UI | [ ] |
| 11.6 | It should show a Client Overrides grid with client typeahead and override name | UI | [ ] |
| 11.7 | It should validate that no client appears more than once in the Client Overrides grid | Unit + Integration | [ ] |
| 11.8 | It should validate that the numeric code is unique when creating a new account | Unit + Integration | [ ] |
| 11.9 | It should reindex the account and all client overrides in Solr after a successful save | Integration | [ ] |
---
## Vendors
### Grid Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 12.1 | It should display a table with columns: Name, Email, Default Account | UI | [ ] |
| 12.2 | It should show an "Unused" pill in red when a vendor has 0 clients | UI | [ ] |
| 12.3 | It should show a "Used by N clients" pill in primary color when a vendor is assigned to clients | UI | [ ] |
| 12.4 | It should show a "Used N times" pill in secondary color when a vendor has transaction usage | UI | [ ] |
| 12.5 | It should show an Edit button on each row that opens the vendor wizard | UI | [ ] |
| 12.6 | It should show a "Merge" button for merging vendors | UI | [ ] |
| 12.7 | It should show a "New Vendor" button | UI | [ ] |
### Filtering Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 13.1 | It should filter vendors by name using case-insensitive substring match on upper-cased name | Integration | [ ] |
| 13.2 | It should filter vendors by visibility: All, Only hidden, or Only global | Integration | [ ] |
### Vendor Wizard Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 14.1 | It should show a multi-step wizard with steps: Info, Terms, Account, Address, Legal | UI | [ ] |
| 14.2 | It should require a name of at least 3 characters on the Info step | Unit + Integration | [ ] |
| 14.3 | It should allow toggling a "Print As" alias on the Info step | UI | [ ] |
| 14.4 | It should show a "Hidden" checkbox on the Info step visible only to admins | UI | [ ] |
| 14.5 | It should allow setting terms in days and a grid of client-specific terms overrides on the Terms step | UI | [ ] |
| 14.6 | It should allow configuring a list of clients for automatically paid when due on the Terms step | UI | [ ] |
| 14.7 | It should allow selecting a default account via typeahead on the Account step | UI | [ ] |
| 14.8 | It should show an Account Overrides grid where account typeahead is scoped by selected client | Integration | [ ] |
| 14.9 | It should allow entering address fields with a 2-character state and 5-character zip on the Address step | UI | [ ] |
| 14.10 | It should allow entering a legal entity name OR first/middle/last name, TIN, TIN type, and 1099 type on the Legal step | UI | [ ] |
| 14.11 | It should validate that terms override clients are unique with no duplicates | Unit + Integration | [ ] |
| 14.12 | It should reindex the vendor name and hidden flag in Solr after a successful save | Integration | [ ] |
### Vendor Merge Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 15.1 | It should open a modal with Source Vendor and Target Vendor selectors | UI | [ ] |
| 15.2 | It should validate that the source and target vendors are different | Unit + Integration | [ ] |
| 15.3 | It should retract all references to the source vendor and assert them as the target vendor on merge | Integration | [ ] |
| 15.4 | It should show a success notification after a successful merge | UI | [ ] |
---
## Rules
### Grid Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 16.1 | It should display a table with columns: Client, Bank Account, Description, Amount, Note | UI | [ ] |
| 16.2 | It should show a group pill in the Client column when the rule applies to a client group | UI | [ ] |
| 16.3 | It should show amount gte/lte filters as pills in the Amount column | UI | [ ] |
| 16.4 | It should show Delete, Execute, and Edit row action buttons | UI | [ ] |
| 16.5 | It should show a "New Transaction Rule" button | UI | [ ] |
### Filtering Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 17.1 | It should filter rules by vendor using an entity typeahead | Integration | [ ] |
| 17.2 | It should filter rules by note using case-insensitive regex match | Integration | [ ] |
| 17.3 | It should filter rules by description using case-insensitive substring match | Integration | [ ] |
| 17.4 | It should filter rules by client group using exact upper-cased match | Integration | [ ] |
### Transaction Rule Wizard Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 18.1 | It should show a two-step wizard: Edit then Test | UI | [ ] |
| 18.2 | It should require a description regex pattern of at least 3 characters on the Edit step | Unit + Integration | [ ] |
| 18.3 | It should allow toggling optional filters for Client, Client Group, Bank Account, Amount range, and Day of Month range | UI | [ ] |
| 18.4 | It should scope the bank account selector to the selected client | Integration | [ ] |
| 18.5 | It should allow assigning a vendor, configuring account grids, and setting approval status as outcomes | UI | [ ] |
| 18.6 | It should derive account location from the account's fixed location, client locations, or "Shared" | Unit | [ ] |
| 18.7 | It should validate that account percentages sum to exactly 100% | Unit + Integration | [ ] |
| 18.8 | It should validate that the selected bank account belongs to the selected client | Unit + Integration | [ ] |
| 18.9 | It should validate that the rule location matches the account's fixed location when one is set | Unit + Integration | [ ] |
| 18.10 | It should show up to 15 matching transactions on the Test step with client, bank, date, and description | Integration | [ ] |
| 18.11 | It should display a badge showing the total match count with "99+" when 99 or more transactions match | UI | [ ] |
### Rule Execution Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 19.1 | It should open a dialog with checkbox-selectable transactions that match the rule and are unapproved | UI | [ ] |
| 19.2 | It should include only transactions on or after the client's locked-until date | Integration | [ ] |
| 19.3 | It should allow selecting all matching transactions or individual transactions | UI | [ ] |
| 19.4 | It should apply rule coding to each selected transaction | Integration | [ ] |
| 19.5 | It should update the Solr index after rule execution | Integration | [ ] |
| 19.6 | It should show a notification reading "Successfully coded X of Y transactions!" after execution | UI | [ ] |
### Rule Deletion Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 20.1 | It should show a confirmation dialog before deleting a rule | UI | [ ] |
| 20.2 | It should retract the rule entity from the database on confirmation | Integration | [ ] |
| 20.3 | It should fade out the row with a "live-removed" animation after deletion | UI | [ ] |
---
## Jobs
### Grid Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 21.1 | It should display a table with columns: Start time, End time, Duration, Name, Status | UI | [ ] |
| 21.2 | It should show status values as running, pending, succeeded, or failed | UI | [ ] |
| 21.3 | It should display ECS tasks filtered by the INTEGREAT_JOB environment variable | Integration | [ ] |
| 21.4 | It should show a "Run job" button | UI | [ ] |
### Job Start Dialog Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 22.1 | It should show a job type dropdown with options: Yodlee Import, Yodlee Account Import, Intuit Import, Plaid Import, Bulk Journal Import, Square Import, Register Invoice Import, Upsert recent ezcater orders, Load Historical Square Sales, Export Backup | UI | [ ] |
| 22.2 | It should show a dynamic subform with an S3 URL path input for Bulk Journal Import and Register Invoice Import | UI | [ ] |
| 22.3 | It should show a client typeahead and days input (1-120) for Load Historical Square Sales | UI | [ ] |
| 22.4 | It should prevent starting a job that is already running | Integration | [ ] |
| 22.5 | It should launch an ECS Fargate Spot task on submit | Integration | [ ] |
---
## History
### Search Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 23.1 | It should allow searching for an entity by numeric Datomic entity ID | UI | [ ] |
| 23.2 | It should show an error notification when the entity ID cannot be parsed as a Long | Integration | [ ] |
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 24.1 | It should display a history table with columns: Date, User, Field, From value, To value | UI | [ ] |
| 24.2 | It should format date values in local format | Unit | [ ] |
| 24.3 | It should display large integers greater than 1 million as clickable links to that entity's history | UI | [ ] |
| 24.4 | It should display nil values as "(none)" | Unit | [ ] |
| 24.5 | It should allow clicking an entity ID to load that entity's history inline | Integration | [ ] |
| 24.6 | It should show a Snapshot link that opens an inspector displaying all entity attributes | UI | [ ] |
| 24.7 | It should show all history rows without pagination | Integration | [ ] |
### Inspector Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 25.1 | It should display a card showing all attributes of an entity at the current database value | UI | [ ] |
| 25.2 | It should allow clicking entity IDs within the inspector to recurse into that entity's history | Integration | [ ] |
---
## Imports
### Grid Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 26.1 | It should display a table with columns: Date, Source, Status, User, Imported, Pre-existing, Suppressed | UI | [ ] |
| 26.2 | It should show an external link on each row to transactions filtered by import batch | UI | [ ] |
| 26.3 | It should show a "New Import" button | UI | [ ] |
### Filtering Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 27.1 | It should filter import batches by date range | Integration | [ ] |
| 27.2 | It should filter import batches by source | Integration | [ ] |
### Sorting Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 28.1 | It should sort import batches by date, source, status, or user | Integration | [ ] |
| 28.2 | It should paginate results with 25 import batches per page by default | Integration | [ ] |
---
## Excel Invoices
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 29.1 | It should display a single-page form with a large textarea for tab-separated invoice data | UI | [ ] |
| 29.2 | It should show sample data as a placeholder in the textarea | UI | [ ] |
### Import Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 30.1 | It should parse tab-separated rows with columns: raw-date, vendor-name, check, location, invoice-number, amount, client-name, bill-entered, bill-rejected, added-on, exported-on, account-numeric-code | Unit | [ ] |
| 30.2 | It should resolve the client by code or name | Integration | [ ] |
| 30.3 | It should resolve the vendor by exact case-sensitive name match | Integration | [ ] |
| 30.4 | It should resolve the account by numeric code | Integration | [ ] |
| 30.5 | It should group rows into new, existing, and error categories | Unit | [ ] |
| 30.6 | It should create a paid invoice with zero outstanding balance and a cash transaction when the check type is "Cash" | Integration | [ ] |
| 30.7 | It should create an unpaid invoice with full outstanding balance when the check type is not "Cash" | Integration | [ ] |
| 30.8 | It should display results as pills showing imported count, extant count, and vendors not found with hover tooltip | UI | [ ] |
| 30.9 | It should display an error grid for rows that failed validation | UI | [ ] |
---
## Sales Summaries
### Grid Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 31.1 | It should display a table with columns: Client, Date, Debits, Credits | UI | [ ] |
| 31.2 | It should hide the Client column when only one client is selected | UI | [ ] |
| 31.3 | It should show each debit/credit item with category, amount, and a red "missing account" pill when no account is mapped | UI | [ ] |
| 31.4 | It should show a total row with a green pill when debits equal credits, or a red pill when unbalanced | UI | [ ] |
| 31.5 | It should show an Edit button on each row | UI | [ ] |
### Filtering Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 32.1 | It should filter sales summaries by date range | Integration | [ ] |
| 32.2 | It should scope results to the user's valid clients | Integration | [ ] |
### Edit Wizard Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 33.1 | It should display a grid of sales summary items with Category, Account, Debits, and Credits columns | UI | [ ] |
| 33.2 | It should allow editing the category, account, and debit/credit amounts for manual items | UI | [ ] |
| 33.3 | It should allow removing manual items from the grid | UI | [ ] |
| 33.4 | It should display auto-generated items with read-only category and amount but editable account | UI | [ ] |
| 33.5 | It should scope the account typeahead to the client and filter for invoice-purpose accounts | Integration | [ ] |
| 33.6 | It should update the live total row and unbalanced row when amounts change | UI | [ ] |
| 33.7 | It should validate that each item has exactly one of credit or debit, not both | Unit + Integration | [ ] |
| 33.8 | It should validate that total debits equal total credits before saving | Unit + Integration | [ ] |
| 33.9 | It should update ledger-mapped account assignments and flag manual items on save | Integration | [ ] |
---
## Cross-Cutting Behaviors
### Admin-Only Access Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 34.1 | It should redirect unauthenticated users to the login page on all admin routes | Integration | [ ] |
| 34.2 | It should show an authorization failure for authenticated non-admin users on all admin routes | Integration | [ ] |
| 34.3 | It should require admin role for all mutating admin handlers | Integration | [ ] |
### Audit History Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 35.1 | It should record the admin user who performed each mutating operation via the `:audit/user` attribute | Integration | [ ] |
| 35.2 | It should write all mutating operations through `audit-transact` or `audit-transact-batch` | Integration | [ ] |
| 35.3 | It should allow querying all changes to an entity from Datomic's history database on the History page | Integration | [ ] |
### Impersonation Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 36.1 | It should allow admin users to select a client from the global client selector to filter admin grids | UI | [ ] |
| 36.2 | It should respect the selected client when filtering the Clients, Transaction Rules, and Sales Summaries grids | Integration | [ ] |
### Form Validation Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 37.1 | It should enforce form structure via Malli schemas | Unit | [ ] |
| 37.2 | It should validate query params, route params, and form params via `wrap-schema-enforce` | Integration | [ ] |
| 37.3 | It should re-render dialogs with field-level validation errors on 400 responses | Integration | [ ] |
### Solr Indexing Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 38.1 | It should reindex Solr documents after creating or updating a client | Integration | [ ] |
| 38.2 | It should reindex Solr documents after creating or updating a vendor or account | Integration | [ ] |
---
## Test Data Requirements
| Entity | Requirements |
|--------|-------------|
| **Users** | Admin user with `:user/role :user-role/admin`; non-admin user with `:user/role :user-role/user` |
| **Clients** | Client with name, code, and locations; client with locked-until in past and future; client with bank accounts (cash, credit, checking); client with Square auth token; client with feature flags and groups |
| **Accounts** | Account with unique numeric code, name, and type; account with fixed location; account with client overrides |
| **Vendors** | Vendor with name and default account; vendor with terms overrides and account overrides; hidden and non-hidden vendors; vendor with 1099 legal entity info |
| **Transaction Rules** | Rule with description pattern, client, bank account, amount gte/lte; rule with account assignments summing to 100%; unapproved transactions matching rule criteria; transactions before and after client's locked-until date |
| **Background Jobs** | ECS cluster with task definitions containing `INTEGREAT_JOB` env var, or mock ECS responses |
| **Import Batches** | Import batch entities with date, source, and status |
| **Sales Summaries** | Sales summary with ledger-mapped items; both manual and auto-generated items |
## Existing Tests to Preserve
- `test/clj/auto_ap/integration/graphql/users.clj` — User role and permission tests
- `test/clj/auto_ap/integration/graphql/accounts.clj` — Account search and override tests
- `test/clj/auto_ap/integration/graphql/vendors.clj` — Vendor management tests
- `test/clj/auto_ap/integration/graphql/transaction_rules.clj` — Rule matching and execution tests
## Dependencies
- Datomic (primary store, history, pull API)
- HTMX (frontend interactivity)
- Alpine.js (modal state, conditional visibility)
- Chartist (dashboard charts)
- Solr (search indexing)
- ECS API (background jobs)
- Malli (schema validation)
- Bidi (routing)

View File

@@ -1,184 +0,0 @@
# Authentication Behaviors
## Overview
Authentication in Integreat uses Google OAuth 2.0 as the primary identity provider. Users authenticate via Google, receive a JWT token, and establish a server-side session. The system supports role-based access control (`admin`, `user`, `read-only`), client-scoped permissions, session versioning for SSR rollout, and admin impersonation via signed JWT tokens.
**Testing Philosophy**
- Prefer unit tests for pure business logic (JWT generation, compression, validation)
- Use integration tests for database interactions and cross-system flows (OAuth callback, session management)
- 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
---
## Testing Patterns
### Pattern: OAuth Login Flow
The standard authentication flow follows these steps:
1. Unauthenticated user navigates to a protected page
2. User is redirected to `/login`
3. User clicks "Sign in with Google"
4. Browser redirects to Google OAuth consent screen
5. User consents and Google redirects to `/api/oauth?code=<code>&state=<state>`
6. Server exchanges code for token, fetches profile, finds/creates user
7. Server redirects to original page (or `/`) with `?jwt=<token>`
8. Client reads JWT and establishes session
**Test implications:** Integration test the OAuth callback handler. UI test only the happy path once.
### Pattern: Middleware Stack
Every protected route passes through authentication middleware:
1. `wrap-session-version` — validates session version, redirects to login if outdated
2. `wrap-secure` — checks for authenticated session, redirects to login if missing
3. `wrap-admin` — checks for admin role, redirects to login if not admin
4. `wrap-client-redirect-unauthenticated` — converts 401 responses to HTMX redirects
**Test implications:** Integration test each middleware independently. UI tests only verify redirect behavior.
### Pattern: JWT Claims
The JWT token contains user identity and permissions:
1. `:user`, `:exp`, `:db/id`, `:user/role`, `:user/name` for all users
2. `:gz-clients` (compressed) for admin and read-only users
3. `:user/clients` (plain) for regular users
**Test implications:** Unit test JWT generation for each role type.
---
## Login
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 1.1 | It should display a "Sign in with Google" button on the login page | UI | [ ] |
### OAuth Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 1.2 | It should redirect to Google OAuth when the user clicks "Sign in with Google" | UI | [ ] |
| 1.3 | It should exchange the authorization code for an access token on callback | Integration | [ ] |
| 1.4 | It should fetch the user's Google profile using the access token | Integration | [ ] |
| 1.5 | It should create a new user account when the user logs in for the first time | Integration | [ ] |
| 1.6 | It should find the existing user account on subsequent logins | Integration | [ ] |
| 1.7 | It should redirect to the original page after successful OAuth | Integration | [ ] |
| 1.8 | It should redirect to the root page when no return URL is provided | Integration | [ ] |
| 1.9 | It should establish a server-side session with user identity and version | Integration | [ ] |
| 1.10 | It should pass the JWT token in the query string after successful OAuth | Integration | [ ] |
| 1.11 | It should display the user's clients and data after successful login | UI | [ ] |
| 1.12 | It should handle users without email via Google provider ID | Integration | [ ] |
| 1.13 | It should return 401 with error message when the OAuth code is missing | Integration | [ ] |
| 1.14 | It should return 401 when the OAuth code is invalid or expired | Integration | [ ] |
| 1.15 | It should return 401 and log a warning when the Google network request fails | Integration | [ ] |
---
## Logout
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 2.1 | It should clear the session when the user navigates to `/logout` | Integration | [ ] |
| 2.2 | It should redirect to the login page after logout | Integration | [ ] |
| 2.3 | It should remain idempotent when logging out without an active session | Integration | [ ] |
---
## Impersonation
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 3.1 | It should allow admin users to assume another user's identity via signed JWT | Integration | [ ] |
| 3.2 | It should validate the impersonation JWT signature with `:jwt-secret` and `:hs512` | Integration | [ ] |
| 3.3 | It should reject expired impersonation JWTs | Integration | [ ] |
| 3.4 | It should block non-admin users from accessing `/impersonate` | Integration | [ ] |
| 3.5 | It should block unauthenticated users from accessing `/impersonate` | Integration | [ ] |
| 3.6 | It should replace the admin's session with the impersonated user's session | Integration | [ ] |
---
## Session Management
### Authentication Gate Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 4.1 | It should allow authenticated requests to proceed to protected routes | Integration | [ ] |
| 4.2 | It should redirect unauthenticated users to `/login` with a `redirect-to` parameter | Integration | [ ] |
| 4.3 | It should return `hx-redirect: /login` for unauthenticated HTMX requests | Integration | [ ] |
### Admin Gate Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 5.1 | It should allow admin requests to proceed to admin-only routes | Integration | [ ] |
| 5.2 | It should redirect non-admin users to `/login` when accessing admin routes | Integration | [ ] |
### Session Version Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 6.1 | It should invalidate sessions with outdated version numbers | Integration | [ ] |
| 6.2 | It should redirect to `/login` when an outdated session accesses normal routes | Integration | [ ] |
| 6.3 | It should return `hx-redirect: /login` for outdated sessions on HTMX routes | Integration | [ ] |
| 6.4 | It should return 401 for outdated sessions on GraphQL routes | Integration | [ ] |
| 6.5 | It should treat sessions without a version as outdated | Integration | [ ] |
---
## Cross-Cutting Behaviors
### JWT Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 7.1 | It should generate a JWT containing the user's role and client access on login | Unit | [ ] |
| 7.2 | It should compress the client list for admin users to fit in the JWT | Unit | [ ] |
| 7.3 | It should compress the client list for read-only users to fit in the JWT | Unit | [ ] |
| 7.4 | It should include a plain client list for regular users in the JWT | Unit | [ ] |
| 7.5 | It should create API tokens with admin role and 1000-day expiration | Unit | [ ] |
### Middleware Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.1 | It should convert 401 responses to HTMX redirects for unauthenticated users | Integration | [ ] |
### Role-Based Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 9.1 | It should allow admin users to access all clients | Integration | [ ] |
| 9.2 | It should allow regular users to access only their assigned clients | Integration | [ ] |
| 9.3 | It should allow read-only users to access all clients with view-only permissions | Integration | [ ] |
| 9.4 | It should handle admin users with no clients by providing an empty compressed list | Unit | [ ] |
| 9.5 | It should handle regular users with no clients by providing an empty client vector | Unit | [ ] |
### Security Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 10.1 | It should reject tampered JWTs during impersonation | Integration | [ ] |
| 10.2 | It should treat sessions with nil identity as unauthenticated | Integration | [ ] |
---
## Test Data Requirements
| Entity | Requirements |
|--------|-------------|
| **Users** | Admin user with multiple clients; regular user with subset of clients; read-only user with multiple clients; new user (not in database) with Google provider details; existing user with Google provider details |
| **Clients** | Multiple clients with `:client/code`, `:client/name`, `:client/locations`; client associations on users |
| **OAuth Mock** | Mock Google token endpoint responses (success and failure); mock Google userinfo endpoint responses |
## Existing Tests to Preserve
- None identified
## Dependencies
- Google OAuth 2.0 (authorization code exchange and userinfo retrieval)
- Buddy JWT (token signing/unsigning with HS512)
- Datomic (user lookup and creation)
- Legacy SPA (login and needs-activation pages, no SSR tests)

View File

@@ -1,320 +0,0 @@
# Company Behaviors
## Overview
The Company section provides company-level settings and reporting for Integreat users. It is implemented as SSR pages using HTMX and is accessed via the left navigation sidebar under "My Company". All pages require authentication and enforce client-level access controls. Most pages react to client selection changes by refreshing their content via HTMX.
**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
---
## Testing Patterns
### 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
**Test implications:** Validate the query generation, not the rendering. UI tests only need to verify one filter and one sort work end-to-end.
### Pattern: Permission Gates
Every mutating operation checks:
1. `assert-can-see-client` — user has access to the client
2. `can?` — user has the specific permission for the activity
3. Admin role checks for destructive operations
**Test implications:** Integration test each gate independently. UI tests only verify the happy path with a permitted user.
### Pattern: HTMX Refresh on Client Switch
All company pages listen for `clientSelected from:body` event and refresh `#app-contents` with a 300ms swap animation.
**Test implications:** Integration test the event handling and route params. UI test only one page to verify the swap animation.
---
## Company Profile
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 1.1 | It should display a "Please select a company" placeholder card when no client is selected | UI | [ ] |
| 1.2 | It should display the company name as a heading when a client is selected | UI | [ ] |
| 1.3 | It should display the company address (street, city, state, zip) when address data exists | UI | [ ] |
| 1.4 | It should omit missing address fields without showing error placeholders | UI | [ ] |
| 1.5 | It should show a "Download vendor list" button | UI | [ ] |
| 1.6 | It should download a CSV/Excel export when the download button is clicked | Integration | [ ] |
### Signature Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 2.1 | It should show the signature section only when the user has signature edit permission | Integration | [ ] |
| 2.2 | It should display the saved signature image when one exists | UI | [ ] |
| 2.3 | It should show a "New signature" button that enables drawing mode on a canvas | UI | [ ] |
| 2.4 | It should show a "Clear" button that clears the canvas while in drawing mode | UI | [ ] |
| 2.5 | It should show an "Accept" button that submits the drawn signature | UI | [ ] |
| 2.6 | It should reject invalid signature image data with a validation error | Unit + Integration | [ ] |
| 2.7 | It should provide a drag-and-drop zone for uploading JPEG signature files | UI | [ ] |
| 2.8 | It should change the drop zone background color on hover | UI | [ ] |
| 2.9 | It should refresh the signature section with the uploaded image on successful upload | Integration | [ ] |
---
## 1099 Reports
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 3.1 | It should display vendors who received $600 or more in check payments during the current tax year | Integration | [ ] |
| 3.2 | It should show grid columns: Client, Vendor Name, TIN, Expense Account, Address, Paid | UI | [ ] |
| 3.3 | It should display the vendor's legal entity name as a subtitle under the vendor name | UI | [ ] |
| 3.4 | It should show a 1099 type pill badge when a 1099 type is set | UI | [ ] |
| 3.5 | It should display the TIN with a TIN type pill (EIN or SSN) | UI | [ ] |
| 3.6 | It should show "No address" placeholder when the vendor has no address | UI | [ ] |
| 3.7 | It should display the total paid amount as a pill badge rounded to the nearest dollar | UI | [ ] |
| 3.8 | It should show an edit icon button on each row | UI | [ ] |
| 3.9 | It should show vendors shared across multiple clients in each client's context | Integration | [ ] |
| 3.10 | It should show an empty grid when no vendors received $600+ in checks during the tax year | UI | [ ] |
### Filtering & Sorting Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 4.1 | It should support standard grid query params (sort, pagination, search) | Integration | [ ] |
| 4.2 | It should default sort by client code then amount | Integration | [ ] |
### Edit Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 5.1 | It should open a vendor edit dialog in a modal when the edit icon is clicked | UI | [ ] |
| 5.2 | It should display address fields (Street 1, Street 2, City, State, ZIP) in the dialog | UI | [ ] |
| 5.3 | It should validate the ZIP code as 5 digits or empty | Unit + Integration | [ ] |
| 5.4 | It should allow entering either a legal entity name or first/middle/last name | UI | [ ] |
| 5.5 | It should allow entering a TIN and selecting TIN type (EIN or SSN) | UI | [ ] |
| 5.6 | It should allow selecting a 1099 type from a dropdown | UI | [ ] |
| 5.7 | It should close the modal and refresh the row with a flash highlight on successful save | Integration | [ ] |
| 5.8 | It should null the address if all address fields are empty and no existing address | Integration | [ ] |
---
## Expense Reports
### Chart Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 6.1 | It should display a bar chart of expenses grouped by top 20 expense accounts over the last 8 weeks | UI | [ ] |
| 6.2 | It should show week ranges (Monday-Sunday) formatted as dates on the X-axis | UI | [ ] |
| 6.3 | It should provide a vendor typeahead to filter expenses to a specific vendor | Integration | [ ] |
| 6.4 | It should provide an expense account typeahead to filter to a specific account | Integration | [ ] |
| 6.5 | It should refresh the chart when filters change | Integration | [ ] |
| 6.6 | It should default to last 65 days of data but display last 8 weeks | Integration | [ ] |
### Invoice Totals Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 7.1 | It should display a grid of total invoice amounts per vendor per company | UI | [ ] |
| 7.2 | It should provide start and end date range filters | UI | [ ] |
| 7.3 | It should default the date range to the last 30 days | Integration | [ ] |
| 7.4 | It should show the vendor name in a sticky left column | UI | [ ] |
| 7.5 | It should show "-" for zero amounts | UI | [ ] |
| 7.6 | It should push filter changes to browser history | Integration | [ ] |
---
## Reconciliation Reports
### Access Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.1 | It should show the reconciliation navigation link only when the user has reconciliation report permission | Integration | [ ] |
| 8.2 | It should require start and end dates to be submitted via a "Run" button | UI | [ ] |
| 8.3 | It should show a "Please choose a time range to run the report" message when no dates are selected | UI | [ ] |
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.4 | It should display a grid with columns: Bank Account, Source Count, Synced Count, Approved, Unapproved, Requires Feedback, Missing | UI | [ ] |
| 8.5 | It should highlight rows with green background when external count equals synced count | UI | [ ] |
| 8.6 | It should highlight rows with red background when counts mismatch | UI | [ ] |
| 8.7 | It should display a missing transactions count with a tooltip button | UI | [ ] |
| 8.8 | It should show a popup table of missing transaction dates and amounts when the tooltip is clicked | UI | [ ] |
| 8.9 | It should hide the missing transactions tooltip when the count is zero | UI | [ ] |
---
## Plaid Bank Linking
### Account Grid Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 9.1 | It should display a grid of Plaid-linked accounts with columns: Plaid Item, Integreat Status, Plaid Bank Status, Accounts | UI | [ ] |
| 9.2 | It should show a red pill with error message tooltip when any linked bank account has failed or unauthorized status | UI | [ ] |
| 9.3 | It should show a green "Success" pill when all accounts are healthy | UI | [ ] |
| 9.4 | It should display linked accounts with name, masked number, last synced date, and identicon | UI | [ ] |
| 9.5 | It should support sorting by external ID and Plaid bank status | Integration | [ ] |
| 9.6 | It should show an empty grid when no bank accounts are linked | UI | [ ] |
### Link Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 10.1 | It should show a "Link account" button when a client is selected | UI | [ ] |
| 10.2 | It should hide the link button when no client is selected | UI | [ ] |
| 10.3 | It should open a Plaid Link modal when the link button is clicked | UI | [ ] |
| 10.4 | It should create the Plaid item and accounts in the system after successful linking | Integration | [ ] |
| 10.5 | It should redirect back to the Plaid page after successful account linking | Integration | [ ] |
### Re-authenticate Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 11.1 | It should show a "Reauthenticate" button on each row | UI | [ ] |
| 11.2 | It should open Plaid Link in update mode when reauthenticate is clicked | UI | [ ] |
| 11.3 | It should refresh the row after successful reauthentication | Integration | [ ] |
---
## Yodlee Bank Linking
### Account Grid Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 12.1 | It should display a grid of Yodlee provider accounts with columns: Client, Provider Account, Status, Detailed Status, Last Updated, Accounts | UI | [ ] |
| 12.2 | It should hide the Client column when the user has only one client | UI | [ ] |
| 12.3 | It should show a green pill for success status and a yellow pill for other statuses | UI | [ ] |
| 12.4 | It should display linked accounts with name and number | UI | [ ] |
| 12.5 | It should support sorting by status, client, provider account, and last updated | Integration | [ ] |
| 12.6 | It should show an empty grid when no bank accounts are linked | UI | [ ] |
### Link Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 13.1 | It should show a "Link new account" button | UI | [ ] |
| 13.2 | It should disable the link button and show helper text when no client is selected | UI | [ ] |
| 13.3 | It should open a Yodlee Fastlink modal when the link button is clicked | UI | [ ] |
| 13.4 | It should display an error notification and close the modal after 3 seconds when Yodlee returns an error | Integration | [ ] |
### Re-authenticate Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 14.1 | It should show a "Reauthenticate" button per row | UI | [ ] |
| 14.2 | It should open Fastlink in edit mode when reauthenticate is clicked | UI | [ ] |
| 14.3 | It should refresh the row after successful reauthentication | Integration | [ ] |
### Admin Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 15.1 | It should show a refresh button on each row for admin users | Integration | [ ] |
| 15.2 | It should trigger a Yodlee account refresh when the refresh button is clicked | Integration | [ ] |
| 15.3 | It should refresh the row after successful Yodlee refresh | Integration | [ ] |
---
## Generated Reports List
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 16.1 | It should display a grid of previously generated reports with columns: Name, Created by, Created | UI | [ ] |
| 16.2 | It should show the creator name as a pill badge | UI | [ ] |
| 16.3 | It should show an empty grid when no reports have been generated | UI | [ ] |
### Row Action Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 17.1 | It should provide a download link to the report file on each row | UI | [ ] |
| 17.2 | It should show a delete button on each row for admin users | Integration | [ ] |
| 17.3 | It should delete the report and its file when the delete button is clicked | Integration | [ ] |
### Filtering & Sorting Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 18.1 | It should support filtering by date range and client | Integration | [ ] |
| 18.2 | It should support sorting by client, created date, creator, and name | Integration | [ ] |
---
## Cross-Cutting Behaviors
### Client Switching Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 19.1 | It should refresh page content with a 300ms swap animation when the user switches clients | Integration | [ ] |
| 19.2 | It should show appropriate placeholder states when no client is selected on pages that require one | UI | [ ] |
| 19.3 | It should operate 1099 and reports grids across all visible clients when no single client is selected | Integration | [ ] |
### Permission Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 20.1 | It should block access to company pages for unauthenticated users | Integration | [ ] |
| 20.2 | It should block access to company pages for users without client access | Integration | [ ] |
| 20.3 | It should hide the signature section from users without signature edit permission | Integration | [ ] |
| 20.4 | It should hide the reconciliation report navigation link from users without reconciliation report permission | Integration | [ ] |
| 20.5 | It should hide the delete report button from non-admin users | Integration | [ ] |
| 20.6 | It should hide the Yodlee refresh button from non-admin users | Integration | [ ] |
### Bank Account Search Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 21.1 | It should provide a bank account typeahead for searching accounts belonging to a specific client | Integration | [ ] |
| 21.2 | It should show "Please select a client" message when no client is selected in the bank account typeahead | UI | [ ] |
---
## Test Data Requirements
| Entity | Requirements |
|--------|-------------|
| **Clients** | Multiple clients with different codes, names, and addresses; one with complete address, one with no address; one with existing signature file, one without |
| **Vendors** | With 1099 data (legal entity name, TIN, TIN type, 1099 type, address); with and without addresses; who received $600+ in check payments in 2025; who received less than $600; shared across multiple clients |
| **Payments** | Check payments dated within 2025 for 1099 testing; payments of different types (check, ACH) — only checks count toward 1099; payments to vendors across multiple clients |
| **Invoices** | Non-voided invoices with expense account allocations for expense report testing; with different vendors and expense accounts; dated across multiple weeks for 8-week breakdown |
| **Bank Accounts** | Plaid-linked accounts with various statuses (success, error, unauthorized); Yodlee-linked accounts with various statuses |
| **Reports** | At least one generated report with name, creator, created date, and file URL; reports belonging to different clients |
| **Users** | Admin user (full access); user with signature edit permission; user with reconciliation report permission; user with single client access; user with multiple client access; read-only user (should not see company nav) |
## Existing Tests to Preserve
- No existing company-specific behavior tests were identified at the time of writing.
- Any future company behavior tests should be added under `test/clj/auto_ap/company/` or similar.
## Dependencies
- Datomic (primary store for all entities and queries)
- Amazonica S3 (signature image storage and report file storage)
- Plaid API (bank account linking)
- Yodlee API (bank account linking)
- Intuit/QuickBooks API (reconciliation report data)
- Solr (client name search)
- HTMX (server-rendered interactions)
- Alpine.js (signature canvas state, drag-and-drop, modal state, chart initialization)
- Chart.js (expense breakdown bar chart)
- SignaturePad library (canvas signature drawing)
- Plaid Link SDK (Plaid account linking)
- Yodlee Fastlink SDK (Yodlee account linking)
- Jdenticon (account identicons in Plaid grid)

View File

@@ -1,250 +0,0 @@
# Dashboard Behaviors
## Overview
The Dashboard is an admin-only, SSR-based overview page that displays financial summary cards across selected clients. It provides at-a-glance visibility into bank account balances, outstanding tasks, recent sales trends, expense breakdowns, and profit/loss summaries. Cards are loaded lazily via HTMX for progressive rendering.
**Testing Philosophy**
- Prefer unit tests for pure business logic (calculations, formatting, client trimming)
- Use integration tests for database interactions and cross-system flows (Datomic queries, GraphQL calls)
- Use UI tests only for end-to-end happy paths and visual states
- Every behavior must be user-visible; no tests for implementation details
---
## Testing Patterns
### Pattern: Progressive Card Loading
Dashboard cards follow a progressive loading pattern:
1. SSR renders stub cards with loading spinners
2. Each stub triggers an independent HTMX request on load
3. Card endpoints return HTML fragments that replace the stub content
4. Cards load independently without blocking each other
**Test implications:** Unit test the card query logic. Integration test each card endpoint returns valid HTML. UI test only needs to verify the page loads and cards appear.
### Pattern: Client Context Propagation
All dashboard operations depend on selected clients:
1. Client selection triggers a page-wide refresh via HTMX
2. `wrap-trim-clients` middleware limits to 20 clients before queries execute
3. All card endpoints receive the same trimmed client set
**Test implications:** Integration test that changing clients updates all cards. Unit test the trimming logic.
### Pattern: Admin Permission Gating
The dashboard is restricted to admin users:
1. `wrap-admin` middleware checks user role before any data access
2. Non-admin users are redirected before reaching handlers
3. Middleware is applied consistently to all card endpoints
**Test implications:** Integration test each endpoint independently for permission enforcement.
---
## Dashboard Page
### Page Loading Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 1.1 | It should render the main dashboard page with navigation, client selector, and "Dashboard" breadcrumb for admin users | UI | [ ] |
| 1.2 | It should display six stub cards with loading spinners for progressive rendering | UI | [ ] |
| 1.3 | It should trigger independent HTMX requests to load each card's content on page load | Integration | [ ] |
| 1.4 | It should progressively replace stub cards with actual data as responses arrive | UI | [ ] |
---
## Bank Accounts Card
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 2.1 | It should display each client's name, account name, ledger balance, and last sync time | UI | [ ] |
| 2.2 | It should exclude bank accounts with cash type from the display | Integration | [ ] |
| 2.3 | It should format ledger balances as currency ($X,XXX.XX) | Unit + UI | [ ] |
| 2.4 | It should display the last sync timestamp in standard time format when present | Unit + UI | [ ] |
| 2.5 | It should display Intuit balance and sync time for Intuit-linked accounts | UI | [ ] |
| 2.6 | It should display Yodlee available balance, sync time, and pending balance for Yodlee-linked accounts | UI | [ ] |
| 2.7 | It should display Plaid balance and sync time for Plaid-linked accounts | UI | [ ] |
| 2.8 | It should display $0.00 for missing or null balances | Unit + UI | [ ] |
---
## Sales Card
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 3.1 | It should display a bar chart of gross sales for the last 14 days | UI | [ ] |
| 3.2 | It should render an empty bar chart when no sales orders exist in the date range | UI | [ ] |
### Data Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 3.3 | It should query and sum sales order totals by date for the selected clients | Integration | [ ] |
---
## Expense Card
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 4.1 | It should display a pie chart of the top 5 expense accounts for the last month | UI | [ ] |
| 4.2 | It should render an empty pie chart when no invoices with expense accounts exist in the date range | UI | [ ] |
### Data Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 4.3 | It should sum expense amounts by account name for the selected clients | Integration | [ ] |
---
## Profit & Loss Card
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 5.1 | It should display income and expenses aggregated by category (sales, COGS, payroll, controllable, fixed overhead, ownership controllable) | UI | [ ] |
| 5.2 | It should show $0.00 for both income and expenses when no data exists for the period | UI | [ ] |
### Data Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 5.3 | It should query P&L data via GraphQL for the selected clients and last month | Integration | [ ] |
---
## Tasks Card
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 6.1 | It should display the count of unpaid invoices when the count is non-zero | UI | [ ] |
| 6.2 | It should display the count of uncategorized transactions requiring feedback when the count is non-zero | UI | [ ] |
| 6.3 | It should provide a "Pay now" link for unpaid invoices linking to the unpaid invoices page with year date range | UI | [ ] |
| 6.4 | It should provide a "Review now" link for uncategorized transactions linking to the requires-feedback page | UI | [ ] |
| 6.5 | It should hide task sections entirely when their respective counts are zero | Integration | [ ] |
### Data Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 6.6 | It should query Datomic for invoices with unpaid status for the selected clients | Integration | [ ] |
| 6.7 | It should query Datomic for transactions with requires-feedback approval status for the selected clients | Integration | [ ] |
---
## Expense Breakdown Card
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 7.1 | It should display a bar chart breaking down expenses by account | UI | [ ] |
| 7.2 | It should render an empty chart when no expense data exists | UI | [ ] |
| 7.3 | It should provide Vendor and Account typeahead filters | UI | [ ] |
### Data Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 7.4 | It should reload the chart with filtered data when filter selections change | Integration | [ ] |
| 7.5 | It should update the URL with filter query parameters via hx-push-url | Integration | [ ] |
| 7.6 | It should exclude voided invoices from the breakdown | Integration | [ ] |
---
## Filtering Behaviors
### Expense Breakdown Filters
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.1 | It should filter the expense breakdown chart by vendor selection | Integration | [ ] |
| 8.2 | It should filter the expense breakdown chart by expense account selection | Integration | [ ] |
| 8.3 | It should trigger an HTMX request to reload the chart when any filter changes | Integration | [ ] |
---
## Client Selection Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 9.1 | It should update the dashboard content when the user selects different clients from the dropdown | UI | [ ] |
| 9.2 | It should trigger a clientSelected event on the body when client selection changes | Integration | [ ] |
| 9.3 | It should swap the dashboard content area with fresh content for the newly selected clients | Integration | [ ] |
| 9.4 | It should re-fetch all card data with the new client context | Integration | [ ] |
| 9.5 | It should limit reports to the first 20 selected clients from the valid set | Unit + Integration | [ ] |
| 9.6 | It should display a yellow warning banner when more than 20 clients are selected | UI | [ ] |
| 9.7 | It should persist the warning banner across client selection changes until fewer than 21 clients are selected | UI | [ ] |
| 9.8 | It should trim the client set before executing any card data queries | Integration | [ ] |
---
## Error Handling Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 10.1 | It should load each card independently via separate HTMX requests | Integration | [ ] |
| 10.2 | It should not prevent other cards from loading when one card endpoint fails | Integration | [ ] |
| 10.3 | It should display a loading spinner on stub cards until data loads or a timeout occurs | UI | [ ] |
| 10.4 | It should return appropriate HTTP status codes for card endpoint errors without breaking the page layout | Integration | [ ] |
---
## Cross-Cutting Behaviors
### Permission Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 11.1 | It should allow only admin users to access the dashboard page and card endpoints | Integration | [ ] |
| 11.2 | It should redirect non-admin authenticated users to /login with a 302 status | Integration | [ ] |
| 11.3 | It should redirect unauthenticated users to /login with a redirect-to parameter | Integration | [ ] |
| 11.4 | It should verify admin role via middleware before executing any data queries | Integration | [ ] |
### Empty State Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 12.1 | It should render the dashboard page when no clients are selected, with all cards showing empty states | UI | [ ] |
| 12.2 | It should display an empty bank accounts list when no clients are selected | UI | [ ] |
| 12.3 | It should display an empty sales chart when no clients are selected | UI | [ ] |
| 12.4 | It should display an empty expense pie chart when no clients are selected | UI | [ ] |
| 12.5 | It should show $0.00 income and expenses in the P&L card when no clients are selected | UI | [ ] |
| 12.6 | It should hide all task sections when no clients are selected | UI | [ ] |
---
## Test Data Requirements
| Entity | Requirements |
|--------|-------------|
| **Users** | Admin user with access to multiple clients; non-admin user for permission denial |
| **Clients** | Minimum 2, ideally 25+; mix with/without bank accounts |
| **Bank Accounts** | Various types (checking, savings, cash); some linked to Intuit, Yodlee, Plaid; with/without balances and sync timestamps |
| **Sales Orders** | Orders within and outside the 14-day window with totals |
| **Invoices** | With expense accounts and unpaid status; voided invoices to test exclusion |
| **Transactions** | With requires-feedback approval status |
| **Chart of Accounts** | Categories: sales, COGS, payroll, controllable, fixed overhead, ownership controllable |
## Existing Tests to Preserve
- No existing dashboard-specific tests have been identified in the current test suite. Any tests covering dashboard routes or card handlers should be preserved during refactoring.
## Dependencies
- Datomic (primary data store for all card queries)
- GraphQL/Lacinia (P&L data via get-profit-and-loss-raw)
- HTMX (progressive card loading via hx-get/hx-trigger/hx-swap)
- Chart.js (canvas-based charts)
- Alpine.js (chart data binding)

View File

@@ -1,403 +0,0 @@
# Invoice Behaviors
## Overview
Invoices are the core entity of Integreat. This document catalogs every observable behavior with its recommended test strategy and implementation status.
**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
---
## Testing Patterns
### 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
**Test implications:** Validate the query generation, not the rendering. UI tests only need to verify one filter and one sort work end-to-end.
### 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
**Test implications:** Unit test validation logic and state transitions. Integration test the full wizard flow once. UI test only the happy path.
### 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
**Test implications:** Integration test each gate independently. UI tests only verify the happy path with a permitted user.
---
## Invoice List Page
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 1.1 | It should display a table with columns: Client, Vendor, Invoice #, Date, Due, Status, Account, Outstanding, Links | UI | [ ] |
| 1.2 | It should show the Client column only when multiple clients OR multiple locations are selected | Integration | [ ] |
| 1.3 | It should show "Paid" status as a primary-colored pill | UI | [ ] |
| 1.4 | It should show "Voided" status as a red pill | UI | [ ] |
| 1.5 | It should show "Scheduled" status as a yellow pill when a scheduled payment exists | UI | [ ] |
| 1.6 | It should show "Unpaid" status as a secondary-colored pill | UI | [ ] |
| 1.7 | It should display due dates relative to today: "today", "in X days", or "X days ago" with appropriate color coding | Unit + UI | [ ] |
| 1.8 | It should show a partial payment indicator "of $X.XX" when outstanding balance differs from total | UI | [ ] |
| 1.9 | It should display a links dropdown showing payments, transactions, ledger entries, and source files for each invoice | UI | [ ] |
| 1.10 | It should group table rows by vendor name when sorted by vendor | Integration | [ ] |
### Filtering Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 2.1 | It should filter invoices by vendor typeahead selection | Integration | [ ] |
| 2.2 | It should filter invoices by expense account typeahead selection | Integration | [ ] |
| 2.3 | It should filter invoices by date range (invoice date) | Integration | [ ] |
| 2.4 | It should filter invoices by due date range | Integration | [ ] |
| 2.5 | It should filter invoices by amount range (min/max total) | Integration | [ ] |
| 2.6 | It should filter invoices by invoice number partial match | Integration | [ ] |
| 2.7 | It should filter invoices by check number | Integration | [ ] |
| 2.8 | It should filter invoices by status via route (all/unpaid/paid/voided) | Integration | [ ] |
| 2.9 | It should filter invoices by import status (pending/imported) | Integration | [ ] |
| 2.10 | It should support exact-match navigation to a specific invoice by ID, bypassing other filters | Integration | [ ] |
| 2.11 | It should filter to invoices with scheduled payments | Integration | [ ] |
| 2.12 | It should filter to unresolved invoices (missing or unassigned expense accounts) | Integration | [ ] |
| 2.13 | It should filter by expense account location | Integration | [ ] |
| 2.14 | Given multiple filters are applied, when the user changes one filter, then the table should refresh with the combined filter set | Integration | [ ] |
### Sorting Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 3.1 | It should sort by client name ascending/descending | Integration | [ ] |
| 3.2 | It should sort by vendor name ascending/descending | Integration | [ ] |
| 3.3 | It should sort by description original ascending/descending | Integration | [ ] |
| 3.4 | It should sort by expense account location ascending/descending | Integration | [ ] |
| 3.5 | It should sort by invoice date ascending/descending | Integration | [ ] |
| 3.6 | It should sort by due date ascending/descending, with nulls last | Integration | [ ] |
| 3.7 | It should sort by invoice number ascending/descending | Integration | [ ] |
| 3.8 | It should sort by total amount ascending/descending | Integration | [ ] |
| 3.9 | It should sort by outstanding balance ascending/descending | Integration | [ ] |
| 3.10 | Given the user clicks a column header twice, then the sort direction should toggle | Integration | [ ] |
### Pagination Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 4.1 | It should display 25 invoices per page by default | Integration | [ ] |
| 4.2 | It should allow changing the per-page count | Integration | [ ] |
| 4.3 | It should calculate the total outstanding balance and total amount across ALL matching invoices, not just the current page | Unit | [ ] |
### Selection Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 5.1 | It should allow selecting individual invoices via checkboxes | UI | [ ] |
| 5.2 | It should allow selecting all visible invoices via a header checkbox | UI | [ ] |
| 5.3 | It should allow selecting all filtered invoices (up to 250) for bulk operations | Integration | [ ] |
| 5.4 | Given invoices are selected, when the user applies a filter, then the selection should be cleared | Integration | [ ] |
### Row Action Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 6.1 | It should show a void button for unpaid invoices when the user has delete permission | UI | [ ] |
| 6.2 | It should show an edit button for unpaid and paid invoices when the user has edit permission | UI | [ ] |
| 6.3 | It should show an unvoid button for voided invoices when the user has edit permission | UI | [ ] |
| 6.4 | It should show an undo-autopay button for paid invoices with scheduled payments and no linked payments, when the user has edit permission | UI | [ ] |
| 6.5 | Given a paid invoice with linked non-voided payments, when the user attempts to void it, then it should be blocked with a message to void payments first | Integration | [ ] |
### Pay Button Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 7.1 | It should display the pay button disabled when no invoices are selected | UI | [ ] |
| 7.2 | It should display the pay button disabled when invoices from multiple clients are selected | UI | [ ] |
| 7.3 | It should display the pay button disabled when selected invoices have mixed positive and negative vendor totals | UI | [ ] |
| 7.4 | It should display "Pay N invoices ($X.XX)" when valid invoices are selected | UI | [ ] |
| 7.5 | It should display "Pay invoices using credit" when selected invoices for a single vendor have a net zero balance | UI | [ ] |
| 7.6 | It should show a tooltip explaining why the pay button is disabled | UI | [ ] |
---
## New Invoice Wizard
### Basic Details Step
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.1 | It should require client, vendor, date, invoice number, and total | Integration | [ ] |
| 8.2 | It should auto-calculate the due date from vendor terms when client, date, and vendor are selected | Unit | [ ] |
| 8.3 | It should auto-calculate the scheduled payment date from vendor autopay settings | Unit | [ ] |
| 8.4 | It should suggest the vendor's default expense account | Unit | [ ] |
| 8.5 | It should prevent duplicate invoice numbers for the same vendor and client | Unit + Integration | [ ] |
| 8.6 | It should allow editing all fields when creating a new invoice | UI | [ ] |
### Expense Accounts Step
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 9.1 | It should allow adding multiple expense account rows | UI | [ ] |
| 9.2 | It should allow selecting an account, location, and amount per row | UI | [ ] |
| 9.3 | It should auto-populate the location from the account's configured location, or default to "Shared" | Unit | [ ] |
| 9.4 | It should validate that expense account amounts sum to the invoice total | Unit + Integration | [ ] |
| 9.5 | Given a "Shared" location, when the invoice is saved, then the amount should be spread equally across all client locations | Unit | [ ] |
| 9.6 | Given a "Shared" location with an odd total, when spread across N locations, then the remainder should be distributed 1 cent at a time to the first locations | Unit | [x] |
| 9.7 | Given a negative total, when spread across locations, then negative amounts should be distributed correctly | Unit | [x] |
| 9.8 | It should allow removing individual account rows | UI | [ ] |
| 9.9 | It should update the total and balance dynamically when amounts change | UI | [ ] |
### Next Steps
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 10.1 | Given a new invoice is saved successfully, then the wizard should show "Next Steps" with Pay now, Add another, and Close options | UI | [ ] |
| 10.2 | Given the user clicks "Pay now", then the pay wizard should open for the newly created invoice | UI | [ ] |
---
## Edit Invoice
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 11.1 | It should allow editing unpaid and paid invoices | Integration | [ ] |
| 11.2 | It should disable the vendor field when editing | UI | [ ] |
| 11.3 | It should allow modifying expense account amounts, adding/removing accounts | Integration | [ ] |
| 11.4 | It should validate that modified amounts still sum to the total | Unit + Integration | [ ] |
| 11.5 | Given the user saves changes, then the invoice row should update in place without a full page reload | UI | [ ] |
| 11.6 | It should block editing invoices with dates before the client's locked-until date | Integration | [ ] |
---
## Pay Wizard
### Payment Method Selection
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 12.1 | It should show bank account cards with type-appropriate icons (cash=green, check=blue, credit=purple) | UI | [ ] |
| 12.2 | It should allow selecting "Print check" for check-type bank accounts | UI | [ ] |
| 12.3 | It should allow selecting "With cash" for cash-type bank accounts | UI | [ ] |
| 12.4 | It should allow selecting "Debit" for any bank account | UI | [ ] |
| 12.5 | It should allow selecting "Handwrite check" when a single vendor is selected with positive balance | UI | [ ] |
### Payment Details
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 13.1 | It should display a grid of selected invoices with vendor, number, total, and pay amount | UI | [ ] |
| 13.2 | It should default to "Pay in full" mode, paying the outstanding balance of each invoice | Integration | [ ] |
| 13.3 | It should allow switching to "Customize payments" mode to set individual pay amounts | UI | [ ] |
| 13.4 | It should validate that custom payment amounts do not exceed the outstanding balance | Unit + Integration | [ ] |
| 13.5 | It should require a check number for handwritten checks | Integration | [ ] |
| 13.6 | It should block payment if the invoice date is before the client's locked-until date | Integration | [ ] |
| 13.7 | Given the user submits a check payment, when successful, then a PDF download link should be provided | Integration | [ ] |
### Credit Payment
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 14.1 | Given selected invoices for a single vendor with a net zero balance, when the user clicks pay, then a credit payment should be created offsetting credit invoices against payment invoices | Integration | [ ] |
| 14.2 | It should block credit payment when multiple vendors are selected | Integration | [ ] |
| 14.3 | It should block credit payment when the net balance is positive | Integration | [ ] |
---
## Bulk Edit
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 15.1 | It should allow selecting multiple invoices and opening the bulk edit wizard | UI | [ ] |
| 15.2 | It should allow adding expense account rows with account, location, and percentage | UI | [ ] |
| 15.3 | It should validate that percentages sum to 100% | Unit + Integration | [ ] |
| 15.4 | Given valid percentages, when submitted, then all selected invoices should be coded with the new expense accounts | Integration | [ ] |
| 15.5 | It should exclude invoices with dates before the client's locked-until date | Integration | [ ] |
| 15.6 | It should spread "Shared" locations across all client locations, rounding cents correctly | Unit | [ ] |
---
## Bulk Void
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 16.1 | It should show a confirmation modal with the count of invoices to void | UI | [ ] |
| 16.2 | It should require admin permission for bulk void operations | Integration | [ ] |
| 16.3 | Given confirmed, when voiding, then linked cash payments should be voided automatically | Integration | [ ] |
| 16.4 | Given confirmed, when voiding, then each invoice's total, outstanding balance, and expense account amounts should be set to 0 | Integration | [ ] |
| 16.5 | It should exclude invoices with linked non-cash payments | Integration | [ ] |
| 16.6 | It should exclude invoices with dates before the client's locked-until date | Integration | [ ] |
| 16.7 | Given successful voiding, then the table should refresh with a success notification | UI | [ ] |
---
## Single Void
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 17.1 | Given an unpaid invoice with no linked payments, when the user voids it, then the invoice status should change to voided with zero amounts | Integration | [ ] |
| 17.2 | Given a paid invoice with linked payments, when the user attempts to void it, then it should be blocked with an error message | Integration | [ ] |
| 17.3 | It should block voiding invoices with dates before the client's locked-until date | Integration | [ ] |
| 17.4 | Given successful voiding, then the row should update in place with a "live-removed" animation | UI | [ ] |
---
## Unvoid
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 18.1 | Given a voided invoice, when the user unvoids it, then it should restore the original status, total, outstanding balance, and expense accounts from Datomic history | Integration | [ ] |
| 18.2 | It should require edit permission and client access | Integration | [ ] |
| 18.3 | It should block unvoiding invoices with dates before the client's locked-until date | Integration | [ ] |
| 18.4 | Given successful unvoiding, then the row should update in place with a flash animation | UI | [ ] |
---
## Undo Autopay
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 19.1 | Given a paid invoice with a scheduled payment and no linked payments, when the user undoes autopay, then the status should reset to unpaid and outstanding should equal total | Integration | [ ] |
| 19.2 | It should block undoing autopay for invoices without scheduled payments | Unit + Integration | [ ] |
| 19.3 | It should block undoing autopay for invoices with linked payments | Unit + Integration | [ ] |
| 19.4 | It should block undoing autopay for invoices that are not paid | Unit + Integration | [ ] |
| 19.5 | It should block undoing autopay for invoices with dates before the client's locked-until date | Integration | [ ] |
---
## Import Page
### Upload Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 20.1 | It should allow uploading CSV and PDF files via drag-and-drop | UI | [ ] |
| 20.2 | It should parse CSV files directly | Integration | [ ] |
| 20.3 | It should send PDF files to AWS Textract for OCR parsing when enabled | Integration | [ ] |
| 20.4 | It should create invoices with pending import status | Integration | [ ] |
| 20.5 | It should display results with success/failure per file | UI | [ ] |
| 20.6 | It should allow force-overriding client, vendor, location, and ChatGPT parsing mode | UI | [ ] |
### Validation Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 21.1 | It should reject uploads missing required fields (client, vendor, date, total) | Integration | [ ] |
| 21.2 | It should reject uploads where the user has no access to the client | Integration | [ ] |
| 21.3 | It should reject uploads with unmatchable vendors, showing a search hint | Integration | [ ] |
### Approve/Disapprove Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 22.1 | Given a pending imported invoice, when approved, then its status should change to imported | Integration | [ ] |
| 22.2 | Given a pending imported invoice, when disapproved, then it should be deleted | Integration | [ ] |
| 22.3 | It should support bulk approve/disapprove with selection | Integration | [ ] |
---
## Glimpse (OCR Import)
### Upload Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 23.1 | It should allow uploading PDF files | UI | [ ] |
| 23.2 | It should upload the file to S3 and start an AWS Textract job | Integration | [ ] |
| 23.3 | It should poll every 5 seconds while the Textract job is in progress | Integration | [ ] |
| 23.4 | Given a successful Textract job, then it should display extracted fields with confidence scores | UI | [ ] |
### Field Extraction Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 24.1 | It should extract total from AMOUNT_DUE or TOTAL fields | Unit | [ ] |
| 24.2 | It should extract customer from CUSTOMER_NUMBER or RECEIVER_NAME, falling back to Solr search | Unit + Integration | [ ] |
| 24.3 | It should extract vendor from VENDOR_NAME, falling back to Solr search | Unit + Integration | [ ] |
| 24.4 | It should extract date from INVOICE_RECEIPT_DATE, ORDER_DATE, or DELIVERY_DATE | Unit | [ ] |
| 24.5 | It should extract invoice number from INVOICE_RECEIPT_ID or PO_NUMBER | Unit | [ ] |
### Form Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 25.1 | It should show a side-by-side layout with PDF preview and form | UI | [ ] |
| 25.2 | It should display alternative values as clickable pills for each field | UI | [ ] |
| 25.3 | It should require selecting client and vendor from alternatives (fields disabled until selected) | UI | [ ] |
| 25.4 | Given the user saves, then it should create an invoice linked to the textract job | Integration | [ ] |
---
## Cross-Cutting Behaviors
### Permission Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 26.1 | It should block invoice creation for users without `:create` permission | Integration | [ ] |
| 26.2 | It should block invoice editing for users without `:edit` permission | Integration | [ ] |
| 26.3 | It should block invoice voiding for users without `:delete` permission | Integration | [ ] |
| 26.4 | It should block invoice payment for users without `:pay` permission | Integration | [ ] |
| 26.5 | It should block bulk delete for non-admin users | Integration | [ ] |
| 26.6 | It should block bulk edit for users without `:bulk-edit` permission | Integration | [ ] |
| 26.7 | It should block import for users without `:import` permission | Integration | [ ] |
| 26.8 | It should verify the user has access to the invoice's client before any mutation | Integration | [ ] |
### Lock Date Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 27.1 | It should block editing invoices dated before the client's locked-until date | Integration | [ ] |
| 27.2 | It should block paying invoices dated before the client's locked-until date | Integration | [ ] |
| 27.3 | It should block voiding invoices dated before the client's locked-until date | Integration | [ ] |
| 27.4 | It should block importing invoices dated before the client's locked-until date | Integration | [ ] |
| 27.5 | It should block approving imported invoices dated before the client's locked-until date | Integration | [ ] |
| 27.6 | It should filter out locked invoices from bulk operations | Integration | [ ] |
| 27.7 | It should show a warning when some selected invoices are locked | UI | [ ] |
### Legacy Route Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 28.1 | It should redirect old SPA routes (`/invoices`, `/invoices/unpaid`, etc.) to the new SSR routes | Integration | [ ] |
---
## Test Data Requirements
| Entity | Requirements |
|--------|-------------|
| **Clients** | Multiple clients with different locations; some with locked-until dates |
| **Vendors** | With/without default accounts; with/without terms and autopay settings |
| **Accounts** | Expense accounts with/without invoice allowance; different locations |
| **Bank Accounts** | Check, cash, and credit types |
| **Invoices** | Various statuses (unpaid, paid, voided, scheduled), dates, amounts |
| **Payments** | Linked to invoices; cash and check types |
| **Files** | Valid CSV, PDF with text, PDF requiring OCR |
## Existing Tests to Preserve
- `test/clj/auto_ap/ssr/invoice/new_invoice_wizard_test.clj` — Location spreading logic
- `test/clj/auto_ap/integration/routes/invoice_test.clj` — Import routes
- `test/clj/auto_ap/integration/graphql/invoices.clj` — GraphQL invoice operations
## Dependencies
- Datomic (primary store, history for unvoid)
- AWS S3 (file storage)
- AWS Textract (OCR)
- Solr (search for Glimpse matching)
- HTMX/Alpine.js (frontend interactivity)

View File

@@ -1,519 +0,0 @@
# 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.
**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
---
## Testing Patterns
### 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
**Test implications:** Validate the query generation, not the rendering. UI tests only need to verify one filter and one sort work end-to-end.
### Pattern: Modal Form Behaviors
Modal forms are HTMX-driven dialogs:
1. Opened via GET request that renders a form fragment
2. Form submissions are POST/PUT with validation
3. On success, the modal closes and the table updates in place
4. On validation failure, the modal shows error messages
**Test implications:** Unit test validation logic. Integration test the full modal flow once. UI test only the happy path.
### Pattern: Report Behaviors
Financial reports follow a consistent pattern:
1. Form with client multi-select, date/period selectors, and toggles
2. Run button triggers HTMX request to generate report
3. Report data is computed from account snapshots and running balances
4. Export button generates PDF and returns a download modal
**Test implications:** Unit test calculation logic. Integration test report generation with various filter combinations. UI test only one report end-to-end.
### Pattern: Permission Gates
Every mutating operation checks:
1. `assert-can-see-client` — user has access to the client
2. `assert-not-locked` — entry date >= client locked-until
3. `can?` — user has the specific permission for the activity
**Test implications:** Integration test each gate independently. UI tests only verify the happy path with a permitted user.
---
## Ledger Entries List
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 1.1 | It should display a paginated, sortable data grid of journal entries | UI | [ ] |
| 1.2 | It should show the Client column only when multiple clients OR multiple locations are selected | Integration | [ ] |
| 1.3 | It should display the Vendor column, falling back to `alternate-description` when no vendor is present | Integration | [ ] |
| 1.4 | It should hide the Source column on the internal ledger page | UI | [ ] |
| 1.5 | It should hide the External ID column on the internal ledger page | UI | [ ] |
| 1.6 | It should truncate the External ID column to a max-width when displayed | UI | [ ] |
| 1.7 | It should display the Date column with formatted dates | UI | [ ] |
| 1.8 | It should display the Amount column formatted as currency | UI | [ ] |
| 1.9 | It should display Debit lines with account, location, and amount per line item | UI | [ ] |
| 1.10 | It should display Credit lines with account, location, and amount per line item | UI | [ ] |
| 1.11 | It should display a Links dropdown with links to original invoice, source file, transaction, or memo | UI | [ ] |
| 1.12 | It should show the page title reflecting the status filter, e.g. "Unpaid Register" or "Paid Register" | UI | [ ] |
| 1.13 | It should show an "Add journal entry" button on the internal ledger page | UI | [ ] |
| 1.14 | It should hide the "Add journal entry" button on the external ledger page | UI | [ ] |
| 1.15 | It should show a client selection sidebar when the user has access to multiple clients | UI | [ ] |
### Filtering Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 2.1 | It should filter entries by vendor typeahead selection | Integration | [ ] |
| 2.2 | It should filter entries by account typeahead selection | Integration | [ ] |
| 2.3 | It should filter entries by bank account via radio filter | Integration | [ ] |
| 2.4 | It should refresh the bank account filter when the client selection changes | Integration | [ ] |
| 2.5 | It should filter entries by date range | Integration | [ ] |
| 2.6 | It should filter entries by invoice number text search | Integration | [ ] |
| 2.7 | It should filter entries by account code range (gte/lte inputs) | Integration | [ ] |
| 2.8 | It should filter entries by amount range (gte/lte inputs) | Integration | [ ] |
| 2.9 | It should filter to unbalanced entries when "Show unbalanced" is checked | Integration | [ ] |
| 2.10 | It should support exact-match navigation to a specific entry by ID, bypassing other filters | Integration | [ ] |
| 2.11 | It should clear the exact match ID pill when clicked | UI | [ ] |
| 2.12 | Given multiple filters are applied, when the user changes one filter, then the table should refresh with the combined filter set | Integration | [ ] |
### Sorting Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 3.1 | It should sort by Client ascending/descending | Integration | [ ] |
| 3.2 | It should sort by Vendor ascending/descending | Integration | [ ] |
| 3.3 | It should sort by Source ascending/descending | Integration | [ ] |
| 3.4 | It should sort by External ID ascending/descending | Integration | [ ] |
| 3.5 | It should sort by Date ascending/descending | Integration | [ ] |
| 3.6 | It should sort by Amount ascending/descending | Integration | [ ] |
| 3.7 | It should sort by Account ascending/descending | Integration | [ ] |
| 3.8 | It should default to Date ascending | Integration | [ ] |
| 3.9 | Given sorting by Vendor, then rows should be grouped with break headers | Integration | [ ] |
| 3.10 | Given sorting by Source, then rows should be grouped with break headers | Integration | [ ] |
| 3.11 | Given the user clicks a column header twice, then the sort direction should toggle | Integration | [ ] |
### Pagination Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 4.1 | It should display 25 entries per page by default | Integration | [ ] |
| 4.2 | It should allow changing the per-page count | Integration | [ ] |
| 4.3 | It should show pagination controls at the bottom of the table | UI | [ ] |
### Row Action Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 5.1 | It should show a void button for unpaid invoices when the user has `:delete :invoice` permission | UI | [ ] |
| 5.2 | It should show an edit button for unpaid and paid invoices when the user has `:edit :invoice` permission | UI | [ ] |
| 5.3 | It should show an unvoid button for voided invoices when the user has `:edit :invoice` permission | UI | [ ] |
| 5.4 | It should show a trash icon with confirmation for delete operations | UI | [ ] |
### CSV Export Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 6.1 | It should export all matching entries with line-item-level rows | Integration | [ ] |
| 6.2 | It should include columns: ID, Client, Vendor, Source, External ID, Date, Amount, Account, Debit, Credit | Integration | [ ] |
---
## New Journal Entry
### Modal Form Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 7.1 | It should open as a modal dialog 750px wide | UI | [ ] |
| 7.2 | It should show a client typeahead pre-filled if a client is already selected on the parent page | UI | [ ] |
| 7.3 | It should show a date input defaulting to today in MM/DD/YYYY format | UI | [ ] |
| 7.4 | It should show a vendor typeahead disabled when editing an existing entry | UI | [ ] |
| 7.5 | It should show a total amount input requiring a value of at least $0.01 | Unit + Integration | [ ] |
| 7.6 | It should show an optional memo text input | UI | [ ] |
| 7.7 | It should display a line items grid with Account, Location, Debit, and Credit columns | UI | [ ] |
### Line Item Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.1 | It should allow account typeahead search scoped to the selected client | Integration | [ ] |
| 8.2 | It should update the location dropdown based on the selected account's required location | Integration | [ ] |
| 8.3 | It should lock the location dropdown to a fixed location when the account requires it | Integration | [ ] |
| 8.4 | It should show all client locations when the account has no location restriction | Integration | [ ] |
| 8.5 | It should add new line item rows via HTMX request | UI | [ ] |
| 8.6 | It should allow removing line item rows with an X button | UI | [ ] |
### Validation Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 9.1 | It should require a client | Unit + Integration | [ ] |
| 9.2 | It should require a valid date | Unit + Integration | [ ] |
| 9.3 | It should require a vendor | Unit + Integration | [ ] |
| 9.4 | It should require an amount of at least $0.01 | Unit + Integration | [ ] |
| 9.5 | It should require each line item to have an allowed account | Unit + Integration | [ ] |
| 9.6 | It should require each line item to have a location belonging to the account | Unit + Integration | [ ] |
| 9.7 | It should validate that debits sum to the total amount | Unit + Integration | [ ] |
| 9.8 | It should validate that credits sum to the total amount | Unit + Integration | [ ] |
| 9.9 | It should validate that debits and credits each equal the journal entry amount | Unit + Integration | [ ] |
| 9.10 | It should block saving when the entry date is on or before the client's `locked-until` date | Integration | [ ] |
### Save Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 10.1 | It should generate an external ID in the format `manual-<uuid>` | Unit | [ ] |
| 10.2 | It should update the client's `ledger-last-change` timestamp | Integration | [ ] |
| 10.3 | Given a new entry is saved successfully, then it should prepend the new row to the table and close the modal | UI | [ ] |
| 10.4 | Given an existing entry is saved successfully, then it should replace the existing row in the table and close the modal | UI | [ ] |
---
## External Import
### Clipboard Paste Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 11.1 | It should allow clicking a "Load from clipboard" button | UI | [ ] |
| 11.2 | It should read TSV data from the browser clipboard | UI | [ ] |
| 11.3 | It should parse tab-separated values with columns: Id, Client, Source, Vendor, Date, Account Code, Location, Debit, Credit | Integration | [ ] |
### Parse Validation Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 12.1 | It should validate that all rows have required fields | Integration | [ ] |
| 12.2 | It should validate that dates are parseable | Unit + Integration | [ ] |
| 12.3 | It should validate that account codes are numeric or bank account strings | Unit + Integration | [ ] |
| 12.4 | It should validate that locations are 1-2 characters | Unit + Integration | [ ] |
| 12.5 | It should validate that debits and credits are valid money amounts | Unit + Integration | [ ] |
### Import Validation Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 13.1 | It should validate that the client code exists | Integration | [ ] |
| 13.2 | It should validate that the vendor exists, creating a hidden vendor if missing | Integration | [ ] |
| 13.3 | It should block entries for dates when the client is locked | Integration | [ ] |
| 13.4 | It should validate that debits and credits balance per entry | Unit + Integration | [ ] |
| 13.5 | It should warn when an entry totals $0.00 | Unit + Integration | [ ] |
| 13.6 | It should validate that the location belongs to the client | Integration | [ ] |
| 13.7 | It should validate that the account code exists | Integration | [ ] |
| 13.8 | It should validate that bank account codes belong to the client | Integration | [ ] |
| 13.9 | It should validate that account location requirements are satisfied | Integration | [ ] |
### Import Result Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 14.1 | It should import successful entries | Integration | [ ] |
| 14.2 | It should ignore entries with warnings, removing them if they previously existed | Integration | [ ] |
| 14.3 | It should block import and show error counts when entries have errors | Integration | [ ] |
| 14.4 | It should retract existing entries by external ID before importing | Integration | [ ] |
| 14.5 | It should index imported entries in Solr asynchronously | Integration | [ ] |
---
## P&L Report
### Form Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 15.1 | It should show a customer multi-select typeahead with a max of 20 selections | UI | [ ] |
| 15.2 | It should default to the first 5 customers when "all" is selected | Integration | [ ] |
| 15.3 | It should show a periods dropdown defaulting to year-to-date | UI | [ ] |
| 15.4 | It should show a "Column per location" toggle | UI | [ ] |
| 15.5 | It should show an "Include deltas" toggle | UI | [ ] |
| 15.6 | It should trigger report generation via HTMX PUT on the Run button | UI | [ ] |
| 15.7 | It should show an Export PDF button | UI | [ ] |
### Report Generation Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 16.1 | It should compute running balances before generating the report | Integration | [ ] |
| 16.2 | It should query detailed account snapshots for each client and period end date | Integration | [ ] |
| 16.3 | It should calculate amounts as debits minus credits for assets, dividends, and expenses | Unit | [ ] |
| 16.4 | It should calculate amounts as credits minus debits for liabilities, equity, and revenue | Unit | [ ] |
| 16.5 | It should group data by client, location, and period | Integration | [ ] |
### Report Output Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 17.1 | It should display a summary table with Sales, COGS, Payroll, Gross Profits, Overhead, and Net Income | UI | [ ] |
| 17.2 | It should display a detail table with account-level breakdown within each category | UI | [ ] |
| 17.3 | It should calculate percent of sales for each row | Unit | [ ] |
| 17.4 | It should show deltas between periods when enabled | UI | [ ] |
| 17.5 | It should show each location as separate columns when column-per-location mode is enabled | UI | [ ] |
### Warning Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 18.1 | It should warn when more than 20 clients are selected and truncate to 20 | Integration | [ ] |
| 18.2 | It should warn about unresolved ledger entries with missing numeric codes | Integration | [ ] |
| 18.3 | It should show sample links to admin history for invalid entries when the user has `:view :history` permission | Integration | [ ] |
### PDF Export Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 19.1 | It should generate a PDF with Calibri Light font at 6pt | Integration | [ ] |
| 19.2 | It should upload the PDF to S3 at `reports/profit-and-loss/<uuid>/<name>.pdf` | Integration | [ ] |
| 19.3 | It should persist a report record in Datomic | Integration | [ ] |
| 19.4 | It should return a modal with a download link | UI | [ ] |
---
## Balance Sheet
### Form Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 20.1 | It should show a customer multi-select typeahead with a max of 20 selections | UI | [ ] |
| 20.2 | It should default to the first 5 customers when "all" is selected | Integration | [ ] |
| 20.3 | It should show a date dropdown defaulting to today | UI | [ ] |
| 20.4 | It should show an "Include deltas" toggle | UI | [ ] |
| 20.5 | It should trigger report generation via HTMX GET on the Run button | UI | [ ] |
| 20.6 | It should show an Export PDF button | UI | [ ] |
### Report Generation Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 21.1 | It should compute running balances before generating the report | Integration | [ ] |
| 21.2 | It should query account snapshots as of each selected date | Integration | [ ] |
| 21.3 | It should group accounts into Assets, Liabilities, and Owner's Equity | Integration | [ ] |
| 21.4 | It should include Retained Earnings as net income across all P&L categories | Unit | [ ] |
### Report Output Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 22.1 | It should display an Assets section with account detail and subtotal | UI | [ ] |
| 22.2 | It should display a Liabilities section with account detail and subtotal | UI | [ ] |
| 22.3 | It should display an Owner's Equity section with account detail and subtotal | UI | [ ] |
| 22.4 | It should display a Retained Earnings line | UI | [ ] |
| 22.5 | It should show delta columns between periods when enabled and multiple dates are selected | UI | [ ] |
### Warning Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 23.1 | It should warn when more than 20 clients are selected | Integration | [ ] |
| 23.2 | It should warn about unresolved ledger entries | Integration | [ ] |
### PDF Export Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 24.1 | It should generate a PDF and upload to S3 at `reports/balance-sheet/<uuid>/<name>.pdf` | Integration | [ ] |
| 24.2 | It should persist a report record in Datomic | Integration | [ ] |
| 24.3 | It should return a modal with a download link | UI | [ ] |
---
## Cash Flows
### Form Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 25.1 | It should show a customer multi-select typeahead with a max of 20 selections | UI | [ ] |
| 25.2 | It should default to the first 5 customers when "all" is selected | Integration | [ ] |
| 25.3 | It should show a periods dropdown defaulting to year-to-date | UI | [ ] |
| 25.4 | It should trigger report generation via HTMX PUT on the Run button | UI | [ ] |
| 25.5 | It should show an Export PDF button | UI | [ ] |
### Report Generation Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 26.1 | It should query account snapshots as of period end plus one day | Integration | [ ] |
| 26.2 | It should group accounts into Operating Activities, Investment Activities, Financing Activities, and Cash | Integration | [ ] |
| 26.3 | It should calculate cash flow effect by adding or subtracting based on account code ranges | Unit | [ ] |
### Report Output Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 27.1 | It should display Net Income as the starting point | UI | [ ] |
| 27.2 | It should display Operating Activities detail with increases, decreases, and cash impact | UI | [ ] |
| 27.3 | It should display Investment Activities detail | UI | [ ] |
| 27.4 | It should display Financing Activities detail | UI | [ ] |
| 27.5 | It should display Change in Cash and Cash Equivalents total | UI | [ ] |
| 27.6 | It should display Bank Accounts / Cash detail | UI | [ ] |
### Warning Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 28.1 | It should warn when more than 20 clients are selected | Integration | [ ] |
| 28.2 | It should warn about unresolved ledger entries | Integration | [ ] |
### PDF Export Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 29.1 | It should generate a PDF and upload to S3 at `reports/cash-flows/<uuid>/<name>.pdf` | Integration | [ ] |
| 29.2 | It should persist a report record in Datomic | Integration | [ ] |
| 29.3 | It should return a modal with a download link | UI | [ ] |
---
## Investigation
### Modal Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 30.1 | It should open as a modal dialog from report table cell clicks | UI | [ ] |
| 30.2 | It should filter ledger entries by the clicked cell's filters: account code range, client, location, and date range | Integration | [ ] |
| 30.3 | It should display a raw table without checkboxes | UI | [ ] |
| 30.4 | It should constrain the modal to a max height of 600px with scrollable content | UI | [ ] |
### Table Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 31.1 | It should use the same query schema as the main ledger list | Integration | [ ] |
| 31.2 | It should support sorting and pagination | Integration | [ ] |
| 31.3 | It should not push URL state on filter or sort changes | Integration | [ ] |
---
## Cross-Cutting Behaviors
### Report Generation Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 32.1 | It should call `upsert-running-balance` before querying to ensure cached balances are current | Integration | [ ] |
| 32.2 | It should use `detailed-account-snapshot` Datomic query for raw report data | Integration | [ ] |
| 32.3 | It should build account lookups per-client via `build-account-lookup` | Integration | [ ] |
| 32.4 | It should skip entries without numeric codes and warn about unresolved entries | Integration | [ ] |
### Export Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 33.1 | It should support PDF export via `clj-pdf` for all three reports | Integration | [ ] |
| 33.2 | It should use Calibri Light 6pt font on letter size for all PDF exports | Integration | [ ] |
| 33.3 | It should upload PDFs to the S3 data bucket with a UUID-based key | Integration | [ ] |
| 33.4 | It should persist report metadata to Datomic with name, client, key, URL, creator, and created timestamp | Integration | [ ] |
| 33.5 | It should return a modal with an S3 download link after export | UI | [ ] |
### Filtering and Sorting Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 34.1 | It should apply ledger list filters via HTMX on change with a 500ms debounce | Integration | [ ] |
| 34.2 | It should apply hot filters via HTMX on keyup with a 1000ms debounce | Integration | [ ] |
| 34.3 | It should refresh the bank account filter when client selection changes | Integration | [ ] |
| 34.4 | It should support multiple sort keys with ascending and descending direction | Integration | [ ] |
| 34.5 | It should default to date ascending sort | Integration | [ ] |
| 34.6 | It should bypass all other filters when an exact match ID filter is active | Integration | [ ] |
### Permission Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 35.1 | It should require an authenticated user for all ledger pages | Integration | [ ] |
| 35.2 | It should require `:read :ledger` permission for the main ledger page | Integration | [ ] |
| 35.3 | It should require `:edit :ledger` permission for new and edit journal entry | Integration | [ ] |
| 35.4 | It should require `:import :ledger` permission plus admin assertion for external import | Integration | [ ] |
| 35.5 | It should require `:read :profit-and-loss` permission for the P&L report | Integration | [ ] |
| 35.6 | It should require `:read :balance-sheet` permission for the balance sheet | Integration | [ ] |
| 35.7 | It should require `:read :cash-flows` permission for the cash flows report | Integration | [ ] |
| 35.8 | It should restrict users to clients they have permission for via `assert-can-see-client` | Integration | [ ] |
| 35.9 | It should require `:delete :invoice` permission for invoice void actions | Integration | [ ] |
| 35.10 | It should require `:edit :invoice` permission for invoice edit and unvoid actions | Integration | [ ] |
### Empty State Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 36.1 | It should show an empty table with pagination showing 0 results when no entries exist | UI | [ ] |
| 36.2 | It should show an empty table with filter pills remaining when filters match nothing | UI | [ ] |
| 36.3 | It should show an empty report form with no table rendered when report data is empty | UI | [ ] |
### Data Locking Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 37.1 | It should block creating journal entries for dates on or before the client's `locked-until` date | Integration | [ ] |
| 37.2 | It should reject external import entries for locked dates | Integration | [ ] |
### Unbalanced Entry Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 38.1 | It should compute debit and credit sums per entry for the "Show unbalanced" filter | Unit | [ ] |
| 38.2 | It should display unbalanced entries in the normal view without filtering | UI | [ ] |
### Account Location Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 39.1 | It should reject locations other than the fixed location for accounts with fixed locations | Integration | [ ] |
| 39.2 | It should reject "A" (all) location for accounts without location restrictions | Integration | [ ] |
| 39.3 | It should validate account location requirements on both the frontend location select and backend schema | Integration | [ ] |
### Running Balance Cache Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 40.1 | It should recompute balances for dirty line items via `refresh-running-balance-cache` | Integration | [ ] |
| 40.2 | It should mark a changed entry's line items and subsequent entries as dirty | Integration | [ ] |
| 40.3 | It should skip recomputation for non-dirty entries | Integration | [ ] |
---
## Test Data Requirements
| Entity | Requirements |
|--------|-------------|
| **Clients** | At least 2 clients with different locations; at least 1 client with a `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** | Balanced entries (debits = credits); unbalanced entries (debits ≠ credits); 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 |
## Existing Tests to Preserve
- `test/clj/auto_ap/ssr/ledger/ledger_test.clj` — Ledger page rendering and grid behaviors
- `test/clj/auto_ap/integration/routes/ledger_test.clj` — Ledger routes and mutations
- `test/clj/auto_ap/ledger/reports_test.clj` — Report generation and calculation logic
## 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

View File

@@ -1,340 +0,0 @@
# Legacy SPA Behaviors
## Overview
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.**
**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
---
## Testing Patterns
### 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`
**Test implications:** Integration test the GraphQL query resolution and response shape. Do not test the re-frame effect machinery.
### 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`)
**Test implications:** Test via integration tests that verify the correct data appears after state transitions. Do not test subscription internals.
### 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.
---
## Home
### Dashboard Display
| # | 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) | [ ] |
---
## Login
### Authentication
| # | 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 | [ ] |
### Needs Activation
| # | 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
### List Display
| # | 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) | [ ] |
### Transaction Edit
| # | 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) | [ ] |
### Bulk Operations (Admin)
| # | 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 | [ ] |
### 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
### General Ledger
| # | 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) | [ ] |
### Profit and Loss
| # | 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) | [ ] |
### 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) | [ ] |
---
## Payments
| # | 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] |
---
## Reports
| # | 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] |
---
## Vendors
### Admin Vendor Management
| # | 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] |
### New Vendor Dialog
| # | 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
| # | 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 | [ ] |
### 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
### 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
### Still Legacy SPA (prioritized by complexity)
1. **Transactions** - High complexity (filters, edit form, bulk operations, manual import)
2. **Home/Dashboard** - Medium complexity (charts, cash flow calculations)
3. **Ledger** - Medium complexity (filters, CSV export)
4. **External Ledger** - Low-medium complexity (subset of ledger + delete)
5. **External Import** - Medium complexity (TSV parsing, validation, batch import)
6. **Profit and Loss** - High complexity (multi-company, periods, PDF export)
7. **Cash Flows** - High complexity (shares P&L infrastructure)
8. **Profit and Loss Detail** - Medium complexity (category filtering, running balances)
9. **Balance Sheet** - Medium complexity (comparison mode, drill-down)
10. **Login** - Low complexity (static page)
11. **Needs Activation** - Low complexity (static page)
12. **New Vendor** - Low complexity (dialog on home page)
### Migration Risks
- **Chart libraries**: Home page uses Recharts (React). Replacement needed for SSR.
- **PDF generation**: P&L, Cash Flows, Balance Sheet, P&L Detail all support PDF export via server-side generation.
- **Bulk operations**: Transactions page has complex bulk coding/deletion with validation.
- **External import**: TSV parsing and validation logic lives entirely in SPA; server-side equivalent needed.

View File

@@ -1,192 +0,0 @@
# Outgoing Invoice Behaviors
## Overview
The Outgoing Invoice subsystem allows users to create and generate PDF invoices to send to customers. It provides a form-based workflow for specifying the client (sender), recipient details, invoice metadata (date, number, tax rate), and line items. Upon submission, the system calculates subtotals, applies tax, and invokes an AWS Lambda function (`genpdf`) to generate a downloadable PDF.
**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
---
## Testing Patterns
### Pattern: Form Submission with HTMX
Outgoing invoice forms use HTMX for asynchronous submission and partial page updates:
1. Form fields are rendered server-side with validation state
2. HTMX handles POST submission and swaps the response into the page
3. Success responses trigger modal display with PDF download link
4. Error responses re-render the form with validation errors
**Test implications:** Unit test validation logic and calculation functions. Integration test the full POST flow. UI test only the happy path.
### Pattern: Dynamic Line Items
Line items are added and removed dynamically without page reload:
1. "Add line" button fetches a new row via HTMX
2. Each row has description, quantity, unit price inputs, and a delete button
3. Delete uses Alpine.js to fade out and remove the row
4. Empty line items are filtered out on submission
**Test implications:** Unit test the filtering and calculation logic. Integration test HTMX endpoints. UI test the add/remove interactions.
---
## Create Invoice
### Form Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 1.1 | It should render a new invoice form with breadcrumbs: Invoices > Outgoing > New | UI | [ ] |
| 1.2 | It should display a client typeahead field labeled "From (client)" | UI | [ ] |
| 1.3 | It should display an invoice number input field | UI | [ ] |
| 1.4 | It should display a date picker pre-filled with today's date in `normal-date` format | UI | [ ] |
| 1.5 | It should display recipient name "To" field | UI | [ ] |
| 1.6 | It should display recipient address fields: street1, street2, city, state, zip | UI | [ ] |
| 1.7 | It should display a line items grid with one default empty row | UI | [ ] |
| 1.8 | It should display a tax percentage input with default value 10.0 | UI | [ ] |
| 1.9 | It should display a "Generate" button to submit the form | UI | [ ] |
| 1.10 | It should display an "Add line" button to add more line items | UI | [ ] |
### Form Validation Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 2.1 | It should require client selection | Integration | [ ] |
| 2.2 | It should require invoice date | Integration | [ ] |
| 2.3 | It should require recipient name in "To" field | Integration | [ ] |
| 2.4 | It should require invoice number | Integration | [ ] |
| 2.5 | It should require at least one line item with description, quantity, and unit price | Integration | [ ] |
| 2.6 | It should make recipient address street2 optional | Unit | [ ] |
| 2.7 | It should strip whitespace from street2 and treat empty as nil | Unit | [ ] |
| 2.8 | It should coerce line items from nested form parameters into a vector | Unit | [ ] |
| 2.9 | It should display validation errors next to the offending fields | UI | [ ] |
| 2.10 | It should redisplay the form with entered data preserved when validation fails | Integration | [ ] |
### Submission Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 3.1 | It should filter out line items with empty descriptions before calculation | Unit | [ ] |
| 3.2 | It should calculate each line item total as `unit-price * quantity` | Unit | [ ] |
| 3.3 | It should calculate subtotal as the sum of all line item totals | Unit | [ ] |
| 3.4 | It should calculate tax as `subtotal * (tax-rate / 100)` | Unit | [ ] |
| 3.5 | It should calculate total as `subtotal + tax` | Unit | [ ] |
| 3.6 | It should format monetary values as `$X,XXX.XX` strings before sending to Lambda | Unit | [ ] |
| 3.7 | It should format the invoice date as `normal-date` string before sending to Lambda | Unit | [ ] |
| 3.8 | It should invoke the `genpdf` Lambda function with a JSON payload | Integration | [ ] |
| 3.9 | It should extract the S3 URL from the Lambda response | Integration | [ ] |
| 3.10 | It should display a modal with "Download your invoice" and a link to the S3 URL | UI | [ ] |
| 3.11 | Given the Lambda invocation fails, then it should display an error without showing a modal | Integration | [ ] |
| 3.12 | Given all line items are empty, then subtotal should be `0.0`, tax should be `0.0`, and total should be `0.0` | Unit | [ ] |
---
## Line Items
### Add Line Item Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 4.1 | It should fetch a new empty line item row via HTMX when "Add line" is clicked | Integration | [ ] |
| 4.2 | It should append the new row to the line items grid | UI | [ ] |
| 4.3 | It should render each row with hidden db/id, description input, quantity money-input, unit-price money-input, and delete button | UI | [ ] |
| 4.4 | It should allow adding multiple line items | UI | [ ] |
### Remove Line Item Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 5.1 | It should fade out a line item row over 500ms when the delete button is clicked | UI | [ ] |
| 5.2 | It should remove the line item row from the DOM after the fade animation | UI | [ ] |
| 5.3 | It should preserve data in remaining line items after deletion | UI | [ ] |
| 5.4 | It should allow deleting all line items, leaving the grid empty | UI | [ ] |
### Calculation Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 6.1 | It should handle negative quantities in line item calculations | Unit | [ ] |
| 6.2 | It should show `$0.00` for line items with zero unit price | Unit | [ ] |
| 6.3 | It should format large monetary values with comma separators (e.g., `$1,234.56`) | Unit | [ ] |
| 6.4 | It should format nil monetary values as `$0.00` | Unit | [ ] |
---
## PDF Generation
### Lambda Integration Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 7.1 | It should invoke `genpdf` Lambda with a JSON payload containing invoice data | Integration | [ ] |
| 7.2 | It should include formatted monetary strings in the Lambda payload | Unit | [ ] |
| 7.3 | It should include the invoice date as a `normal-date` string in the Lambda payload | Unit | [ ] |
| 7.4 | It should extract the S3 URL from a successful Lambda response | Integration | [ ] |
| 7.5 | It should present the S3 URL as a clickable download link in the modal | UI | [ ] |
### Error Handling Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.1 | Given the Lambda returns invalid JSON, then it should propagate an error | Integration | [ ] |
| 8.2 | Given the S3 URL is inaccessible, then the link should still be presented but may fail on click | UI | [ ] |
| 8.3 | Given a very large invoice payload, then Lambda payload size limits may apply | Integration | [ ] |
---
## Cross-Cutting Behaviors
### Authentication Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 9.1 | It should redirect unauthenticated users to `/login` | Integration | [ ] |
| 9.2 | It should redirect unauthenticated users back to `/outgoing-invoice/new` after login | Integration | [ ] |
| 9.3 | It should apply `wrap-secure` middleware to all routes | Integration | [ ] |
| 9.4 | It should apply `wrap-trim-client-ids` middleware to requests | Integration | [ ] |
### Client Selection Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 10.1 | It should populate the client typeahead from the `:company-search` endpoint | Integration | [ ] |
| 10.2 | It should only show clients the authenticated user has access to | Integration | [ ] |
### Tax Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 11.1 | It should treat a whole number tax (e.g., 10) as 10% | Unit | [ ] |
| 11.2 | It should treat a decimal tax (e.g., 8.25) as 8.25% | Unit | [ ] |
| 11.3 | It should allow tax rates over 100% | Unit | [ ] |
| 11.4 | It should calculate total equal to subtotal when tax is zero | Unit | [ ] |
---
## Test Data Requirements
| Entity | Requirements |
|--------|-------------|
| **Users** | Authenticated user with access to at least one client |
| **Clients** | Multiple clients with complete profiles including address (name, street, city, state, zip) |
| **Form Data** | Valid invoice number strings; valid dates in `normal-date` format; recipient names and addresses |
| **Line Items** | Descriptions, quantities (numeric), unit prices (monetary) |
| **Tax Rates** | Percentage values (e.g., 10.0 for 10%) |
| **AWS Lambda** | Mock `genpdf` Lambda returning valid S3 URL; mock `genpdf` Lambda returning error |
## Existing Tests to Preserve
- `test/clj/auto_ap/ssr/outgoing_invoice_test.clj` — Outgoing invoice form rendering, submission, and PDF generation
## Dependencies
- Datomic (client lookup for typeahead, address data)
- AWS Lambda (`genpdf` function generates PDF from invoice data)
- AWS S3 (generated PDFs stored at `data.prod.app.integreatconsult.com/<path>`)
- HTMX (form submission, line item row fetching)
- Alpine.js (line item row removal animation)
- Form cursor (`auto-ap.ssr.form-cursor`) — field state management, error binding

View File

@@ -1,260 +0,0 @@
# Payment Behaviors
## Overview
Payments represent disbursements to vendors for invoices. The payment system supports multiple payment types (check, cash, debit, balance credit) and tracks payments through statuses (pending, cleared, voided). Payments are created through the invoice payment flow and managed through a searchable grid interface.
**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
---
## Testing Patterns
### 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
**Test implications:** Validate the query generation, not the rendering. UI tests only need to verify one filter and one sort work end-to-end.
### Pattern: Permission Gates
Every mutating operation checks:
1. `assert-can-see-client` — user has access to the client
2. `assert-not-locked` — payment date >= client locked-until
3. `can?` — user has the specific permission for the activity
**Test implications:** Integration test each gate independently. UI tests only verify the happy path with a permitted user.
### Pattern: Check Generation Behaviors
Check printing involves:
1. Validation of invoices and bank account
2. Sequential check number assignment
3. PDF generation with MICR encoding
4. S3 upload and storage
5. Transaction creation (for cash payments)
**Test implications:** Integration test the full flow once. Unit test validation logic and PDF content generation.
---
## Payment List Page
### Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 1.1 | It should display a table with columns: Client, Vendor, Bank Account, Check #, Status, Date, Amount, Links | UI | [ ] |
| 1.2 | It should show the Client column only when viewing payments for multiple clients | Integration | [ ] |
| 1.3 | It should hide the Bank Account and Date columns on smaller viewports | UI | [ ] |
| 1.4 | It should show "Cleared" status as a primary-colored pill | UI | [ ] |
| 1.5 | It should show "Pending" status as a secondary-colored pill | UI | [ ] |
| 1.6 | It should show "Voided" status as a red-colored pill | UI | [ ] |
| 1.7 | It should render check numbers as links to S3 PDFs when an s3-url exists | UI | [ ] |
| 1.8 | It should show plain check number text for payments without an s3-url | UI | [ ] |
| 1.9 | It should display a links dropdown showing associated invoices and transactions | UI | [ ] |
| 1.10 | It should display checkboxes for bulk selection on each row | UI | [ ] |
| 1.11 | It should show no check number for non-check payment types | UI | [ ] |
### Filtering Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 2.1 | It should filter payments by vendor typeahead selection | Integration | [ ] |
| 2.2 | It should filter payments by date range | Integration | [ ] |
| 2.3 | It should filter payments by check number (exact match or partial text) | Integration | [ ] |
| 2.4 | It should filter payments by invoice number (exact match) | Integration | [ ] |
| 2.5 | It should filter payments by amount range (min/max) | Integration | [ ] |
| 2.6 | It should filter payments by payment type via radio cards (All, Cash, Check, Debit) | Integration | [ ] |
| 2.7 | It should support exact-match navigation to a specific payment by ID, bypassing other filters | Integration | [ ] |
| 2.8 | It should filter payments by status via route (`/payments/pending`, `/payments/cleared`, `/payments/voided`) | Integration | [ ] |
| 2.9 | It should apply all filters via HTMX with debounced triggers | Integration | [ ] |
| 2.10 | It should combine all filters with AND logic | Integration | [ ] |
| 2.11 | It should use efficient time-bounded queries for date range filtering | Integration | [ ] |
| 2.12 | It should parse check number search as Long when possible, falling back to exact string match | Unit + Integration | [ ] |
| 2.13 | Given multiple filters are applied, when the user changes one filter, then the table should refresh with the combined filter set | Integration | [ ] |
| 2.14 | It should bypass all other filters when exact-match ID is provided | Integration | [ ] |
### Sorting Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 3.1 | It should sort by client name ascending/descending | Integration | [ ] |
| 3.2 | It should sort by vendor name ascending/descending | Integration | [ ] |
| 3.3 | It should sort by bank account ascending/descending | Integration | [ ] |
| 3.4 | It should sort by check number ascending/descending | Integration | [ ] |
| 3.5 | It should sort by date ascending/descending | Integration | [ ] |
| 3.6 | It should sort by amount ascending/descending | Integration | [ ] |
| 3.7 | It should sort by status ascending/descending | Integration | [ ] |
| 3.8 | Given the user clicks a column header twice, then the sort direction should toggle | Integration | [ ] |
### Pagination Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 4.1 | It should display 25 payments per page by default | Integration | [ ] |
| 4.2 | It should allow changing the per-page count | Integration | [ ] |
| 4.3 | It should calculate the total visible float and total float across all matching payments, not just the current page | Unit | [ ] |
### Selection Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 5.1 | It should allow selecting individual payments via checkboxes | UI | [ ] |
| 5.2 | It should allow selecting all visible payments via a header checkbox | UI | [ ] |
| 5.3 | It should allow selecting all filtered payments (up to 250) for bulk operations | Integration | [ ] |
| 5.4 | Given payments are selected, when the user applies a filter, then the selection should be cleared | Integration | [ ] |
### Row Action Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 6.1 | It should show a trash icon on each row unless the payment status is already voided | UI | [ ] |
| 6.2 | It should prompt for confirmation when clicking the trash icon ("Are you sure you want to void this payment?") | UI | [ ] |
| 6.3 | Given confirmation, when voiding a payment, then the row should be removed from the table with animation | UI | [ ] |
| 6.4 | It should block voiding cleared check payments | Integration | [ ] |
### Float Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 7.1 | It should display a "Visible in float" pill showing the sum of pending payment amounts in the current filter view | Unit | [ ] |
| 7.2 | It should display a "Total in float" pill showing the sum of all pending payments for the selected client(s) | Unit | [ ] |
| 7.3 | It should exclude voided payments from float calculations | Unit | [ ] |
| 7.4 | It should include only pending status payments in float calculations | Unit | [ ] |
---
## Bulk Void
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.1 | It should show a confirmation modal with warning icon and count of payments to be voided | UI | [ ] |
| 8.2 | It should support "Selected only" mode to void only checkboxed payments | UI | [ ] |
| 8.3 | It should support "All selected" mode to void all payments matching current filters (up to 250) | Integration | [ ] |
| 8.4 | It should require admin permission for bulk void operations | Integration | [ ] |
| 8.5 | Given confirmation, when voiding, then the modal should close and a notification should show "Successfully voided X of Y payments" | Integration | [ ] |
| 8.6 | It should skip payments that already have transactions and skip already-voided payments | Integration | [ ] |
---
## Check Printing
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 9.1 | It should generate physical check PDFs with MICR encoding at the bottom | Integration | [ ] |
| 9.2 | It should include payee, amount in numbers and words, date, memo, bank info, and client signature image | Integration | [ ] |
| 9.3 | It should generate voucher copies with full invoice details below the check | Integration | [ ] |
| 9.4 | It should store check PDFs in S3 under `checks/{uuid}.pdf` | Integration | [ ] |
| 9.5 | It should assign check numbers sequentially from the bank account's check number | Integration | [ ] |
| 9.6 | It should increment the bank account's check number by the number of vendors paid | Integration | [ ] |
| 9.7 | It should validate that the bank account has a starting check number | Integration | [ ] |
| 9.8 | It should merge multiple checks into a single PDF at `merged-checks/{uuid}.pdf` | Integration | [ ] |
| 9.9 | It should group invoices by vendor, creating one check per vendor per batch | Integration | [ ] |
| 9.10 | It should validate that all invoices belong to the same client and the selected bank account belongs to the same client | Integration | [ ] |
| 9.11 | It should reject check creation if the total amount is <= $0.00 | Integration | [ ] |
---
## ACH Payments
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 10.1 | It should create pending payments with `payment-type/debit` | Integration | [ ] |
| 10.2 | It should not generate check PDFs for ACH payments | Integration | [ ] |
| 10.3 | It should not create transactions for ACH payments | Integration | [ ] |
---
## Balance Credit Payments
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 11.1 | It should allow paying invoices from existing vendor credit with `payment-type/balance-credit` | Integration | [ ] |
| 11.2 | It should block balance credit payments when multiple vendors are selected | Integration | [ ] |
| 11.3 | It should offset positive-balance invoices against negative-balance invoices | Integration | [ ] |
| 11.4 | It should create a single cleared payment for the net amount, consuming credit invoices first-in | Integration | [ ] |
---
## Cash Payments
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 12.1 | It should create payments with `payment-type/cash` automatically marked as cleared | Integration | [ ] |
| 12.2 | It should create an associated transaction with POSTED status | Integration | [ ] |
| 12.3 | It should use the account with numeric code 21000 for cash payment transactions | Integration | [ ] |
| 12.4 | It should set the payment date to the latest invoice date | Integration | [ ] |
---
## Cross-Cutting Behaviors
### Voiding Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 13.1 | It should allow voiding pending payments | Integration | [ ] |
| 13.2 | It should allow voiding cash, debit, and balance-credit payments even when cleared | Integration | [ ] |
| 13.3 | It should block voiding cleared check payments | Integration | [ ] |
| 13.4 | It should set the payment amount to 0.0 when voided | Integration | [ ] |
| 13.5 | It should set the payment status to voided | Integration | [ ] |
| 13.6 | It should remove all invoice-payment links when voiding | Integration | [ ] |
| 13.7 | It should restore invoice outstanding balances by adding back the invoice-payment amount | Integration | [ ] |
| 13.8 | It should revert invoice status to unpaid when restored balance becomes non-zero | Integration | [ ] |
| 13.9 | It should unlink associated transactions when voiding | Integration | [ ] |
### Permission Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 14.1 | It should require client visibility for viewing payments | Integration | [ ] |
| 14.2 | It should require client visibility for voiding individual payments | Integration | [ ] |
| 14.3 | It should require admin permission for bulk voiding payments | Integration | [ ] |
| 14.4 | It should allow viewing S3 check PDFs to all users who can see the payment | Integration | [ ] |
### Lock Date Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 15.1 | It should block voiding payments dated before the client's locked-until date | Integration | [ ] |
| 15.2 | It should check lock dates on individual void operations | Integration | [ ] |
| 15.3 | It should check lock dates on bulk void operations | Integration | [ ] |
| 15.4 | It should exclude locked payments from bulk void results | Integration | [ ] |
| 15.5 | It should show a warning when some selected payments are locked | UI | [ ] |
---
## Test Data Requirements
| Entity | Requirements |
|--------|-------------|
| **Clients** | Multiple clients with different locations; some with locked-until dates |
| **Vendors** | With/without default accounts; with/without terms and autopay settings |
| **Bank Accounts** | Check, cash, and credit types; with/without starting check numbers |
| **Invoices** | Various statuses, dates, amounts; positive and negative balances |
| **Payments** | Check, cash, debit, and balance-credit types; pending, cleared, and voided statuses |
| **Transactions** | Linked to cash payments with POSTED status |
| **Files** | Check PDFs stored in S3 |
## Existing Tests to Preserve
- `test/clj/auto_ap/integration/graphql/checks.clj` — Check/payment GraphQL operations
## Dependencies
- Datomic for payment/invoice/transaction persistence
- S3 for check PDF storage and retrieval
- Solr for index updates after payment mutations
- HTMX + Alpine.js for interactive grid behavior
- `clj-pdf` for check PDF generation
- `clj-time` for date parsing and coercion
- `auto-ap.datomic/scan-payments` for efficient date-range queries
- `auto-ap.permissions/can?` for permission checks
- `auto-ap.datomic/audit-transact` for all mutations

View File

@@ -1,405 +0,0 @@
# POS Behaviors
## Overview
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.
**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
---
## Testing Patterns
### 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
**Test implications:** Validate the query generation, not the rendering. UI tests only need to verify one filter and one sort work end-to-end.
### 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
**Test implications:** Unit test validation logic and state transitions. Integration test the full wizard flow once. UI test only the happy path.
### 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
**Test implications:** Integration test each gate independently. UI tests only verify the happy path with a permitted user.
---
## Sales Orders
### Display Behaviors
| # | 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 | [ ] |
### Filtering Behaviors
| # | 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 | [ ] |
### Sorting Behaviors
| # | 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 | [ ] |
### Pagination Behaviors
| # | 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 | [ ] |
---
## Expected Deposits
### Display Behaviors
| # | 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 | [ ] |
### Filtering Behaviors
| # | 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 | [ ] |
### Sorting Behaviors
| # | 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 | [ ] |
### Pagination Behaviors
| # | 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 | [ ] |
---
## Tenders
### Display Behaviors
| # | 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 | [ ] |
### Filtering Behaviors
| # | 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 | [ ] |
### Sorting Behaviors
| # | 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 | [ ] |
### Pagination Behaviors
| # | 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 | [ ] |
---
## Refunds
### Display Behaviors
| # | 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 | [ ] |
### Filtering Behaviors
| # | 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 | [ ] |
### 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
### HTMX Live Filtering Behaviors
| # | 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 Behaviors
| # | 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 | [ ] |
### Total Range Behaviors
| # | 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 | [ ] |
### Exact Match ID Behaviors
| # | 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 | [ ] |
### Sorting Behaviors
| # | 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 | [ ] |
### Pagination Behaviors
| # | 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
| 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
- 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

View File

@@ -1,167 +0,0 @@
# Search & Indicators Behaviors
## Overview
The Search subsystem provides a global full-text search dialog accessible from the main navigation bar. It queries a Solr index of invoices, payments, transactions, and journal entries, returning results filtered by the user's client permissions. The Indicators subsystem provides small UI utilities like relative date badges (e.g., "5 days ago") used across the application.
**Testing Philosophy**
- Prefer unit tests for pure business logic (query parsing, date formatting, number formatting)
- 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
---
## Testing Patterns
### Pattern: Search Query Parsing
Search queries are transformed before being sent to Solr:
1. Bare words become text search clauses joined with `AND`
2. Quoted phrases are preserved as exact tokens
3. Type keywords (`invoice`, `payment`, `transaction`, `journal-entry`) filter by document type
4. Dates in normal or ISO format are converted to Solr date format
5. Decimal numbers are formatted to 2 decimal places
6. Unparseable tokens pass through unchanged
**Test implications:** Unit test each transformation rule. Integration test the full query pipeline once.
### Pattern: Permission Filtering
All search results are filtered by the user's client access permissions.
**Test implications:** Integration test with users having different client access levels.
---
## Search
### Modal Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 1.1 | It should open a search modal when the user clicks the search icon in the navbar | UI | [ ] |
| 1.2 | It should display an autofocused search input with placeholder text in the modal | UI | [ ] |
| 1.3 | It should trigger a search after 300ms debounce when the user types in the search input | Integration | [ ] |
| 1.4 | It should display the search modal without results when no query is provided | Integration | [ ] |
### Query Parsing Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 2.1 | It should convert bare words in the search query to text search clauses joined with `AND` | Unit | [ ] |
| 2.2 | It should preserve quoted phrases in the search query as exact text matches | Unit | [ ] |
| 2.3 | It should filter by document type when the user includes a type keyword (`invoice`, `payment`, `transaction`, `journal-entry`) | Unit | [ ] |
| 2.4 | It should convert dates in normal format (e.g., `5/5/2034`) to Solr-compatible date format | Unit | [ ] |
| 2.5 | It should convert dates in ISO format to Solr-compatible date format | Unit | [ ] |
| 2.6 | It should pass through unparseable dates unchanged | Unit | [ ] |
| 2.7 | It should format decimal numbers to exactly 2 decimal places with `HALF_UP` rounding | Unit | [ ] |
| 2.8 | It should pass through integer numbers unchanged | Unit | [ ] |
| 2.9 | It should handle mixed type keywords and text tokens in the search query | Unit | [ ] |
| 2.10 | It should handle multiple type keywords in the search query | Unit | [ ] |
### Results Display Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 3.1 | It should display search results as cards below the search input | UI | [ ] |
| 3.2 | It should show a type icon on each result card | UI | [ ] |
| 3.3 | It should show the document type name on each result card | UI | [ ] |
| 3.4 | It should show the client code as a pill on each result card | UI | [ ] |
| 3.5 | It should show the amount as a pill on each result card | UI | [ ] |
| 3.6 | It should show the vendor name as a pill when present on each result card | UI | [ ] |
| 3.7 | It should show the date on each result card | UI | [ ] |
| 3.8 | It should show the description or number on each result card | UI | [ ] |
| 3.9 | It should link each result card to the appropriate detail page with an `exact-match-id` parameter | Integration | [ ] |
| 3.10 | It should open the detail page in a new tab when the user clicks the external link icon | UI | [ ] |
| 3.11 | It should filter results to only show documents from clients the user can access | Integration | [ ] |
### Empty Results Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 4.1 | It should display "No results found." when the query matches no documents | UI | [ ] |
| 4.2 | It should return an empty results list when the user has no accessible clients | Integration | [ ] |
| 4.3 | It should return an empty results list when Solr is unavailable | Integration | [ ] |
### Type Filter Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 5.1 | It should filter results to only show payment documents when the user types "payment" | Integration | [ ] |
| 5.2 | It should filter results to only show invoice documents when the user types "invoice" | Integration | [ ] |
| 5.3 | It should show the payment icon and link to the payments detail page for payment results | UI | [ ] |
### Date Search Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 6.1 | It should return documents matching a date in normal format (e.g., `5/5/2034`) | Integration | [ ] |
| 6.2 | It should return documents matching a date in ISO format | Integration | [ ] |
| 6.3 | It should accept future dates in the search query | Integration | [ ] |
---
## Indicators
### Days-Ago Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 7.1 | It should display a relative date badge showing "N days ago" for past dates | UI | [ ] |
| 7.2 | It should display a relative date badge showing "N days from now" for future dates | UI | [ ] |
| 7.3 | It should show the days-ago badge in primary color for dates less than 30 days old | Unit + UI | [ ] |
| 7.4 | It should show the days-ago badge in secondary color for dates 30-59 days old | Unit + UI | [ ] |
| 7.5 | It should show the days-ago badge in yellow color for dates 60-89 days old | Unit + UI | [ ] |
| 7.6 | It should show the days-ago badge in red color for dates 90 or more days old | Unit + UI | [ ] |
| 7.7 | It should show "0 days ago" with primary color for same-day dates | Unit + UI | [ ] |
| 7.8 | It should render an empty indicator when the date is nil | Unit + UI | [ ] |
---
## Cross-Cutting Behaviors
### Permission Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.1 | It should require authentication to access the search endpoint | Integration | [ ] |
| 8.2 | It should require authentication to access the days-ago endpoint | Integration | [ ] |
| 8.3 | It should redirect unauthenticated users to the login page when accessing search or days-ago | Integration | [ ] |
### Error Handling Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 9.1 | It should reject invalid date formats for the days-ago endpoint via schema validation | Integration | [ ] |
| 9.2 | It should handle special characters in the search query without errors | Integration | [ ] |
| 9.3 | It should handle very long search queries without UI breakage | UI | [ ] |
| 9.4 | It should handle numeric tokens with commas or currency symbols as literal text search | Unit | [ ] |
---
## Test Data Requirements
| Entity | Requirements |
|--------|-------------|
| **Solr Index** | Indexed documents of all four types: `invoice`, `payment`, `transaction`, `journal-entry`; spanning multiple clients; with varying dates, amounts, descriptions, numbers, and vendor names |
| **Users** | Authenticated user with access to subset of clients; authenticated user with access to all clients; admin user |
| **Clients** | Multiple clients with `:client/code` and `:client/name` |
| **Invoices** | With `:invoice/invoice-number`, `:invoice/total`, `:invoice/date`, `:invoice/client`, `:invoice/vendor` |
| **Payments** | With `:payment/check-number`, `:payment/amount`, `:payment/date`, `:payment/client`, `:payment/vendor` |
| **Transactions** | With `:transaction/description-original`, `:transaction/amount`, `:transaction/date`, `:transaction/client`, `:transaction/vendor` |
| **Journal Entries** | With `:journal-entry/amount`, `:journal-entry/date`, `:journal-entry/client`, `:journal-entry/vendor`, `:journal-entry/line-items` |
## Existing Tests to Preserve
No existing test files specified for Search & Indicators.
## Dependencies
- **Solr**: Full-text search index (`auto-ap.solr`). Uses `MockSolrClient` in test environments without Solr configured
- **Datomic**: Client visibility checks pull user/client associations
- **HTMX**: Modal loading, search debounce (`keyup changed delay:300ms`), indicator spinner
- **Alpine.js**: Modal card structure
- **Middleware**: `wrap-secure` requires authentication; `wrap-client-redirect-unauthenticated` redirects unauthenticated to `/login`; `wrap-schema-enforce` validates `date` query parameter for `/days-ago`
- **Invoices**: Search results link to invoice detail pages
- **Payments**: Search results link to payment detail pages
- **Transactions**: Search results link to transaction detail pages
- **Ledger**: Search results link to ledger for journal entries

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -293,9 +293,7 @@
(condp = (:name (:source order))
"GRUBHUB" :ccp-processor/grubhub
"UBEREATS" :ccp-processor/uber-eats
"Uber Eats" :ccp-processor/uber-eats
"DOORDASH" :ccp-processor/doordash
"DoorDash" :ccp-processor/doordash
"Koala" :ccp-processor/koala
"koala-production" :ccp-processor/koala
:ccp-processor/na))
@@ -351,10 +349,7 @@
(s/reduce conj []))]
[(remove-nils
#:sales-order
{:date (if (= "Invoices" (:name (:source order)))
(when (:closed_at order)
(coerce/to-date (time/to-time-zone (coerce/to-date-time (:closed_at order)) (time/time-zone-for-id "America/Los_Angeles"))))
(coerce/to-date (time/to-time-zone (coerce/to-date-time (:created_at order)) (time/time-zone-for-id "America/Los_Angeles"))))
{:date (coerce/to-date (time/to-time-zone (coerce/to-date-time (:created_at order)) (time/time-zone-for-id "America/Los_Angeles")))
:client (:db/id client)
:location (:square-location/client-location location)
:external-id (str "square/order/" (:client/code client) "-" (:square-location/client-location location) "-" (:id order))
@@ -384,9 +379,6 @@
;; sometimes orders stay open in square. At least one payment
;; is needed to import, in order to avoid importing orders in-progress.
(and
(if (= "Invoices" (:name (:source order)))
(boolean (:closed_at order))
true)
(or (> (count (:tenders order)) 0)
(seq (:returns order)))
(or (= #{} (set (map #(:status (:card_details %)) (:tenders order))))
@@ -870,11 +862,7 @@
#_(comment
(require 'auto-ap.time-reader)
@(let [[c [l]] (get-square-client-and-location "DBFS") ]
(log/peek :x [ c l])
(search c l #clj-time/date-time "2026-03-28" #clj-time/date-time "2026-03-29")
)
@(let [[c [l]] (get-square-client-and-location "NGAK") ]
(log/peek :x [ c l])
@@ -984,14 +972,13 @@
:headers (client-base-headers client)
:as :json})
:body)))
(->>
@(let [[c [l]] (get-square-client-and-location "NGGG")]
(->>
@(let [[c [l]] (get-square-client-and-location "NGGG")]
(search c l (time/now) (time/plus (time/now) (time/days -1))))
(filter (fn [r]
(str/starts-with? (:created_at r) "2024-03-14"))))
(search c l (time/plus (time/now))))
(filter (fn [r]
(str/starts-with? (:created_at r) "2024-03-14"))))
(def refs
(->>
@@ -1008,35 +995,35 @@
(map (fn [r] @(get-payment c (:payment_id r))) refs))
(get-square-client-and-location "NGGB")
(get-square-client-and-location "NGGB")
(def my-results
(let [[c [l]] (get-square-client-and-location "NGFA")]))
(clojure.data.csv/write-csv *out*
(for [c (get-square-clients)
l (:client/square-locations c)
:when (:square-location/client-location l)
bad-row (try (->> @(search c l (coerce/to-date-time #inst "2024-04-01T00:00:00-07:00") (coerce/to-date-time #inst "2024-04-15T23:59:00-07:00"))
(filter #(not (should-import-order? %)))
(map #(first (deref (order->sales-order c l %))))
(filter (fn already-exists [o]
(when (:sales-order/external-id o)
(seq (dc/q '[:find ?i
:in $ ?ei
:where [?i :sales-order/external-id ?ei]]
(dc/db conn)
(:sales-order/external-id o)))))))
(catch Exception e
[]))]
[(:client/code c) (atime/unparse-local (clj-time.coerce/to-date-time (:sales-order/date bad-row)) atime/normal-date) (:sales-order/total bad-row) (:sales-order/tax bad-row) (:sales-order/tip bad-row) (:db/id bad-row)])
:separator \tab)
(for [c (get-square-clients)
l (:client/square-locations c)
:when (:square-location/client-location l)
bad-row (try (->> @(search c l (coerce/to-date-time #inst "2024-04-01T00:00:00-07:00") (coerce/to-date-time #inst "2024-04-15T23:59:00-07:00"))
(filter #(not (should-import-order? %)))
(map #(first (deref (order->sales-order c l %))))
(filter (fn already-exists [o]
(when (:sales-order/external-id o)
(seq (dc/q '[:find ?i
:in $ ?ei
:where [?i :sales-order/external-id ?ei]]
(dc/db conn)
(:sales-order/external-id o)))))))
(catch Exception e
[]))]
[(:client/code c) (atime/unparse-local (clj-time.coerce/to-date-time (:sales-order/date bad-row)) atime/normal-date) (:sales-order/total bad-row) (:sales-order/tax bad-row) (:sales-order/tip bad-row) (:db/id bad-row)])
:separator \tab)
;; =>
(require 'auto-ap.time-reader)
@@ -1048,7 +1035,7 @@
(def z @(search c l #clj-time/date-time "2025-02-23T00:00:00-08:00"
#clj-time/date-time "2025-02-28T00:00:00-08:00"))
#clj-time/date-time "2025-02-28T00:00:00-08:00"))
(take 10 (map #(first (deref (order->sales-order c l %))) z)))
@@ -1064,43 +1051,17 @@
(count)
)
(doseq [[code] (seq (dc/q '[:find ?code
:in $
:where [?o :sales-order/date ?d]
[(>= ?d #inst "2026-01-01")]
[?o :sales-order/source "Invoices"]
[?o :sales-order/client ?c]
[?c :client/code ?code]]
(dc/db conn)))
:let [[c [l]] (get-square-client-and-location code)
]
order @(search c l #clj-time/date-time "2026-01-01T00:00:00-08:00" (time/now))
:when (= "Invoices" (:name (:source order) ))
:let [[sales-order] @(order->sales-order c l order)]]
(when (should-import-order? order)
(println "DATE IS" (:sales-order/date sales-order))
(when (some-> (:sales-order/date sales-order) coerce/to-date-time (time/after? #clj-time/date-time "2026-2-16T00:00:00-08:00"))
(println "WOULD UPDATE" sales-order)
@(dc/transact auto-ap.datomic/conn [sales-order])
)
#_@(dc/transact )
(println "DONE"))
)
(doseq [c (get-square-clients)]
(println "Upserting" (:client/name c))
@(upsert c))
#_(filter (comp #{"OTHER"} :type) (mapcat :tenders z))
@(let [[c [l]] (get-square-client-and-location "NGRY")]
#_(search c l (clj-time.coerce/from-date #inst "2025-02-28") (clj-time.coerce/from-date #inst "2025-03-01"))
(let [[c [l]] (get-square-client-and-location "LFHH")]
(search c l (clj-time.coerce/from-date #inst "2025-02-28") (clj-time.coerce/from-date #inst "2025-03-01"))
(order->sales-order c l (:order (get-order c l "KdvwntmfMNTKBu8NOocbxatOs18YY" )))
)
(:order (get-order c l "CLjQqkzVfGa82o5hEFUrGtUGO6QZY" ))
)
)

View File

@@ -1,13 +1,11 @@
(ns auto-ap.ssr.components.date-range
(:require [auto-ap.ssr.components :as com]
[auto-ap.ssr.components.buttons :as but]
[auto-ap.ssr.svg :as svg]
[auto-ap.time :as atime]
[clj-time.coerce :as c]
[clj-time.core :as t]
[clj-time.periodic :as per]))
(defn date-range-field [{:keys [value id apply-button?]}]
(defn date-range-field [{:keys [value id]}]
[:div {:id id}
(com/field {:label "Date Range"}
[:div.space-y-4
@@ -23,17 +21,11 @@
(atime/unparse-local atime/normal-date))
:placeholder "Date"
:size :small
:class "shrink date-filter-input"})
:class "shrink"})
(com/date-input {:name "end-date"
:value (some-> (:end value)
(atime/unparse-local atime/normal-date))
:placeholder "Date"
:size :small
:class "shrink date-filter-input"})
(when apply-button?
(but/button- {:color :secondary
:size :small
:type "button"
"x-on:click" "$dispatch('datesApplied')"}
"Apply"))]])])
:class "shrink"})]])])

View File

@@ -37,7 +37,7 @@
[auto-ap.ssr.invoice.common :refer [default-read]]
[auto-ap.ssr.invoice.import :as invoice-import]
[auto-ap.ssr.invoice.new-invoice-wizard :as new-invoice-wizard :refer [location-select*]]
[auto-ap.ssr.components.date-range :as dr]
[auto-ap.ssr.pos.common :refer [date-range-field*]]
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils
:refer [apply-middleware-to-all-handlers assert-schema
@@ -77,7 +77,7 @@
[:div {:id "exact-match-id-tag"}]))
(defn filters [request]
[:form#invoice-filters {"hx-trigger" "datesApplied, change delay:500ms from:.filter-trigger, keyup changed from:.hot-filter delay:1000ms"
[:form#invoice-filters {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
"hx-get" (bidi/path-for ssr-routes/only-routes
::route/table)
"hx-target" "#entity-table"
@@ -92,8 +92,7 @@
:url (bidi/path-for ssr-routes/only-routes :vendor-search)
:value (:vendor (:query-params request))
:value-fn :db/id
:content-fn :vendor/name
:class "filter-trigger"}))
:content-fn :vendor/name}))
(com/field {:label "Account"}
(com/typeahead {:name "account"
:id "account"
@@ -101,12 +100,8 @@
:value (:account (:query-params request))
:value-fn :db/id
:content-fn #(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read (:db/id %))
(:db/id (:client request))))
:class "filter-trigger"}))
(dr/date-range-field {:value {:start (:start-date (:query-params request))
:end (:end-date (:query-params request))}
:id "date-range"
:apply-button? true})
(:db/id (:client request))))}))
(date-range-field* request)
(com/field {:label "Check #"}
(com/text-input {:name "check-number"
:id "check-number"
@@ -491,10 +486,7 @@
:fetch-page fetch-page
:oob-render
(fn [request]
[(assoc-in (dr/date-range-field {:value {:start (:start-date (:query-params request))
:end (:end-date (:query-params request))}
:id "date-range"
:apply-button? true}) [1 :hx-swap-oob] true)
[(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)
(assoc-in (exact-match-id* request) [1 :hx-swap-oob] true)])
:query-schema query-schema
:parse-query-params (fn [p]

View File

@@ -81,7 +81,7 @@
data (into []
(for [client-id client-ids
d date
[client-id account-id location debits credits balance count] (iol-ion.query/detailed-account-snapshot (dc/db conn) client-id (coerce/to-date d))
[client-id account-id location debits credits balance count] (iol-ion.query/detailed-account-snapshot (dc/db conn) client-id (coerce/to-date (time/plus d (time/days 1))))
:let [account ((or (lookup-account client-id) {}) account-id)]]
{:client-id client-id
:account-id account-id

View File

@@ -51,22 +51,3 @@
(is (= "720.33" (:total (nth results 1))))
(is (= "853.16" (:total (nth results 2))))
(is (= "1066.60" (:total (nth results 3)))))))
(deftest parse-bonanza-produce-invoice-03882095
(testing "Should parse Bonanza Produce invoice 03882095 with customer identifier including address"
(let [pdf-file (io/file "dev-resources/INVOICE - 03882095.pdf")
pdf-text (:out (clojure.java.shell/sh "pdftotext" "-layout" (str pdf-file) "-"))
results (sut/parse pdf-text)
result (first results)]
(is (some? results) "parse should return a result")
(is (some? result) "Template should match and return a result")
(when result
(is (= "Bonanza Produce" (:vendor-code result)))
(is (= "03882095" (:invoice-number result)))
(let [d (:date result)]
(is (= 2026 (time/year d)))
(is (= 1 (time/month d)))
(is (= 23 (time/day d))))
(is (= "NICK THE GREEK" (:customer-identifier result)))
(is (= "600 VISTA WAY" (str/trim (:account-number result))))
(is (= "946.24" (:total result)))))))