Compare commits
6 Commits
feat/compl
...
sales-clea
| Author | SHA1 | Date | |
|---|---|---|---|
| 8528be9faa | |||
| 9b0382e587 | |||
| ef64d5fc41 | |||
| 13ac73c7c5 | |||
| f51f4a0b8e | |||
| 9283f9238f |
@@ -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
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,3 +0,0 @@
|
||||
|
||||
# Use bd merge for beads JSONL files
|
||||
.beads/issues.jsonl merge=beads
|
||||
54
AGENTS.md
54
AGENTS.md
@@ -1,54 +0,0 @@
|
||||
# Agent Instructions
|
||||
|
||||
This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started.
|
||||
|
||||
## Issue Tracking
|
||||
|
||||
This project uses **bd (beads)** for issue tracking.
|
||||
Run `bd prime` for workflow context, or install hooks (`bd hooks install`) for auto-injection.
|
||||
|
||||
**Quick reference:**
|
||||
- `bd ready` - Find unblocked work
|
||||
- `bd create "Title" --type task --priority 2` - Create issue
|
||||
- `bd close <id>` - Complete work
|
||||
- `bd sync` - Sync with git (run at session end)
|
||||
|
||||
For full workflow details: `bd prime`
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
bd ready # Find available work
|
||||
bd show <id> # View issue details
|
||||
bd update <id> --status in_progress # Claim work
|
||||
bd close <id> # Complete work
|
||||
bd sync # Sync with git
|
||||
```
|
||||
|
||||
## Landing the Plane (Session Completion)
|
||||
|
||||
**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.
|
||||
|
||||
**MANDATORY WORKFLOW:**
|
||||
|
||||
1. **File issues for remaining work** - Create issues for anything that needs follow-up
|
||||
2. **Run quality gates** (if code changed) - Tests, linters, builds
|
||||
3. **Update issue status** - Close finished work, update in-progress items
|
||||
4. **PUSH TO REMOTE** - This is MANDATORY:
|
||||
```bash
|
||||
git pull --rebase
|
||||
bd sync
|
||||
git push
|
||||
git status # MUST show "up to date with origin"
|
||||
```
|
||||
5. **Clean up** - Clear stashes, prune remote branches
|
||||
6. **Verify** - All changes committed AND pushed
|
||||
7. **Hand off** - Provide context for next session
|
||||
|
||||
**CRITICAL RULES:**
|
||||
- Work is NOT complete until `git push` succeeds
|
||||
- NEVER stop before pushing - that leaves work stranded locally
|
||||
- NEVER say "ready to push when you are" - YOU must push
|
||||
- If push fails, resolve and retry until it succeeds
|
||||
|
||||
Use 'bd' for task tracking
|
||||
Binary file not shown.
@@ -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
|
||||
@@ -1,219 +0,0 @@
|
||||
---
|
||||
title: Complete Automatic Sales Summary Calculations and Ledger Posting
|
||||
type: feat
|
||||
status: completed
|
||||
date: 2026-04-24
|
||||
---
|
||||
|
||||
# Complete Automatic Sales Summary Calculations and Ledger Posting
|
||||
|
||||
## What's Incomplete
|
||||
- **Automatic Totals**: Aggregate attributes (e.g., `:sales-summary/total-card-payments`) are not calculated/stored by the job.
|
||||
- **Data Persistence**: Automatic recalculations risk overwriting manual user adjustments.
|
||||
- **Automation Gap**: Ledger entries are currently imported from external Excel files rather than generated automatically from the summaries.
|
||||
- **UI Polish**: "Clientization" and HTMX context (`client-id`) TODOs remain in the admin interface.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
...
|
||||
|
||||
|
||||
This plan completes the implementation of automatic sales summary calculations and ensures they are correctly posted to the ledger. Currently, the `sales-summaries-v2` job calculates detailed daily summaries, but it doesn't store aggregate totals, preserve manual adjustments, or trigger the creation of actual ledger entries. Additionally, the admin UI has several unresolved TODOs.
|
||||
|
||||
---
|
||||
|
||||
## Problem Frame
|
||||
|
||||
The system currently aggregates raw sales data into a `sales-summary` entity, but the final step—creating balanced journal entries for the general ledger—is a manual process involving external Excel calculations and subsequent imports. This creates a dependency on external tools and increases the risk of data entry errors. The goal is to automate this pipeline entirely within the product.
|
||||
|
||||
---
|
||||
|
||||
## Requirements Trace
|
||||
|
||||
- R1. Calculate and store aggregate totals (e.g., `:sales-summary/total-card-payments`) on the `sales-summary` entity.
|
||||
- R2. Preserve user-made manual adjustments (`:sales-summary-item/manual? true`) during automatic recalculations.
|
||||
- R3. Aggregate detailed `sales-summary-item`s into balanced `journal-entry` lines by account and location.
|
||||
- R4. Automate the posting of these aggregated totals to the ledger.
|
||||
- R5. Resolve UI TODOs in the Sales Summaries admin page, specifically regarding client-scoping ("clientize") and HTMX context (`client-id`).
|
||||
|
||||
---
|
||||
|
||||
## Scope Boundaries
|
||||
|
||||
- **In-Scope**:
|
||||
- Enhancements to the `sales-summaries-v2` job.
|
||||
- Implementation of the summary-to-ledger aggregation and posting logic.
|
||||
- Cleanup of the Sales Summaries admin UI.
|
||||
- **Out-of-Scope**:
|
||||
- Changing the fundamental calculation logic for sales orders/refunds.
|
||||
- Creating new ledger accounts (assume existing account mapping is sufficient).
|
||||
- Changing the naming of refunds/returns (user requested to keep as is).
|
||||
|
||||
---
|
||||
|
||||
## Context & Research
|
||||
|
||||
### Relevant Code and Patterns
|
||||
|
||||
- **Jobs**: `src/clj/auto_ap/jobs/sales_summaries.clj` contains the main calculation logic.
|
||||
- **UI**: `src/clj/auto_ap/ssr/admin/sales_summaries.clj` implements the admin interface.
|
||||
- **Ledger Posting**: `src/clj/auto_ap/ledger.clj` and `iol_ion/src/iol_ion/tx/upsert_ledger.clj` handle journal entry creation.
|
||||
- **Reconciliation Pattern**: `reconcile-ledger` in `src/clj/auto_ap/ledger.clj` shows how to find missing ledger entries and trigger their creation.
|
||||
|
||||
### Institutional Learnings
|
||||
|
||||
- No existing documented patterns for sales summary posting were found in `docs/solutions/`. This implementation will establish the pattern.
|
||||
|
||||
---
|
||||
|
||||
## Key Technical Decisions
|
||||
|
||||
- **Detailed Summary $\to$ Aggregated Ledger**: The `sales-summary` will maintain granular detail (line items, specific fee types), but the ledger posting will aggregate these items by account and location to create balanced `journal-entry` lines.
|
||||
- **Automatic Posting**: Posting to the ledger will be integrated into the reconciliation process, similar to how invoices and transactions are handled in `reconcile-ledger`.
|
||||
- **Location Handling**: Since `sales-summary-item`s don't have a location, a default location for the client will be used for ledger posting.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
### Resolved During Planning
|
||||
|
||||
- **Architectural Decision**: Use a detailed summary that aggregates into the ledger.
|
||||
- **Renaming**: Keep "Refunds/Returns" as is.
|
||||
|
||||
### Deferred to Implementation
|
||||
|
||||
- **Default Location Logic**: Exactly how the "default location" for a client is retrieved or defined.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Units
|
||||
|
||||
- U1. **Enhance `sales-summaries-v2` Job**
|
||||
|
||||
**Goal:** Ensure the job stores aggregate totals and preserves manual adjustments.
|
||||
|
||||
**Requirements:** R1, R2
|
||||
|
||||
**Dependencies:** None
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/clj/auto_ap/jobs/sales_summaries.clj`
|
||||
|
||||
**Approach:**
|
||||
- Update `sales-summaries-v2` to calculate totals for attributes like `:sales-summary/total-card-payments`, `:sales-summary/total-cash-payments`, etc., based on the generated items.
|
||||
- Implement a merge strategy: when updating a summary, keep any items where `:sales-summary-item/manual?` is true, and only replace the automatically calculated items.
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path: Running the job for a client with sales and refunds results in a `sales-summary` with correct `:sales-summary/total-*` attributes.
|
||||
- Edge case: Running the job on a summary that already has a manual item ensures the manual item is not overwritten.
|
||||
|
||||
**Verification:**
|
||||
- Datomic query shows `sales-summary` entities have populated total attributes and preserved manual items.
|
||||
|
||||
---
|
||||
|
||||
- U2. **Implement Summary-to-Ledger Aggregation**
|
||||
|
||||
**Goal:** Create a function to transform detailed summary items into balanced ledger lines.
|
||||
|
||||
**Requirements:** R3
|
||||
|
||||
**Dependencies:** U1
|
||||
|
||||
**Files:**
|
||||
- Create: `src/clj/auto_ap/ledger/sales_summaries.clj` (or add to `src/clj/auto_ap/ledger.clj`)
|
||||
- Test: `test/clj/auto_ap/ledger_test.clj`
|
||||
|
||||
**Approach:**
|
||||
- Create a function `aggregate-summary-items` that:
|
||||
1. Groups `sales-summary-item`s by `:ledger-mapped/account`.
|
||||
2. Sums the `:ledger-mapped/amount` based on `:ledger-mapped/ledger-side` (debit vs credit).
|
||||
3. Assigns a location (default client location).
|
||||
4. Returns a list of `journal-entry-line` maps.
|
||||
|
||||
**Test scenarios:**
|
||||
- Happy path: A set of items with mixed accounts and sides aggregates into the correct number of ledger lines with summed amounts.
|
||||
- Edge case: Items with `nil` accounts are handled gracefully (e.g., mapped to an "Unknown" account or logged as error).
|
||||
|
||||
**Verification:**
|
||||
- Unit tests verify that a list of `sales-summary-item`s is correctly transformed into `journal-entry-line`s.
|
||||
|
||||
---
|
||||
|
||||
- U3. **Implement Automatic Ledger Posting for Summaries**
|
||||
|
||||
**Goal:** Ensure sales summaries trigger the creation of ledger entries.
|
||||
|
||||
**Requirements:** R4
|
||||
|
||||
**Dependencies:** U2
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/clj/auto_ap/ledger.clj`
|
||||
- Modify: `src/clj/auto_ap/jobs/sales_summaries.clj`
|
||||
|
||||
**Approach:**
|
||||
- Implement a `:upsert-sales-summary-ledger` transaction or function that takes a `sales-summary` and uses the aggregation logic from U2 to post to the ledger.
|
||||
- Integrate this into the `reconcile-ledger` function in `src/clj/auto_ap/ledger.clj` to find summaries missing ledger entries and post them.
|
||||
|
||||
**Test scenarios:**
|
||||
- Integration: Running `reconcile-ledger` identifies a `sales-summary` missing a `journal-entry` and creates a balanced `journal-entry` for it.
|
||||
- Happy path: The created `journal-entry` has the correct total amount and matches the summary totals.
|
||||
|
||||
**Verification:**
|
||||
- A `sales-summary` entity is linked to a `journal-entry` via `:journal-entry/original-entity`.
|
||||
|
||||
---
|
||||
|
||||
- U4. **Resolve UI TODOs in Sales Summaries Admin**
|
||||
|
||||
**Goal:** Fix client-scoping and HTMX context in the admin UI.
|
||||
|
||||
**Requirements:** R5
|
||||
|
||||
**Dependencies:** None
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/clj/auto_ap/ssr/admin/sales_summaries.clj`
|
||||
|
||||
**Approach:**
|
||||
- Resolve "clientize" TODOs: Ensure the data pulled for the table and edit wizard is correctly scoped and transformed using client-specific context.
|
||||
- Fix HTMX `client-id` passing: Update the `new-summary-item` trigger to correctly pass the `client-id` via `hx-vals` from the form state.
|
||||
- Clean up any remaining schema TODOs in the SSR file.
|
||||
|
||||
**Test scenarios:**
|
||||
- Integration: Adding a new summary item in the UI correctly sends the `client-id` and the item is created for the correct client.
|
||||
- Happy path: The summary table displays correctly and "missing account" warnings appear only for items without a mapped account.
|
||||
|
||||
**Verification:**
|
||||
- Manual verification in the browser: New items are added correctly, and the UI is free of "missing account" red pills for mapped items.
|
||||
|
||||
---
|
||||
|
||||
## System-Wide Impact
|
||||
|
||||
- **Interaction graph**: The `sales-summaries-v2` job now feeds into the ledger system via `reconcile-ledger`.
|
||||
- **Error propagation**: Failures in the aggregation logic will prevent the `journal-entry` from being created, which will be surfaced by `reconcile-ledger` as a missing entry.
|
||||
- **State lifecycle risks**: Ensuring that `manual?` items are not overwritten during automatic recalculation is critical to avoid losing user adjustments.
|
||||
- **Integration coverage**: Integration tests must cover the full flow: `sales-orders` $\to$ `sales-summary` $\to$ `journal-entry`.
|
||||
|
||||
---
|
||||
|
||||
## Risks & Dependencies
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Overwriting manual adjustments | Implement explicit merge logic based on the `:sales-summary-item/manual?` flag. |
|
||||
| Unbalanced ledger entries | Use a strict aggregation function that ensures debits = credits for every posted summary. |
|
||||
| Missing location data | Implement a robust fallback to a default client location. |
|
||||
|
||||
---
|
||||
|
||||
## Sources & References
|
||||
|
||||
- Related code: `src/clj/auto_ap/jobs/sales_summaries.clj`
|
||||
- Related code: `src/clj/auto_ap/ssr/admin/sales_summaries.clj`
|
||||
- Related code: `src/clj/auto_ap/ledger.clj`
|
||||
- Related code: `iol_ion/src/iol_ion/tx/upsert_ledger.clj`
|
||||
@@ -38,19 +38,18 @@
|
||||
|
||||
(defn regenerate-literals []
|
||||
(require 'com.github.ivarref.gen-fn)
|
||||
(spit
|
||||
"resources/functions.edn"
|
||||
(let [datomic-fn @(resolve 'com.github.ivarref.gen-fn/datomic-fn)]
|
||||
[(datomic-fn :pay #'iol-ion.tx.pay/pay)
|
||||
(datomic-fn :plus #'iol-ion.tx.plus/plus)
|
||||
(datomic-fn :propose-invoice #'iol-ion.tx.propose-invoice/propose-invoice)
|
||||
(datomic-fn :reset-rels #'iol-ion.tx.reset-rels/reset-rels)
|
||||
(datomic-fn :reset-scalars #'iol-ion.tx.reset-scalars/reset-scalars)
|
||||
(datomic-fn :upsert-entity #'iol-ion.tx.upsert-entity/upsert-entity)
|
||||
(datomic-fn :upsert-invoice #'iol-ion.tx.upsert-invoice/upsert-invoice)
|
||||
(datomic-fn :upsert-ledger #'iol-ion.tx.upsert-ledger/upsert-ledger)
|
||||
(datomic-fn :upsert-transaction #'iol-ion.tx.upsert-transaction/upsert-transaction)
|
||||
(datomic-fn :upsert-sales-summary-ledger #'iol-ion.tx.upsert-sales-summary-ledger/upsert-sales-summary-ledger)])))
|
||||
(spit
|
||||
"resources/functions.edn"
|
||||
(let [datomic-fn @(resolve 'com.github.ivarref.gen-fn/datomic-fn)]
|
||||
[(datomic-fn :pay #'iol-ion.tx.pay/pay)
|
||||
(datomic-fn :plus #'iol-ion.tx.plus/plus)
|
||||
(datomic-fn :propose-invoice #'iol-ion.tx.propose-invoice/propose-invoice)
|
||||
(datomic-fn :reset-rels #'iol-ion.tx.reset-rels/reset-rels)
|
||||
(datomic-fn :reset-scalars #'iol-ion.tx.reset-scalars/reset-scalars)
|
||||
(datomic-fn :upsert-entity #'iol-ion.tx.upsert-entity/upsert-entity)
|
||||
(datomic-fn :upsert-invoice #'iol-ion.tx.upsert-invoice/upsert-invoice)
|
||||
(datomic-fn :upsert-ledger #'iol-ion.tx.upsert-ledger/upsert-ledger)
|
||||
(datomic-fn :upsert-transaction #'iol-ion.tx.upsert-transaction/upsert-transaction)])))
|
||||
|
||||
(comment
|
||||
(regenerate-literals)
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
(ns iol-ion.tx.upsert-sales-summary-ledger
|
||||
(:import [java.util UUID Date])
|
||||
(:require [datomic.api :as dc]))
|
||||
|
||||
(defn -random-tempid []
|
||||
(dc/tempid :db.part/user))
|
||||
|
||||
(def extant-read '[:db/id :journal-entry/date :journal-entry/client {:journal-entry/line-items [:journal-entry-line/account :journal-entry-line/location :db/id :journal-entry-line/client+account+location+date]}])
|
||||
|
||||
(defn current-date [db]
|
||||
(let [last-tx (dc/t->tx (dc/basis-t db))
|
||||
[[date]] (seq (dc/q '[:find ?ti :in $ ?tx
|
||||
:where [?tx :db/txInstant ?ti]]
|
||||
db
|
||||
last-tx))]
|
||||
date))
|
||||
|
||||
(defn calc-client+account+location+date [je jel]
|
||||
[(or
|
||||
(:db/id (:journal-entry/client je))
|
||||
(:journal-entry/client je))
|
||||
(or (:db/id (:journal-entry-line/account jel))
|
||||
(:journal-entry-line/account jel))
|
||||
(-> jel :journal-entry-line/location)
|
||||
(-> je :journal-entry/date)])
|
||||
|
||||
(defn summary->journal-entry [db summary-id]
|
||||
(let [summary (dc/pull db '[:sales-summary/client
|
||||
:sales-summary/date
|
||||
{:sales-summary/items [:sales-summary-item/category
|
||||
:ledger-mapped/account
|
||||
:ledger-mapped/amount
|
||||
:ledger-mapped/ledger-side]}]
|
||||
summary-id)
|
||||
items (:sales-summary/items summary)
|
||||
aggregated (->> items
|
||||
(filter :ledger-mapped/account)
|
||||
(group-by :ledger-mapped/account)
|
||||
(map (fn [[account acc-items]]
|
||||
(reduce
|
||||
(fn [m item]
|
||||
(update m (:ledger-mapped/ledger-side item) (fnil + 0.0) (:ledger-mapped/amount item 0.0)))
|
||||
{:account account}
|
||||
acc-items))))
|
||||
line-items (mapv (fn [{:keys [account] :as m}]
|
||||
(cond-> {:db/id (str (java.util.UUID/randomUUID))
|
||||
:journal-entry-line/account account}
|
||||
(get m :ledger-side/debit) (assoc :journal-entry-line/debit (get m :ledger-side/debit))
|
||||
(get m :ledger-side/credit) (assoc :journal-entry-line/credit (get m :ledger-side/credit))))
|
||||
aggregated)
|
||||
total-amount (reduce + 0.0 (map #(get % :ledger-side/debit 0.0) aggregated))]
|
||||
(when (seq line-items)
|
||||
{:journal-entry/source "sales-summary"
|
||||
:journal-entry/client (:db/id (:sales-summary/client summary))
|
||||
:journal-entry/date (:sales-summary/date summary)
|
||||
:journal-entry/original-entity summary-id
|
||||
:journal-entry/amount total-amount
|
||||
:journal-entry/line-items line-items})))
|
||||
|
||||
(defn upsert-sales-summary-ledger [db summary]
|
||||
(assert (:db/id summary) "Must provide summary id")
|
||||
(let [upserted-entity [[:upsert-entity {:db/id (:db/id summary)}]]
|
||||
with-summary (dc/with db upserted-entity)
|
||||
summary-id (:db/id summary)
|
||||
journal-entry (summary->journal-entry (:db-after with-summary) summary-id)
|
||||
client-id (-> (dc/pull (:db-after with-summary)
|
||||
[{:sales-summary/client [:db/id]}]
|
||||
summary-id)
|
||||
:sales-summary/client
|
||||
:db/id)]
|
||||
(into upserted-entity
|
||||
(if journal-entry
|
||||
[[:upsert-ledger journal-entry]
|
||||
{:db/id client-id
|
||||
:client/ledger-last-change (current-date db)}]
|
||||
[[:db/retractEntity [:journal-entry/original-entity summary-id]]
|
||||
{:db/id client-id
|
||||
:client/ledger-last-change (current-date db)}]))))
|
||||
@@ -1949,69 +1949,12 @@
|
||||
:db/unique :db.unique/identity
|
||||
:db/index true}
|
||||
{:db/ident :sales-summary/client+dirty
|
||||
:db/valueType :db.type/tuple
|
||||
:db/tupleAttrs [:sales-summary/client :sales-summary/dirty]
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true}
|
||||
:db/valueType :db.type/tuple
|
||||
:db/tupleAttrs [:sales-summary/client :sales-summary/dirty]
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true}
|
||||
|
||||
{:db/ident :sales-summary/total-card-payments
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-cash-payments
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-food-app-payments
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-gift-card-payments
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-card-refunds
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-cash-refunds
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-food-app-refunds
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-fees
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-discounts
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-tax
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-tip
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-returns
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-unknown-payments
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-net
|
||||
:db/noHistory true
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
|
||||
{:db/ident :sales-summary-item/category
|
||||
{:db/ident :sales-summary-item/category
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary-item/sort-order
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
[iol-ion.tx.propose-invoice]
|
||||
[iol-ion.tx.reset-rels]
|
||||
[iol-ion.tx.reset-scalars]
|
||||
[iol-ion.tx.upsert-entity]
|
||||
[iol-ion.tx.upsert-invoice]
|
||||
[iol-ion.tx.upsert-ledger]
|
||||
[iol-ion.tx.upsert-transaction]
|
||||
[iol-ion.tx.upsert-sales-summary-ledger]
|
||||
[iol-ion.tx.upsert-entity]
|
||||
[iol-ion.tx.upsert-invoice]
|
||||
[iol-ion.tx.upsert-ledger]
|
||||
[iol-ion.tx.upsert-transaction]
|
||||
[com.github.ivarref.gen-fn :refer [gen-fn! datomic-fn]]
|
||||
[auto-ap.utils :refer [default-pagination-size by]]
|
||||
[clojure.edn :as edn]
|
||||
|
||||
@@ -276,108 +276,48 @@
|
||||
:ledger-mapped/amount amount
|
||||
:ledger-mapped/ledger-side :ledger-side/debit}))
|
||||
|
||||
(defn- summary->journal-entry [db summary-id]
|
||||
(let [summary (dc/pull db '[:sales-summary/client
|
||||
:sales-summary/date
|
||||
{:sales-summary/items [:sales-summary-item/category
|
||||
:ledger-mapped/account
|
||||
:ledger-mapped/amount
|
||||
:ledger-mapped/ledger-side]}]
|
||||
summary-id)
|
||||
items (:sales-summary/items summary)
|
||||
;; Aggregate items by account and ledger side
|
||||
aggregated (->> items
|
||||
(filter :ledger-mapped/account)
|
||||
(group-by :ledger-mapped/account)
|
||||
(map (fn [[account acc-items]]
|
||||
(reduce
|
||||
(fn [m item]
|
||||
(update m (:ledger-mapped/ledger-side item) (fnil + 0.0) (:ledger-mapped/amount item 0.0)))
|
||||
{:account account}
|
||||
acc-items))))
|
||||
line-items (mapv (fn [{:keys [account] :as m}]
|
||||
(cond-> {:db/id (str (java.util.UUID/randomUUID))
|
||||
:journal-entry-line/account account}
|
||||
(get m :ledger-side/debit) (assoc :journal-entry-line/debit (get m :ledger-side/debit))
|
||||
(get m :ledger-side/credit) (assoc :journal-entry-line/credit (get m :ledger-side/credit))))
|
||||
aggregated)
|
||||
total-amount (reduce + 0.0 (map #(get % :ledger-side/debit 0.0) aggregated))]
|
||||
(when (seq line-items)
|
||||
{:journal-entry/source "sales-summary"
|
||||
:journal-entry/client (:db/id (:sales-summary/client summary))
|
||||
:journal-entry/date (:sales-summary/date summary)
|
||||
:journal-entry/original-entity summary-id
|
||||
:journal-entry/amount total-amount
|
||||
:journal-entry/line-items line-items})))
|
||||
|
||||
(defn- calc-aggregate-totals [items]
|
||||
(reduce
|
||||
(fn [acc item]
|
||||
(let [cat (:sales-summary-item/category item)
|
||||
amt (:ledger-mapped/amount item 0.0)
|
||||
side (:ledger-mapped/ledger-side item)]
|
||||
(cond-> acc
|
||||
;; Payments (debits)
|
||||
(= cat "Card Payments") (update :sales-summary/total-card-payments (fnil + 0.0) amt)
|
||||
(= cat "Cash Payments") (update :sales-summary/total-cash-payments (fnil + 0.0) amt)
|
||||
(= cat "Food App Payments") (update :sales-summary/total-food-app-payments (fnil + 0.0) amt)
|
||||
(= cat "Gift Card Payments") (update :sales-summary/total-gift-card-payments (fnil + 0.0) amt)
|
||||
(= cat "Unknown") (update :sales-summary/total-unknown-payments (fnil + 0.0) amt)
|
||||
;; Refunds (credits)
|
||||
(= cat "Card Refunds") (update :sales-summary/total-card-refunds (fnil + 0.0) amt)
|
||||
(= cat "Cash Refunds") (update :sales-summary/total-cash-refunds (fnil + 0.0) amt)
|
||||
(= cat "Food App Refunds") (update :sales-summary/total-food-app-refunds (fnil + 0.0) amt)
|
||||
;; Other
|
||||
(= cat "Fees") (update :sales-summary/total-fees (fnil + 0.0) amt)
|
||||
(= cat "Discounts") (update :sales-summary/total-discounts (fnil + 0.0) amt)
|
||||
(= cat "Tax") (update :sales-summary/total-tax (fnil + 0.0) amt)
|
||||
(= cat "Tip") (update :sales-summary/total-tip (fnil + 0.0) amt)
|
||||
(= cat "Returns") (update :sales-summary/total-returns (fnil + 0.0) amt)
|
||||
;; Net from sales line items
|
||||
:else (update :sales-summary/total-net (fnil + 0.0) amt))))
|
||||
{}
|
||||
items))
|
||||
|
||||
(defn sales-summaries-v2 []
|
||||
(doseq [[c client-code] (dc/q '[:find ?c ?client-code
|
||||
:in $
|
||||
:where [?c :client/code ?client-code]]
|
||||
(dc/db conn))
|
||||
{:sales-summary/keys [date] :db/keys [id] :as existing-summary} (dirty-sales-summaries c)]
|
||||
:in $
|
||||
:where [?c :client/code ?client-code]]
|
||||
(dc/db conn))
|
||||
{:sales-summary/keys [date] :db/keys [id]} (dirty-sales-summaries c)]
|
||||
(mu/with-context {:client-code client-code
|
||||
:date date}
|
||||
(alog/info ::updating)
|
||||
(let [manual-items (->> existing-summary
|
||||
:sales-summary/items
|
||||
(filter :sales-summary-item/manual?))
|
||||
calculated-items (->>
|
||||
(get-sales c date)
|
||||
(concat (get-payment-items c date))
|
||||
(concat (get-refund-items c date))
|
||||
(cons (get-discounts c date))
|
||||
(cons (get-fees c date))
|
||||
(cons (get-tax c date))
|
||||
(cons (get-tip c date))
|
||||
(cons (get-returns c date))
|
||||
(filter identity)
|
||||
(map (fn [z]
|
||||
(assoc z :ledger-mapped/account (some-> z :sales-summary-item/category str/lower-case name->number lookup-account)
|
||||
:sales-summary-item/manual? false))))
|
||||
all-items (concat calculated-items manual-items)
|
||||
aggregates (calc-aggregate-totals all-items)
|
||||
result (into {:db/id id
|
||||
:sales-summary/client c
|
||||
:sales-summary/date date
|
||||
:sales-summary/dirty false
|
||||
:sales-summary/client+date [c date]
|
||||
:sales-summary/items all-items}
|
||||
aggregates)]
|
||||
(if (seq (:sales-summary/items result))
|
||||
(do
|
||||
(alog/info ::upserting-summaries
|
||||
:category-count (count (:sales-summary/items result)))
|
||||
@(dc/transact conn [[:upsert-entity result]]))
|
||||
@(dc/transact conn [{:db/id id :sales-summary/dirty false}]))))))
|
||||
:date date}
|
||||
(alog/info ::updating)
|
||||
(let [result {:db/id id
|
||||
:sales-summary/client c
|
||||
:sales-summary/date date
|
||||
:sales-summary/dirty false
|
||||
:sales-summary/client+date [c date]
|
||||
|
||||
:sales-summary/items
|
||||
(->>
|
||||
(get-sales c date)
|
||||
(concat (get-payment-items c date))
|
||||
(concat (get-refund-items c date))
|
||||
(cons (get-discounts c date))
|
||||
(cons (get-fees c date))
|
||||
(cons (get-tax c date))
|
||||
(cons (get-tip c date))
|
||||
(cons (get-returns c date))
|
||||
(filter identity)
|
||||
(map (fn [z]
|
||||
(assoc z :ledger-mapped/account (some-> z :sales-summary-item/category str/lower-case name->number lookup-account)
|
||||
:sales-summary-item/manual? false))
|
||||
)) }]
|
||||
(if (seq (:sales-summary/items result))
|
||||
(do
|
||||
(alog/info ::upserting-summaries
|
||||
:category-count (count (:sales-summary/items result)))
|
||||
@(dc/transact conn [[:upsert-entity result]]))
|
||||
@(dc/transact conn [{:db/id id :sales-summary/dirty false}]))))))
|
||||
|
||||
(let [c (auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGCL" ])
|
||||
date #inst "2024-04-14T00:00:00-07:00"]
|
||||
(get-payment-items c date)
|
||||
|
||||
)
|
||||
|
||||
|
||||
(defn reset-summaries []
|
||||
|
||||
@@ -35,42 +35,27 @@
|
||||
|
||||
|
||||
invoices-missing-ledger-entries (->> (dc/q {:find ['?t ]
|
||||
:in ['$ '?sd]
|
||||
:where ['[?t :invoice/date ?d]
|
||||
'[(>= ?d ?sd)]
|
||||
'(not [_ :journal-entry/original-entity ?t])
|
||||
'[?t :invoice/total ?amt]
|
||||
'[(not= 0.0 ?amt)]
|
||||
'(not [?t :invoice/status :invoice-status/voided])
|
||||
'(not [?t :invoice/import-status :import-status/pending])
|
||||
'(not [?t :invoice/exclude-from-ledger true])
|
||||
]}
|
||||
(dc/db conn) start-date)
|
||||
:in ['$ '?sd]
|
||||
:where ['[?t :invoice/date ?d]
|
||||
'[(>= ?d ?sd)]
|
||||
'(not [_ :journal-entry/original-entity ?t])
|
||||
'[?t :invoice/total ?amt]
|
||||
'[(not= 0.0 ?amt)]
|
||||
'(not [?t :invoice/status :invoice-status/voided])
|
||||
'(not [?t :invoice/import-status :import-status/pending])
|
||||
'(not [?t :invoice/exclude-from-ledger true])
|
||||
]}
|
||||
(dc/db conn) start-date)
|
||||
(map first)
|
||||
(mapv (fn [i]
|
||||
[:upsert-invoice {:db/id i}])))
|
||||
|
||||
sales-summaries-missing-ledger-entries (->> (dc/q {:find ['?ss ]
|
||||
:in ['$ '?sd]
|
||||
:where ['[?ss :sales-summary/date ?d]
|
||||
'[(>= ?d ?sd)]
|
||||
'(not [_ :journal-entry/original-entity ?ss])
|
||||
'[?ss :sales-summary/items ?item]
|
||||
'[?item :ledger-mapped/account]
|
||||
]}
|
||||
(dc/db conn) start-date)
|
||||
(map first)
|
||||
(mapv (fn [ss]
|
||||
[:upsert-sales-summary-ledger {:db/id ss}])))
|
||||
|
||||
repairs (vec (concat txes-missing-ledger-entries invoices-missing-ledger-entries sales-summaries-missing-ledger-entries))]
|
||||
repairs (vec (concat txes-missing-ledger-entries invoices-missing-ledger-entries))]
|
||||
(when (seq repairs)
|
||||
(mu/log ::ledger-repairs-needed
|
||||
:sample (take 3 repairs)
|
||||
:transaction-count (count txes-missing-ledger-entries)
|
||||
:invoice-count (count invoices-missing-ledger-entries)
|
||||
:sales-summary-count (count sales-summaries-missing-ledger-entries))
|
||||
@(dc/transact conn repairs)))))
|
||||
(mu/log ::ledger-repairs-needed
|
||||
:sample (take 3 repairs)
|
||||
:transaction-count (count txes-missing-ledger-entries)
|
||||
:invoice-count (count invoices-missing-ledger-entries))
|
||||
@(dc/transact conn repairs)))))
|
||||
|
||||
|
||||
(defn touch-transaction [e]
|
||||
|
||||
@@ -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" ))
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
@@ -72,14 +72,14 @@
|
||||
[:sales-summary/date :xform clj-time.coerce/from-date]
|
||||
{:sales-summary/client [:client/code :client/name :db/id]}
|
||||
{:sales-summary/items [{[:ledger-mapped/ledger-side :xform iol-ion.query/ident] [:db/ident]
|
||||
}
|
||||
:ledger-mapped/account
|
||||
:ledger-mapped/amount
|
||||
:sales-summary-item/category
|
||||
:sales-summary-item/sort-order
|
||||
:db/id
|
||||
:sales-summary-item/manual?]
|
||||
} ])
|
||||
} ;; TODO clientize
|
||||
:ledger-mapped/account
|
||||
:ledger-mapped/amount
|
||||
:sales-summary-item/category
|
||||
:sales-summary-item/sort-order
|
||||
:db/id
|
||||
:sales-summary-item/manual?]
|
||||
} ]) ;; TODO
|
||||
|
||||
(defn fetch-ids [db request]
|
||||
(let [query-params (:query-params request)
|
||||
@@ -179,8 +179,10 @@
|
||||
:db/id (:db/id entity))}
|
||||
svg/pencil)])
|
||||
:oob-render
|
||||
(fn [_request]
|
||||
[])
|
||||
(fn [request]
|
||||
[#_(assoc-in (date-range-field {:value {:start (:start-date (:query-params request))
|
||||
:end (:end-date (:query-params request))}
|
||||
:id "date-range"}) [1 :hx-swap-oob] true)]) ;; TODO
|
||||
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:admin)}
|
||||
"Admin"]
|
||||
@@ -239,9 +241,13 @@
|
||||
:primary
|
||||
:red)} "Total: " (format "$%,.2f" total-credits))]]))}]}))
|
||||
|
||||
;; Architecture: Sales summary maintains granular detail (line items, fee types)
|
||||
;; and is aggregated into ledger entries by account/location. Manual adjustments
|
||||
;; are preserved during automatic recalculation.
|
||||
;; TODO schema cleanup
|
||||
;; Decide on what should be calculated as generating ledger entries, and what should be calculated
|
||||
;; as part of the summary
|
||||
;; default thought here is that the summary has more detail (e.g., line items), fees broken out by type
|
||||
;; and aggregated into the final ledger entry
|
||||
;; that allows customization at any level.
|
||||
;; TODO rename refunds/returns
|
||||
|
||||
(def row* (partial helper/row* grid-page))
|
||||
(def table* (partial helper/table* grid-page))
|
||||
@@ -430,14 +436,15 @@
|
||||
(com/data-grid-header {} "")]}
|
||||
(fc/with-field :sales-summary/items
|
||||
(list
|
||||
(fc/cursor-map #(sales-summary-item-row* {:value %
|
||||
:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state))) }))
|
||||
(com/data-grid-new-row {:colspan 5
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item)
|
||||
:row-offset 0
|
||||
:index (count (fc/field-value))
|
||||
:tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}}
|
||||
"New Summary Item")))
|
||||
(fc/cursor-map #(sales-summary-item-row* {:value %
|
||||
:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state))) }))
|
||||
;; TODO
|
||||
(com/data-grid-new-row {:colspan 5
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item)
|
||||
:row-offset 0
|
||||
:index (count (fc/field-value))
|
||||
:tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}} ;; TODO
|
||||
"New Summary Item")))
|
||||
(summary-total-row* request)
|
||||
(unbalanced-row* request)) ])
|
||||
|
||||
|
||||
@@ -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"})]])])
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)))))))
|
||||
|
||||
Reference in New Issue
Block a user