Compare commits
81 Commits
6a7c529c24
...
integreat-
| Author | SHA1 | Date | |
|---|---|---|---|
| 5452b8b779 | |||
| 5c2cf8a631 | |||
| b8a0e9c3dc | |||
| 9659164fdc | |||
| 8f0a474fa8 | |||
| 6814cf1b15 | |||
| 3441ae63b4 | |||
| 79ddda624a | |||
| cbb9bc750d | |||
| a7ac7eae35 | |||
| c6699dd05a | |||
| 1be83a01f5 | |||
| ebd91f1911 | |||
| c9a587a8c5 | |||
| 9997d60de1 | |||
| 06fb0ea067 | |||
| 9a7d0b8b18 | |||
| 70a3db9a64 | |||
| 4e22fb1d82 | |||
| a88dcf4122 | |||
| 00b5303c28 | |||
| ab1a2c3368 | |||
| 724b6d82f5 | |||
| 6500c44909 | |||
| 2e4152e3fc | |||
| 6ce6a6e0c7 | |||
| 17eebe5628 | |||
| e5a2d0bbba | |||
| 7db1e07512 | |||
| df32100ca2 | |||
| daea729e8e | |||
| de933699aa | |||
| 4fca49bff0 | |||
| 2f9da3cdd9 | |||
| 78bd1d92e0 | |||
| 99dd88329e | |||
| de73233a08 | |||
| 11cc887671 | |||
| a4d7ac5982 | |||
| f42d937691 | |||
| 200056098f | |||
| 712b2c0cb8 | |||
| 85652a7ce7 | |||
| ae0788e6dd | |||
| f239b114c3 | |||
| 8e3aa13f4d | |||
| 5b2aba561c | |||
| 3715910029 | |||
| 03bfca35cb | |||
| ba87805d4c | |||
| 8bd0cee1b1 | |||
| 76c6eaddb9 | |||
| ddf11a7cb3 | |||
| adb6ecb1ff | |||
| 4221d6a0d6 | |||
| 918ddd14ff | |||
| acd4184ef0 | |||
| 857a1536ef | |||
| 535ef4d113 | |||
| 351659f8eb | |||
| 4739769297 | |||
| 567db50a66 | |||
| dbfa04c766 | |||
| 0692089e39 | |||
| 8189a7648b | |||
| dd4d1a6d4f | |||
| 4f32527732 | |||
| 0811771ae6 | |||
| c6b55ce567 | |||
| 1f9a7080e1 | |||
| 6f7f1c7815 | |||
| 065d1182d7 | |||
| b42e2a6a44 | |||
| e8979738ab | |||
| 08b948c24b | |||
| aae1d2168b | |||
| 83a739ac5b | |||
| 021a2f14f7 | |||
| 2c8985203e | |||
| 64506705e7 | |||
| 66b0b611e4 |
55
.agents/skills/agent-browser/SKILL.md
Normal file
55
.agents/skills/agent-browser/SKILL.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
name: agent-browser
|
||||
description: Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.
|
||||
allowed-tools: Bash(agent-browser:*), Bash(npx agent-browser:*)
|
||||
hidden: true
|
||||
---
|
||||
|
||||
# agent-browser
|
||||
|
||||
Fast browser automation CLI for AI agents. Chrome/Chromium via CDP with
|
||||
accessibility-tree snapshots and compact `@eN` element refs.
|
||||
|
||||
Install: `npm i -g agent-browser && agent-browser install`
|
||||
|
||||
## Start here
|
||||
|
||||
This file is a discovery stub, not the usage guide. Before running any
|
||||
`agent-browser` command, load the actual workflow content from the CLI:
|
||||
|
||||
```bash
|
||||
agent-browser skills get core # start here — workflows, common patterns, troubleshooting
|
||||
agent-browser skills get core --full # include full command reference and templates
|
||||
```
|
||||
|
||||
The CLI serves skill content that always matches the installed version,
|
||||
so instructions never go stale. The content in this stub cannot change
|
||||
between releases, which is why it just points at `skills get core`.
|
||||
|
||||
## Specialized skills
|
||||
|
||||
Load a specialized skill when the task falls outside browser web pages:
|
||||
|
||||
```bash
|
||||
agent-browser skills get electron # Electron desktop apps (VS Code, Slack, Discord, Figma, ...)
|
||||
agent-browser skills get slack # Slack workspace automation
|
||||
agent-browser skills get dogfood # Exploratory testing / QA / bug hunts
|
||||
agent-browser skills get vercel-sandbox # agent-browser inside Vercel Sandbox microVMs
|
||||
agent-browser skills get agentcore # AWS Bedrock AgentCore cloud browsers
|
||||
```
|
||||
|
||||
Run `agent-browser skills list` to see everything available on the
|
||||
installed version.
|
||||
|
||||
## Why agent-browser
|
||||
|
||||
- Fast native Rust CLI, not a Node.js wrapper
|
||||
- Works with any AI agent (Cursor, Claude Code, Codex, Continue, Windsurf, etc.)
|
||||
- Chrome/Chromium via CDP with no Playwright or Puppeteer dependency
|
||||
- Accessibility-tree snapshots with element refs for reliable interaction
|
||||
- Sessions, authentication vault, state persistence, video recording
|
||||
- Specialized skills for Electron apps, Slack, exploratory testing, cloud providers
|
||||
|
||||
## Observability Dashboard
|
||||
|
||||
The dashboard runs independently of browser sessions on port 4848 and can also be opened through a proxied or forwarded URL such as `https://dashboard.agent-browser.localhost`. Agents should stay on the dashboard origin: session tabs, status, and stream traffic are proxied internally, so session ports do not need to be exposed.
|
||||
177
.agents/skills/frontend-design/LICENSE.txt
Normal file
177
.agents/skills/frontend-design/LICENSE.txt
Normal file
@@ -0,0 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
42
.agents/skills/frontend-design/SKILL.md
Normal file
42
.agents/skills/frontend-design/SKILL.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: frontend-design
|
||||
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
|
||||
|
||||
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
|
||||
|
||||
## Design Thinking
|
||||
|
||||
Before coding, understand the context and commit to a BOLD aesthetic direction:
|
||||
- **Purpose**: What problem does this interface solve? Who uses it?
|
||||
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
|
||||
- **Constraints**: Technical requirements (framework, performance, accessibility).
|
||||
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
|
||||
|
||||
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
|
||||
|
||||
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
|
||||
- Production-grade and functional
|
||||
- Visually striking and memorable
|
||||
- Cohesive with a clear aesthetic point-of-view
|
||||
- Meticulously refined in every detail
|
||||
|
||||
## Frontend Aesthetics Guidelines
|
||||
|
||||
Focus on:
|
||||
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
|
||||
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
|
||||
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
|
||||
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
|
||||
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
|
||||
|
||||
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
|
||||
|
||||
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
|
||||
|
||||
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
|
||||
|
||||
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
|
||||
1
.claude/skills/agent-browser
Symbolic link
1
.claude/skills/agent-browser
Symbolic link
@@ -0,0 +1 @@
|
||||
../../.agents/skills/agent-browser
|
||||
1
.envrc
1
.envrc
@@ -1 +1,2 @@
|
||||
export OPENROUTER_API_KEY=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
|
||||
export AWS_PROFILE=integreat
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -8,6 +8,8 @@ pom.xml.asc
|
||||
*.class
|
||||
/.lein-*
|
||||
/.nrepl-port
|
||||
nrepl-port
|
||||
.http-port
|
||||
resources/public/js/compiled
|
||||
*.log
|
||||
examples/
|
||||
@@ -46,3 +48,6 @@ data/solr/logs
|
||||
.vscode/**
|
||||
sysco-poller/**/*.csv
|
||||
.aider*
|
||||
.tmp/**
|
||||
playwright-report/**
|
||||
test-results/**
|
||||
|
||||
8
.opencode/opencode.json
Normal file
8
.opencode/opencode.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"edit": {
|
||||
"src/*": "deny"
|
||||
}
|
||||
}
|
||||
}
|
||||
56
.opencode/package-lock.json
generated
56
.opencode/package-lock.json
generated
@@ -5,7 +5,7 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@opencode-ai/plugin": "1.14.31"
|
||||
"@opencode-ai/plugin": "1.15.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
|
||||
@@ -87,32 +87,36 @@
|
||||
]
|
||||
},
|
||||
"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==",
|
||||
"version": "1.15.12",
|
||||
"resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.15.12.tgz",
|
||||
"integrity": "sha512-BBteGXEwJt+1ehHqQ+yKXmoWltrW+2xO++B1Fm/dnMGYWT9luEKA5RlUuVYA2qDF6uwlE7kmHZvQZAM79zWHEA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "1.14.31",
|
||||
"effect": "4.0.0-beta.57",
|
||||
"@opencode-ai/sdk": "1.15.12",
|
||||
"effect": "4.0.0-beta.66",
|
||||
"zod": "4.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentui/core": ">=0.2.0",
|
||||
"@opentui/solid": ">=0.2.0"
|
||||
"@opentui/core": ">=0.2.16",
|
||||
"@opentui/keymap": ">=0.2.16",
|
||||
"@opentui/solid": ">=0.2.16"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@opentui/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@opentui/keymap": {
|
||||
"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==",
|
||||
"version": "1.15.12",
|
||||
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.15.12.tgz",
|
||||
"integrity": "sha512-lOaBNX93dkakZe6C42ttX1bkSx3K2c6+Yv+w8Qv02v5rPlu1vCXbmdfYDh9/bw+oq+NKPSaBm9d6kPA19hA5Lg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "7.0.6"
|
||||
@@ -149,9 +153,9 @@
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"version": "4.0.0-beta.66",
|
||||
"resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.66.tgz",
|
||||
"integrity": "sha512-4arEr62cziFa8BBVDUwJCJJmaVepXf/kRg7KtC0h8+bufngscrHbwWFhr9c+HonwOF+31U3iD3xUJmw9KzX7Dw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
@@ -167,9 +171,9 @@
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.8.0.tgz",
|
||||
"integrity": "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -216,9 +220,9 @@
|
||||
"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==",
|
||||
"version": "1.11.12",
|
||||
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.12.tgz",
|
||||
"integrity": "sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg==",
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"msgpackr-extract": "^3.0.2"
|
||||
@@ -323,9 +327,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "13.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.1.tgz",
|
||||
"integrity": "sha512-9ezox2roIft6ExBVTVqibSd5dc5/47Sw/uY6b4SjQUT2TzQ0tltNquWA46y4xPQmdZYqvnio22SgWd41M86+jw==",
|
||||
"version": "13.0.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz",
|
||||
"integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
@@ -351,9 +355,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
|
||||
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
|
||||
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
|
||||
13
AGENTS.md
13
AGENTS.md
@@ -20,6 +20,11 @@ clj-nrepl-eval -p PORT "(+ 1 2 3)" # evaluate clojure code
|
||||
|
||||
```
|
||||
|
||||
### Editing clojure
|
||||
|
||||
When editing clojure, use the clojure-mcp editing tools, or ask @clojure-author to make the change. It is critical that you
|
||||
do not use the file editing tools unless absolutely necessary.
|
||||
|
||||
Often times, if a file won't compile, first clj-paren-repair on the file, then try again. If it doesn't wor still, try cljfmt check.
|
||||
|
||||
### Running the Application
|
||||
@@ -29,6 +34,14 @@ INTEGREAT_JOB="" lein run # Default: port 3000
|
||||
PORT=3449 lein run
|
||||
```
|
||||
|
||||
If you want to start the server, you should run `lein mcp-repl` which will output a nrepl-server port file and http-server port file.
|
||||
|
||||
## Browser Automation
|
||||
|
||||
When using the **agent-browser** skill for testing or automation:
|
||||
- Navigate to `/dev-login` to simulate an admin user and fake a session
|
||||
- Do not open directly to a specific page unless explicitly instructed to; instead, start on the dashboard and navigate from there
|
||||
|
||||
## Test Execution
|
||||
prefer using clojure-eval skill
|
||||
|
||||
|
||||
144
AUTOMATION_NOTES.md
Normal file
144
AUTOMATION_NOTES.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Automation Notes
|
||||
|
||||
Findings from investigating intermittent dialog-open failures on `/pos/summaries` (and likely other grid pages) when driven by `agent-browser`. Most of these apply equally to any browser automation — Playwright, Selenium, manual rapid-click testing.
|
||||
|
||||
## TL;DR
|
||||
|
||||
The reported "sometimes the dialog opens, sometimes it doesn't" was a server-side bug: `icon-button-` rendered as `<button>` with the HTML default `type="submit"`. Inside a `<form>` (every row in `grid_page_helper`), the click raced HTMX. If form submission won, the browser navigated to `/pos/summaries?id=…` and the modal request was canceled.
|
||||
|
||||
Fix is in `src/clj/auto_ap/ssr/components/buttons.clj` — `icon-button-` now defaults `:type "button"`. Verified with 30/30 rapid open/close cycles with random close delays spanning the entire 300 ms transition window.
|
||||
|
||||
## Modal lifecycle (for reference)
|
||||
|
||||
1. User clicks pencil → htmx `GET /pos/summaries/:id` (the edit-wizard route).
|
||||
2. Server returns response with headers `hx-trigger: modalopen`, `hx-retarget: #modal-content`, `hx-reswap: innerHTML`. See `modal-response` in `src/clj/auto_ap/ssr/utils.clj:41`.
|
||||
3. htmx swaps innerHTML of `#modal-content`, then dispatches a `modalopen` document event.
|
||||
4. Alpine handler on `#modal-holder` (`src/clj/auto_ap/ssr/ui.clj:84`) sets `open=true`.
|
||||
5. `x-show="open"` triggers a 300 ms enter transition on two nested divs (backdrop + content).
|
||||
6. Closing dispatches `modalclose`, sets `open=false`, runs the 300 ms leave transition.
|
||||
|
||||
## Root cause of the reported flakiness
|
||||
|
||||
`grid_page_helper.clj:58-61` wraps each row's action buttons in a `<form>` with a hidden `id` field:
|
||||
|
||||
```clojure
|
||||
(com/data-grid-right-stack-cell {}
|
||||
(into [:form.flex.space-x-2
|
||||
[:input {:type :hidden :name "id" :value ((:id-fn gridspec) entity)}]]
|
||||
((:row-buttons gridspec) request entity)))
|
||||
```
|
||||
|
||||
The buttons in `:row-buttons` come from `icon-button-`, which rendered `<button>` with no explicit type. HTML default: `type="submit"`. When the pencil is clicked:
|
||||
|
||||
- htmx normally intercepts via `hx-get` and calls `preventDefault()`.
|
||||
- If anything (large DOM, htmx still initializing other elements, agent-browser issuing the click in a busy frame) delays htmx's listener relative to the form's submit handler, the form submits.
|
||||
- Form submission triggers a same-page navigation to `/pos/summaries?id=<value>`, which cancels the in-flight XHR. The modal request never lands.
|
||||
|
||||
The race is non-deterministic, which is why it was intermittent. Browser automation makes it more visible because clicks fire faster than a human's, hitting moments when htmx might not yet have fully registered.
|
||||
|
||||
**Fix:** `icon-button-` now does `(merge {:type "button"} params)`. Same fix should be applied prophylactically to any other button helper used inside a row form: `button-`, `a-button-` (less relevant, `<a>` doesn't submit), `navigation-button-`. `group-button-` already sets `type="button"`. `validated-save-button-` correctly stays `submit`.
|
||||
|
||||
## Other findings (cosmetic — not causing failures)
|
||||
|
||||
### Duplicate `x-trap` directive
|
||||
|
||||
`src/clj/auto_ap/ssr/ui.clj:99-100`:
|
||||
|
||||
```clojure
|
||||
"x-trap.inert.noscroll" "open"
|
||||
"x-trap.inert" "open"
|
||||
```
|
||||
|
||||
Both bound to the same expression. Alpine de-duplicates by directive name, so this is dead code. Drop the second line.
|
||||
|
||||
### Mixed `bg-opacity` and `opacity` in inner-modal transitions
|
||||
|
||||
`src/clj/auto_ap/ssr/ui.clj:103-107`:
|
||||
|
||||
```clojure
|
||||
"x-transition:enter-start" "!bg-opacity-0 !translate-y-32"
|
||||
"x-transition:enter-end" "!bg-opacity-100 !translate-y-0"
|
||||
"x-transition:leave-start" "!opacity-100 !translate-y-0"
|
||||
"x-transition:leave-end" "!opacity-0 !translate-y-32"
|
||||
```
|
||||
|
||||
The inner div has no background color, so `bg-opacity-*` does nothing during enter. The leave correctly animates `opacity`. Net effect: enter is a translate-only animation while leave is translate-plus-fade. Asymmetric but works. Should be `opacity` on both sides for consistency.
|
||||
|
||||
### `_x_hidePromise` lingering flag
|
||||
|
||||
After rapid close→open cycles, Alpine's internal `_x_hidePromise` property remains truthy on the inner div even when the element is fully visible. It looks alarming when inspecting state but does not block subsequent transitions. Verified empirically with 30 trials.
|
||||
|
||||
## Browser-automation specifics
|
||||
|
||||
Things that aren't bugs but bite agent-browser scripts:
|
||||
|
||||
### Refs go stale on every HTMX swap
|
||||
|
||||
The `@eN` refs from a `snapshot` are valid only until the page changes. HTMX swaps innerHTML of `#modal-content` on every modal open, so any ref pointing inside the modal — or even refs pointing to row elements after the grid refreshes — silently breaks. Re-snapshot before each interaction; the docs explicitly warn about this.
|
||||
|
||||
### Default click is too fast for a busy frame
|
||||
|
||||
`agent-browser click @eN` dispatches a synthetic click via CDP without waiting for the page to settle. For htmx-driven interactions, the safe pattern is:
|
||||
|
||||
1. Click.
|
||||
2. Wait for the observable side-effect, not for time.
|
||||
|
||||
For modal opens specifically:
|
||||
|
||||
```bash
|
||||
agent-browser click @e_pencil
|
||||
agent-browser wait --fn "document.querySelector('#modal-holder')?._x_dataStack?.[0]?.open && document.querySelector('#modal-content').children.length > 0"
|
||||
agent-browser snapshot -i
|
||||
```
|
||||
|
||||
For modal closes:
|
||||
|
||||
```bash
|
||||
# After clicking a Save button that returns hx-trigger: modalclose
|
||||
agent-browser wait --fn "!document.querySelector('#modal-holder')?._x_dataStack?.[0]?.open"
|
||||
```
|
||||
|
||||
For grid refreshes after filter changes:
|
||||
|
||||
```bash
|
||||
agent-browser wait --fn "!document.querySelector('.htmx-request')"
|
||||
```
|
||||
|
||||
(htmx adds the `.htmx-request` class to elements during in-flight requests.)
|
||||
|
||||
### CDP screenshot timeouts
|
||||
|
||||
`agent-browser screenshot` occasionally returns `CDP command timed out: Page.captureScreenshot`. This is a Chromium/CDP issue, not application code. Workarounds:
|
||||
|
||||
- Don't rely on screenshots for state verification. Read state via `agent-browser eval` directly.
|
||||
- If you need an image, retry once after a small wait.
|
||||
|
||||
### Reading Alpine state for diagnostics
|
||||
|
||||
Useful one-liners when debugging modal state:
|
||||
|
||||
```bash
|
||||
agent-browser eval --stdin <<'EOF'
|
||||
(()=>{
|
||||
const h = document.querySelector('#modal-holder');
|
||||
const c = document.querySelector('#modal-content');
|
||||
const inner = c?.parentElement;
|
||||
return {
|
||||
open: h?._x_dataStack?.[0]?.open,
|
||||
unexpectedError: h?._x_dataStack?.[0]?.unexpectedError,
|
||||
contentChildren: c?.children.length,
|
||||
innerDisplay: inner ? getComputedStyle(inner).display : null,
|
||||
innerOpacity: inner ? getComputedStyle(inner).opacity : null,
|
||||
hxRequest: !!document.querySelector('.htmx-request')
|
||||
};
|
||||
})()
|
||||
EOF
|
||||
```
|
||||
|
||||
## Patterns that improve reliability
|
||||
|
||||
When adding new interactive components:
|
||||
|
||||
- **Every `<button>` inside a form must declare `:type`**. Default to `"button"` for icon/utility buttons; only the actual submit needs `"submit"`. Either the component helper sets it or the call site does — never rely on the HTML default inside a form.
|
||||
- **Don't dispatch `modalclose` and `modalopen` in the same tick.** They share `open` state and the result depends on order. If a flow needs to swap modals, use `modal-replace-response` (which sets `hx-trigger: modalswap` — see `src/clj/auto_ap/ssr/utils.clj:51`) so the swap goes through the `@modalswap.document` handler that explicitly sequences with `$nextTick`.
|
||||
- **Prefer waiting on observable DOM/state over fixed delays.** `wait --fn` with an Alpine state check is faster and more reliable than `wait 500`.
|
||||
125
CLAUDE.md
125
CLAUDE.md
@@ -1,125 +1,2 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Integreat is a full-stack web application for accounts payable (AP) and accounting automation. It integrates with multiple financial data sources (Plaid, Yodlee, Square, Intuit QBO) to manage invoices, bank accounts, transactions, and vendor information.
|
||||
|
||||
**Tech Stack:**
|
||||
- Backend: Clojure 1.10.1 with Ring (Jetty), Datomic database, GraphQL (Lacinia, in process of depracation)
|
||||
- Frontend, two versions:
|
||||
* ClojureScript with Reagent, Re-frame, (almost depracated)
|
||||
* Server side: HTMX, TailwindCSS, alpinejs (current)
|
||||
- Java: Amazon Corretto 11 (required for Clojure 1.10.1)
|
||||
|
||||
## Development Commands
|
||||
|
||||
**Build:**
|
||||
```bash
|
||||
lein build # Create uberjar
|
||||
lein uberjar # Standalone JAR
|
||||
npm install # Install Node.js dependencies (for frontend build)
|
||||
```
|
||||
|
||||
### Development
|
||||
```bash
|
||||
docker-compose up -d # Start Datomic, Solr services
|
||||
lein repl # Start Clojure REPL (nREPL on port 9000), typically one will be running for you already
|
||||
lein cljfmt check # Check code formatting
|
||||
lein cljfmt fix # Auto-format code
|
||||
clj-paren-repair [FILE ...] # fix parentheses in files
|
||||
clj-nrepl-eval -p PORT "(+ 1 2 3)" # evaluate clojure code
|
||||
|
||||
```
|
||||
|
||||
Often times, if a file won't compile, first clj-paren-repair on the file, then try again. If it doesn't wor still, try cljfmt check.
|
||||
|
||||
|
||||
**Running the Application:**
|
||||
|
||||
**As Web Server:**
|
||||
```bash
|
||||
INTEGREAT_JOB="" lein run # Default: port 3000
|
||||
# Or with custom port:
|
||||
PORT=3449 lein run
|
||||
```
|
||||
|
||||
**As Background Job:**
|
||||
Set `INTEGREAT_JOB` environment variable to one of:
|
||||
- `square-import-job` - Square POS transaction sync
|
||||
- `yodlee2` - Yodlee bank account sync
|
||||
- `plaid` - Plaid bank linking
|
||||
- `intuit` - Intuit QBO sync
|
||||
- `import-uploaded-invoices` - Process uploaded invoice PDFs
|
||||
- `ezcater-upsert` - EZcater PO sync
|
||||
- `ledger_reconcile` - Ledger reconciliation
|
||||
- `bulk_journal_import` - Journal entry import
|
||||
- (no job) - Run web server + nREPL
|
||||
|
||||
## Architecture
|
||||
|
||||
**Request Flow:**
|
||||
1. Ring middleware pipeline processes requests
|
||||
2. Authentication/authorization middleware (Buddy) wraps handlers
|
||||
3. Bidi routes dispatch to handlers
|
||||
4. SSR (server-side rendering) generates HTML with Hiccup for main views
|
||||
5. For interactive pages, HTMX handles partial updates
|
||||
6. Client-side uses alpinejs as a bonus
|
||||
|
||||
**Multi-tenancy:**
|
||||
- Client-based filtering via `:client/code` and `:client/groups`
|
||||
- Client selection via `X-Clients` header or session
|
||||
- Role-based permissions: admin, standard user, vendor
|
||||
|
||||
**Key Directories:**
|
||||
- `src/clj/auto_ap/` - Backend Clojure code
|
||||
- `src/clj/auto_ap/server.clj` - Main entry point, job dispatcher, Mount lifecycle
|
||||
- `src/clj/auto_ap/handler.clj` - Ring app, middleware stack
|
||||
- `src/clj/auto_ap/datomic/` - Datomic schema and queries
|
||||
- `src/clj/auto_ap/ssr/` - Server-side rendered page handlers (Hiccup templates)
|
||||
- `src/clj/auto_ap/routes/` - HTTP route definitions
|
||||
- `src/clj/auto_ap/jobs/` - Background batch jobs
|
||||
- `src/clj/auto_ap/graphql/` - GraphQL type definitions and resolvers
|
||||
- `src/cljs/auto_ap/` - Frontend ClojureScript for old, depracated version
|
||||
- `test/clj/auto_ap/` - Unit/integration tests
|
||||
|
||||
## Database
|
||||
|
||||
- Datomic schema defined in `resources/schema.edn`
|
||||
- Key entity patterns:
|
||||
- `:client/code`, `:client/groups` for multi-tenancy
|
||||
- `:vendor/*`, `:invoice/*`, `:transaction/*`, `:account/*` for standard entities
|
||||
- `:db/type/ref` for relationships, many with `:db/cardinality :db.cardinality/many`
|
||||
|
||||
## Configuration
|
||||
|
||||
- Dev config: `config/dev.edn` (set via `-Dconfig=config/dev.edn`)
|
||||
- Env vars: `INTEGREAT_JOB`, `PORT`
|
||||
- Docker: Uses Alpine-based Amazon Corretto 11 image
|
||||
|
||||
## Important Patterns
|
||||
|
||||
- **Middleware stack** in `handler.clj`: route matching → logging → client hydration → session/auth → idle timeout → error handling → gzip
|
||||
- **Client context** added by middleware: `:identity`, `:clients`, `:client`, `:matched-route`
|
||||
- **Job dispatching** in `server.clj`: checks `INTEGREAT_JOB` env var to run specific background jobs or start web server
|
||||
- **Test selectors**: namespaces ending in `integration` or `functional` are selected by `lein test :integration` / `lein test :functional`
|
||||
|
||||
## Clojure REPL Evaluation
|
||||
|
||||
The command `clj-nrepl-eval` is installed on your path for evaluating Clojure code via nREPL.
|
||||
|
||||
**Discover nREPL servers:**
|
||||
|
||||
`clj-nrepl-eval --discover-ports`
|
||||
|
||||
**Evaluate code:**
|
||||
|
||||
`clj-nrepl-eval -p <port> "<clojure-code>"`
|
||||
|
||||
With timeout (milliseconds)
|
||||
|
||||
`clj-nrepl-eval -p <port> --timeout 5000 "<clojure-code>"`
|
||||
|
||||
The REPL session persists between evaluations - namespaces and state are maintained.
|
||||
Always use `:reload` when requiring namespaces to pick up changes.
|
||||
@AGENTS.md
|
||||
|
||||
106
config/core.clj
Normal file
106
config/core.clj
Normal file
@@ -0,0 +1,106 @@
|
||||
(ns config.core
|
||||
(:require [clojure.java.io :as io]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.string :as s]
|
||||
[clojure.tools.logging :as log])
|
||||
(:import java.io.PushbackReader))
|
||||
|
||||
(defn parse-number [^String v]
|
||||
(try
|
||||
(Long/parseLong v)
|
||||
(catch NumberFormatException _
|
||||
(BigInteger. v))))
|
||||
|
||||
;originally found in cprop https://github.com/tolitius/cprop/blob/6963f8e04fd093744555f990c93747e0e5889395/src/cprop/source.cljc#L26
|
||||
(defn str->value
|
||||
"ENV vars and system properties are strings. str->value will convert:
|
||||
the numbers to longs, the alphanumeric values to strings, and will use Clojure reader for the rest
|
||||
in case reader can't read OR it reads a symbol, the value will be returned as is (a string)"
|
||||
[v]
|
||||
(cond
|
||||
(re-matches #"[0-9]+" v) (parse-number v)
|
||||
(re-matches #"^(true|false)$" v) (Boolean/parseBoolean v)
|
||||
(re-matches #"\w+" v) v
|
||||
:else
|
||||
(try
|
||||
(let [parsed (edn/read-string v)]
|
||||
(if (symbol? parsed) v parsed))
|
||||
(catch Throwable _ v))))
|
||||
|
||||
(defn keywordize [s]
|
||||
(-> (s/lower-case s)
|
||||
(s/replace "_" "-")
|
||||
(s/replace "." "-")
|
||||
(keyword)))
|
||||
|
||||
(defn read-system-env []
|
||||
(->> (System/getenv)
|
||||
(map (fn [[k v]] [(keywordize k) (str->value v)]))
|
||||
(into {})))
|
||||
|
||||
(defn read-system-props []
|
||||
(->> (System/getProperties)
|
||||
(map (fn [[k v]] [(keywordize k) (str->value v)]))
|
||||
(into {})))
|
||||
|
||||
(defn read-env-file [f]
|
||||
(try
|
||||
(when-let [env-file (io/file f)]
|
||||
(when (.exists env-file)
|
||||
(edn/read-string (slurp env-file))))
|
||||
(catch Exception e
|
||||
(log/warn (str "WARNING: failed to parse " f " " (.getLocalizedMessage e))))))
|
||||
|
||||
(defn read-config-file [f]
|
||||
(try
|
||||
(when-let [url (or (io/resource f) (io/file f))]
|
||||
(with-open [r (-> url io/reader PushbackReader.)]
|
||||
(edn/read r)))
|
||||
(catch java.io.FileNotFoundException _)
|
||||
(catch Exception e
|
||||
(log/warn (str "failed to parse " f " " (.getLocalizedMessage e))))))
|
||||
|
||||
(defn contains-in?
|
||||
"checks whether the nested key exists in a map"
|
||||
[m k-path]
|
||||
(let [one-before (get-in m (drop-last k-path))]
|
||||
(when (map? one-before) ;; in case k-path is "longer" than a map: {:a {:b {:c 42}}} => [:a :b :c :d]
|
||||
(contains? one-before (last k-path)))))
|
||||
|
||||
;; author of "deep-merge-with" is Chris Houser: https://github.com/clojure/clojure-contrib/commit/19613025d233b5f445b1dd3460c4128f39218741
|
||||
(defn deep-merge-with
|
||||
"Like merge-with, but merges maps recursively, appling the given fn
|
||||
only when there's a non-map at a particular level.
|
||||
(deepmerge + {:a {:b {:c 1 :d {:x 1 :y 2}} :e 3} :f 4}
|
||||
{:a {:b {:c 2 :d {:z 9} :z 3} :e 100}})
|
||||
-> {:a {:b {:z 3, :c 3, :d {:z 9, :x 1, :y 2}}, :e 103}, :f 4}"
|
||||
[f & maps]
|
||||
(apply
|
||||
(fn m [& maps]
|
||||
(if (every? map? maps)
|
||||
(apply merge-with m maps)
|
||||
(apply f maps)))
|
||||
(remove nil? maps)))
|
||||
|
||||
(defn merge-maps [& m]
|
||||
(reduce #(deep-merge-with (fn [_ v] v) %1 %2) m))
|
||||
|
||||
(defn load-env
|
||||
"Generate a map of environment variables."
|
||||
[& configs]
|
||||
(let [env-props (merge-maps (read-system-env) (read-system-props))]
|
||||
(apply
|
||||
merge-maps
|
||||
(read-config-file "config.edn")
|
||||
(read-env-file ".lein-env")
|
||||
(read-env-file (io/resource ".boot-env"))
|
||||
(read-env-file (:config env-props))
|
||||
env-props
|
||||
configs)))
|
||||
|
||||
(defonce
|
||||
^{:doc "A map of environment variables."}
|
||||
env (load-env))
|
||||
|
||||
(defn reload-env []
|
||||
(alter-var-root #'env (fn [_] (load-env))))
|
||||
@@ -11,7 +11,7 @@
|
||||
:requests-queue-url "https://sqs.us-east-1.amazonaws.com/679918342773/integreat-background-request-prod"
|
||||
:invoice-email "invoices@mail.app.integreatconsult.com"
|
||||
:import-failure-destination-email "ben@integreatconsult.com"
|
||||
:data-bucket "data.app-new.app.integreatconsult.com"
|
||||
:data-bucket "data.prod.app.integreatconsult.com"
|
||||
:yodlee-cobrand-name "qstartus12"
|
||||
:yodlee-cobrand-login "qstartus12"
|
||||
:yodlee-cobrand-password "MPD@mg78hd"
|
||||
|
||||
298
docs/superpowers/plans/2025-01-15-wizard-phase-i-trial.md
Normal file
298
docs/superpowers/plans/2025-01-15-wizard-phase-i-trial.md
Normal file
@@ -0,0 +1,298 @@
|
||||
# Phase I Trial: Django-Formtools Style Wizard (Server-Side Storage)
|
||||
|
||||
## Goal
|
||||
Create a **copy** of one existing wizard using Approach B (server-side session storage) without modifying any existing multi_modal.clj code. This is an isolated trial to validate the pattern.
|
||||
|
||||
## Trial Subject: Transaction Bulk Code Wizard
|
||||
**Why this one:**
|
||||
- Single-step form (simplest case)
|
||||
- Currently uses wizard protocols unnecessarily
|
||||
- ~420 lines in `transaction/bulk_code.clj`
|
||||
- Well-contained with clear inputs/outputs
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Browser Server
|
||||
| |
|
||||
|-- GET /bulk-code-trial -->|
|
||||
| |-- Create session entry
|
||||
| |-- Return form with wizard-id
|
||||
|<-- HTML + wizard-id -----|
|
||||
| |
|
||||
|-- POST (wizard-id + -->|
|
||||
| current-step data) |-- Validate step
|
||||
| |-- Store in session
|
||||
| |-- Return next step or done
|
||||
|<-- HTML -----------------|
|
||||
```
|
||||
|
||||
**Key difference from current approach:**
|
||||
- Current: EDN snapshot serialized in hidden form fields
|
||||
- Trial: Only `wizard-id` and current step fields in form. State lives server-side in atom.
|
||||
|
||||
---
|
||||
|
||||
## Files (All New - No Existing Files Modified)
|
||||
|
||||
```
|
||||
src/clj/auto_ap/ssr/components/wizard_trial/
|
||||
state.clj - Session storage backend
|
||||
core.clj - Trial wizard engine (minimal)
|
||||
|
||||
src/clj/auto_ap/ssr/transaction/
|
||||
bulk_code_trial.clj - Trial implementation of bulk code
|
||||
|
||||
test/clj/auto_ap/ssr/transaction/
|
||||
bulk_code_trial_test.clj - Tests for trial
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase I: Trial Implementation (This Week)
|
||||
|
||||
### Step 1: Create Session Storage
|
||||
|
||||
**File:** `src/clj/auto_ap/ssr/components/wizard_trial/state.clj`
|
||||
|
||||
```clojure
|
||||
(ns auto-ap.ssr.components.wizard-trial.state)
|
||||
|
||||
(defonce ^:private store (atom {}))
|
||||
|
||||
(defn create!
|
||||
"Creates new wizard session. Returns wizard-id."
|
||||
[initial-data]
|
||||
(let [id (str (java.util.UUID/randomUUID))]
|
||||
(swap! store assoc id {:data initial-data
|
||||
:created-at (java.util.Date.)})
|
||||
id))
|
||||
|
||||
(defn get-wizard
|
||||
"Retrieves wizard data by id."
|
||||
[id]
|
||||
(get @store id))
|
||||
|
||||
(defn update-step!
|
||||
"Merges step data into wizard session."
|
||||
[id step-key step-data]
|
||||
(swap! store assoc-in [id :data step-key] step-data))
|
||||
|
||||
(defn get-all-data
|
||||
"Returns merged data from all steps."
|
||||
[id]
|
||||
(-> (get-wizard id)
|
||||
:data
|
||||
vals
|
||||
(apply merge)))
|
||||
|
||||
(defn destroy!
|
||||
"Removes wizard session."
|
||||
[id]
|
||||
(swap! store dissoc id))
|
||||
```
|
||||
|
||||
### Step 2: Create Minimal Wizard Engine
|
||||
|
||||
**File:** `src/clj/auto_ap/ssr/components/wizard_trial/core.clj`
|
||||
|
||||
```clojure
|
||||
(ns auto-ap.ssr.components.wizard-trial.core
|
||||
(:require [auto-ap.ssr.components.wizard-trial.state :as ws]
|
||||
[malli.core :as mc]))
|
||||
|
||||
(defn render-step
|
||||
"Renders a single step form."
|
||||
[{:keys [wizard-id step-config request]}]
|
||||
(let [step-data (get-in (ws/get-wizard wizard-id) [:data (:key step-config)])]
|
||||
[:form {:hx-post (:submit-route step-config)
|
||||
:hx-target "this"
|
||||
:hx-swap "outerHTML"}
|
||||
[:input {:type "hidden" :name "wizard-id" :value wizard-id}]
|
||||
[:input {:type "hidden" :name "step-key" :value (name (:key step-config))}]
|
||||
((:render step-config) (assoc request :step-data step-data))]))
|
||||
|
||||
(defn handle-submit
|
||||
"Handles step submission."
|
||||
[step-config request]
|
||||
(let [{:keys [wizard-id step-key]} (:form-params request)
|
||||
wizard-id (str wizard-id)
|
||||
step-key (keyword step-key)
|
||||
step-data (select-keys (:form-params request) (:fields step-config))]
|
||||
|
||||
(if-let [errors (mc/explain (:schema step-config) step-data)]
|
||||
;; Validation failed - re-render with errors
|
||||
(render-step {:wizard-id wizard-id
|
||||
:step-config step-config
|
||||
:request (assoc request :errors errors)})
|
||||
|
||||
;; Success - save and done (single step for trial)
|
||||
(let [all-data (ws/get-all-data wizard-id)]
|
||||
(ws/destroy! wizard-id)
|
||||
((:done-fn step-config) all-data request)))))
|
||||
```
|
||||
|
||||
### Step 3: Create Trial Bulk Code Form
|
||||
|
||||
**File:** `src/clj/auto_ap/ssr/transaction/bulk_code_trial.clj`
|
||||
|
||||
```clojure
|
||||
(ns auto-ap.ssr.transaction.bulk-code-trial
|
||||
(:require [auto-ap.ssr.components.wizard-trial.core :as wt]
|
||||
[auto-ap.ssr.components.wizard-trial.state :as ws]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[auto-ap.datomic :refer [conn pull-attr]]
|
||||
[datomic.api :as dc]))
|
||||
|
||||
(def bulk-code-schema
|
||||
[:map
|
||||
[:vendor {:optional true} [:maybe int?]]
|
||||
[:approval-status {:optional true} [:maybe keyword?]]
|
||||
[:accounts {:optional true}
|
||||
[:vector {:coerce? true}
|
||||
[:map
|
||||
[:account int?]
|
||||
[:location :string]
|
||||
[:percentage :double]]]]]])
|
||||
|
||||
(defn render-bulk-code-form
|
||||
"Pure function - renders the form given data."
|
||||
[{:keys [step-data errors]}]
|
||||
[:div.bulk-code-trial
|
||||
[:h2 "Bulk Code (Trial - Phase I)"]
|
||||
|
||||
[:div.space-y-4
|
||||
;; Vendor field
|
||||
[:div
|
||||
[:label "Vendor"]
|
||||
(com/typeahead {:name "vendor"
|
||||
:value (:vendor step-data)
|
||||
:url "/api/vendors/search"})]
|
||||
|
||||
;; Accounts table
|
||||
[:div
|
||||
[:h3 "Accounts"]
|
||||
[:table
|
||||
[:thead
|
||||
[:tr
|
||||
[:th "Account"]
|
||||
[:th "Location"]
|
||||
[:th "%"]]]
|
||||
[:tbody
|
||||
(for [[idx account] (map-indexed vector (:accounts step-data []))]
|
||||
[:tr {:key idx}
|
||||
[:td (com/typeahead {:name (str "accounts[" idx "][account]")
|
||||
:value (:account account)})]
|
||||
[:td (com/text-input {:name (str "accounts[" idx "][location]")
|
||||
:value (:location account)})]
|
||||
[:td (com/money-input {:name (str "accounts[" idx "][percentage]")
|
||||
:value (:percentage account)})]])]]]
|
||||
|
||||
;; Submit
|
||||
[:button {:type "submit"} "Apply Bulk Code"]]])
|
||||
|
||||
(def trial-step-config
|
||||
{:key :bulk-code
|
||||
:schema bulk-code-schema
|
||||
:fields [:vendor :approval-status :accounts]
|
||||
:render render-bulk-code-form
|
||||
:submit-route "/transaction/bulk-code-trial"
|
||||
:done-fn (fn [data request]
|
||||
;; Apply bulk coding logic here
|
||||
(html-response [:div.success "Bulk code applied! (Trial)"]))})
|
||||
|
||||
;; Route handlers
|
||||
(defn open-trial [request]
|
||||
(let [wizard-id (ws/create! {:accounts []})]
|
||||
(wt/render-step {:wizard-id wizard-id
|
||||
:step-config trial-step-config
|
||||
:request request})))
|
||||
|
||||
(defn submit-trial [request]
|
||||
(wt/handle-submit trial-step-config request))
|
||||
```
|
||||
|
||||
### Step 4: Add Routes
|
||||
|
||||
In your routes file (new entries, don't modify existing):
|
||||
|
||||
```clojure
|
||||
{::route/bulk-code-trial open-trial
|
||||
::route/bulk-code-trial-submit submit-trial}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase II: Expand Trial (If Phase I Works)
|
||||
|
||||
**Goal:** Test with a multi-step wizard
|
||||
|
||||
**Subject:** New Invoice Wizard (2-3 steps)
|
||||
- Step 1: Basic details
|
||||
- Step 2: Accounts (conditional)
|
||||
- Step 3: Submit
|
||||
|
||||
**New additions to trial engine:**
|
||||
- Step navigation (next/prev)
|
||||
- Conditional steps (skip accounts if not customizing)
|
||||
- Step validation per-step
|
||||
- Progress indicator
|
||||
|
||||
**Files to create:**
|
||||
```
|
||||
src/clj/auto_ap/ssr/invoice/
|
||||
new_invoice_trial.clj
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase III: Full Migration Decision (If Phase II Works)
|
||||
|
||||
**Goal:** Decide whether to migrate all wizards or keep both systems
|
||||
|
||||
**Evaluation criteria:**
|
||||
1. ✅ Line count reduction (target: 50%+)
|
||||
2. ✅ Testability (pure functions easier to test)
|
||||
3. ✅ Performance (server-side storage vs EDN serialization)
|
||||
4. ✅ Complexity (fewer protocols/middleware)
|
||||
5. ⚠️ Session handling (what happens on server restart?)
|
||||
6. ⚠️ Multiple tabs (can user have two wizards open?)
|
||||
|
||||
**Decision matrix:**
|
||||
|
||||
| Criteria | Current | Trial | Winner |
|
||||
|----------|---------|-------|--------|
|
||||
| Lines of code | ~8,200 | ~3,000 (est.) | Trial |
|
||||
| Server restarts | Survives (state in form) | Loses state | Current |
|
||||
| Multiple tabs | Works (independent forms) | Needs separate IDs | Tie |
|
||||
| Testability | Hard (cursor context) | Easy (pure functions) | Trial |
|
||||
| Complex merges | Painful | Simple (keyed steps) | Trial |
|
||||
|
||||
**If trial wins:** Migrate all wizards using Phase II pattern
|
||||
**If mixed:** Use trial for simple forms, keep current for complex multi-step
|
||||
|
||||
---
|
||||
|
||||
## How to Run the Trial
|
||||
|
||||
1. **Start server:** `lein run`
|
||||
2. **Navigate to:** `/transaction/bulk-code-trial`
|
||||
3. **Test:** Fill form, submit, verify state handling
|
||||
4. **Compare:** Open existing `/transaction/bulk-code` in another tab
|
||||
5. **Evaluate:** Which feels simpler? Which is easier to debug?
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria for Phase I
|
||||
|
||||
- [ ] Trial renders without errors
|
||||
- [ ] Form submission validates correctly
|
||||
- [ ] Server-side state persists across requests
|
||||
- [ ] No modifications to existing multi_modal.clj
|
||||
- [ ] Code is < 200 lines (vs 420 original)
|
||||
- [ ] Developer can understand flow in 5 minutes
|
||||
|
||||
**Ready to implement Phase I?**
|
||||
1283
docs/superpowers/plans/2025-01-15-wizard-refactor.md
Normal file
1283
docs/superpowers/plans/2025-01-15-wizard-refactor.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,203 @@
|
||||
# Memo and Description Filters Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add a new memo filter and enhance the existing description filter to use case-insensitive regex matching on the transaction page.
|
||||
|
||||
**Architecture:** Modify the existing query schema, filter UI, and query logic in `src/clj/auto_ap/ssr/transaction/common.clj`. Both filters convert user input to regex patterns with `(?i)` flag and use Datomic's `re-find`.
|
||||
|
||||
**Tech Stack:** Clojure, Datomic, Hiccup, Malli schema validation
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- **Modify:** `src/clj/auto_ap/ssr/transaction/common.clj` — add memo to query schema, add memo filter UI, update description filter to use regex, add memo filter to query logic
|
||||
- **Test:** `test/clj/auto_ap/ssr/transaction/common_test.clj` — create new test file for filter logic (or add to existing transaction tests)
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Update Query Schema
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/clj/auto_ap/ssr/transaction/common.clj:42`
|
||||
|
||||
- [ ] **Step 1: Add `:memo` to query-schema**
|
||||
|
||||
Add after the `:description` field in the query-schema map:
|
||||
|
||||
```clojure
|
||||
[:memo {:optional true} [:maybe [:string {:decode/string strip}]]]
|
||||
```
|
||||
|
||||
It should be placed after `:description` (line 42) and before `:vendor` (line 43).
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Update Description Filter Query Logic
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/clj/auto_ap/ssr/transaction/common.clj:140-145`
|
||||
|
||||
- [ ] **Step 1: Change description filter from `.contains` to `re-find`**
|
||||
|
||||
Replace the description filter block (lines 140-145):
|
||||
|
||||
```clojure
|
||||
(seq (:description args))
|
||||
(merge-query {:query {:in ['?description]
|
||||
:where ['[?e :transaction/description-original ?do]
|
||||
'[(clojure.string/lower-case ?do) ?do2]
|
||||
'[(.contains ?do2 ?description)]]}
|
||||
:args [(str/lower-case (:description args))]})
|
||||
```
|
||||
|
||||
With:
|
||||
|
||||
```clojure
|
||||
(seq (:description args))
|
||||
(merge-query {:query {:in ['?description-regex]
|
||||
:where ['[?e :transaction/description-original ?do]
|
||||
'[(re-find ?description-regex ?do)]]}
|
||||
:args [(re-pattern (str "(?i).*" (str/lower-case (:description args)) ".*"))]})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Add Memo Filter Query Logic
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/clj/auto_ap/ssr/transaction/common.clj` (after description filter block)
|
||||
|
||||
- [ ] **Step 1: Add memo filter condition in fetch-ids cond-> chain**
|
||||
|
||||
Add after the description filter block (around line 145) and before the amount-gte filter:
|
||||
|
||||
```clojure
|
||||
(seq (:memo args))
|
||||
(merge-query {:query {:in ['?memo-regex]
|
||||
:where ['[?e :transaction/memo ?memo]
|
||||
'[(re-find ?memo-regex ?memo)]]}
|
||||
:args [(re-pattern (str "(?i).*" (str/lower-case (:memo args)) ".*"))]})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Add Memo Filter UI
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/clj/auto_ap/ssr/transaction/common.clj:340-355` (around the description filter UI)
|
||||
|
||||
- [ ] **Step 1: Add memo filter input in the filters function**
|
||||
|
||||
Add after the description filter input (lines 340-346) and before the location filter (lines 348-354):
|
||||
|
||||
```clojure
|
||||
(com/field {:label "Memo"}
|
||||
(com/text-input {:name "memo"
|
||||
:id "memo"
|
||||
:class "hot-filter"
|
||||
:value (:memo (:query-params request))
|
||||
:placeholder "e.g., Rent"
|
||||
:size :small}))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Write Tests
|
||||
|
||||
**Files:**
|
||||
- Create: `test/clj/auto_ap/ssr/transaction/common_test.clj`
|
||||
|
||||
- [ ] **Step 1: Create test file for filter logic**
|
||||
|
||||
```clojure
|
||||
(ns auto-ap.ssr.transaction.common-test
|
||||
(:require
|
||||
[auto-ap.ssr.transaction.common :as sut]
|
||||
[clojure.test :as t :refer [deftest is testing use-fixtures]]))
|
||||
|
||||
(deftest description-filter-regex-pattern
|
||||
(testing "Description filter creates correct regex pattern"
|
||||
(let [pattern (re-pattern (str "(?i).*" "Groceries" ".*"))]
|
||||
(is (re-find pattern "My Groceries Store"))
|
||||
(is (re-find pattern "GROCERIES"))
|
||||
(is (re-find pattern "groceries shop"))
|
||||
(is (not (re-find pattern "Restaurant"))))))
|
||||
|
||||
(deftest memo-filter-regex-pattern
|
||||
(testing "Memo filter creates correct regex pattern"
|
||||
(let [pattern (re-pattern (str "(?i).*" "Rent" ".*"))]
|
||||
(is (re-find pattern "Monthly Rent"))
|
||||
(is (re-find pattern "RENT"))
|
||||
(is (re-find pattern "rent payment"))
|
||||
(is (not (re-find pattern "Utilities"))))))
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests to verify they pass**
|
||||
|
||||
Run: `clj-nrepl-eval -p PORT "(clojure.test/run-tests 'auto-ap.ssr.transaction.common-test)"`
|
||||
|
||||
Expected: All tests pass
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Verify Changes
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/clj/auto_ap/ssr/transaction/common.clj`
|
||||
|
||||
- [ ] **Step 1: Check that both filters work correctly**
|
||||
|
||||
1. Start the application: `INTEGREAT_JOB="" lein run`
|
||||
2. Navigate to the transaction page
|
||||
3. Test description filter:
|
||||
- Enter "gro" in description filter
|
||||
- Should show transactions with descriptions containing "gro" (case-insensitive)
|
||||
4. Test memo filter:
|
||||
- Enter "rent" in memo filter
|
||||
- Should show transactions with memos containing "rent" (case-insensitive)
|
||||
5. Test combined filters:
|
||||
- Use both description and memo filters together
|
||||
- Should show only transactions matching both criteria
|
||||
|
||||
- [ ] **Step 2: Commit changes**
|
||||
|
||||
```bash
|
||||
git add src/clj/auto_ap/ssr/transaction/common.clj
|
||||
git add test/clj/auto_ap/ssr/transaction/common_test.clj
|
||||
git commit -m "feat: add memo filter and enhance description filter with regex matching"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Self-Review
|
||||
|
||||
**Spec coverage check:**
|
||||
- ✅ New memo filter added to query schema (Task 1)
|
||||
- ✅ Memo filter uses `re-find` with `(?i)` flag (Task 3)
|
||||
- ✅ Description filter enhanced to use `re-find` (Task 2)
|
||||
- ✅ Both filters wrap input with `.*` on both ends
|
||||
- ✅ Memo filter placed after description filter in query logic (Task 3)
|
||||
- ✅ Memo filter UI added to filter sidebar (Task 4)
|
||||
- ✅ Tests written for regex patterns (Task 5)
|
||||
|
||||
**Placeholder scan:**
|
||||
- No TBDs, TODOs, or placeholder text found
|
||||
- All code blocks contain actual implementation code
|
||||
|
||||
**Type consistency:**
|
||||
- `:memo` field uses same schema structure as `:description`
|
||||
- Both filters use `re-pattern` with `(?i)` flag consistently
|
||||
|
||||
## Execution Handoff
|
||||
|
||||
Plan complete and saved to `docs/superpowers/plans/2026-05-26-memo-description-filter-plan.md`.
|
||||
|
||||
**Two execution options:**
|
||||
|
||||
**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration
|
||||
|
||||
**2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints
|
||||
|
||||
Which approach?
|
||||
@@ -0,0 +1,601 @@
|
||||
# Transaction Edit Modal: Simple / Advanced Mode Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Replace the always-visible account split table in the transaction edit modal with a simple mode (single account + location fields) that is the default for uncoded or single-account transactions, with a toggle to the full advanced split table.
|
||||
|
||||
**Architecture:** HTMX-driven server-side swap. A new `edit-wizard-toggle-mode` GET endpoint re-renders the manual coding section in the requested mode. Mode is carried via a hidden `<input name="mode">` field included in all HTMX requests. `edit-vendor-changed` is updated to branch on mode. The `LinksStep` render function selects initial mode based on account row count.
|
||||
|
||||
**Tech Stack:** Clojure/Hiccup server-side rendering, HTMX, Alpine.js, Bidi routing, Datomic
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-05-27-transaction-edit-simple-advanced-mode-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
| File | What changes |
|
||||
|------|-------------|
|
||||
| `src/cljc/auto_ap/routes/transactions.cljc` | Add `::edit-wizard-toggle-mode` route |
|
||||
| `src/clj/auto_ap/ssr/transaction/edit.clj` | Add `simple-mode-fields*`, `manual-coding-section*`, `edit-wizard-toggle-mode-handler`; update `LinksStep` render; update `edit-vendor-changed-handler`; register new route handler |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Add the toggle-mode route
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/cljc/auto_ap/routes/transactions.cljc`
|
||||
|
||||
- [ ] **Step 1: Add the route entry**
|
||||
|
||||
Open `src/cljc/auto_ap/routes/transactions.cljc`. After the `"/edit-wizard-new-account"` line, add:
|
||||
|
||||
```clojure
|
||||
"/edit-wizard-toggle-mode" ::edit-wizard-toggle-mode
|
||||
```
|
||||
|
||||
The file should look like:
|
||||
|
||||
```clojure
|
||||
"/edit-wizard-new-account" ::edit-wizard-new-account
|
||||
"/edit-wizard-toggle-mode" ::edit-wizard-toggle-mode
|
||||
"/match-payment" ::link-payment
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the file compiles**
|
||||
|
||||
```bash
|
||||
clj-nrepl-eval -p 9000 "(require '[auto-ap.routes.transactions] :reload)"
|
||||
```
|
||||
|
||||
Expected: no errors.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/cljc/auto_ap/routes/transactions.cljc
|
||||
git commit -m "feat: add edit-wizard-toggle-mode route"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Add `simple-mode-fields*` — the simple-mode account/location UI
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/clj/auto_ap/ssr/transaction/edit.clj`
|
||||
|
||||
This function renders the account typeahead + location select + toggle link for simple mode. It goes near the existing `account-typeahead*` and `location-select*` helpers (around line 180).
|
||||
|
||||
- [ ] **Step 1: Add `simple-mode-fields*` after `account-typeahead*` (around line 180)**
|
||||
|
||||
```clojure
|
||||
(defn simple-mode-fields*
|
||||
"Renders the simple-mode account + location row and the toggle link.
|
||||
request must have :multi-form-state and :entity bound."
|
||||
[request]
|
||||
(let [snapshot (-> request :multi-form-state :snapshot)
|
||||
client-id (or (-> request :entity :transaction/client :db/id)
|
||||
(:transaction/client snapshot))
|
||||
existing-row (first (:transaction/accounts snapshot))
|
||||
account-val (:transaction-account/account existing-row)
|
||||
location-val (or (:transaction-account/location existing-row) "Shared")
|
||||
account-id (when (nat-int? account-val)
|
||||
(dc/pull (dc/db conn) '[:account/location] account-val))
|
||||
row-id (or (:db/id existing-row) (str (java.util.UUID/randomUUID)))]
|
||||
[:div
|
||||
;; hidden inputs to encode the single row as transaction/accounts[0]
|
||||
(fc/with-field :transaction/accounts
|
||||
(fc/with-cursor-index 0
|
||||
[:span
|
||||
(fc/with-field :db/id
|
||||
(com/hidden {:name (fc/field-name) :value row-id}))
|
||||
[:div.flex.gap-2.mt-2
|
||||
(fc/with-field :transaction-account/account
|
||||
(com/validated-field
|
||||
{:label "Account" :errors (fc/field-errors)}
|
||||
[:div.w-72
|
||||
(account-typeahead* {:value account-val
|
||||
:client-id client-id
|
||||
:name (fc/field-name)
|
||||
:x-model "simpleAccountId"})]))
|
||||
(fc/with-field :transaction-account/location
|
||||
(com/validated-field
|
||||
{:label "Location"
|
||||
:errors (fc/field-errors)
|
||||
:x-hx-val:account-id "simpleAccountId"
|
||||
:hx-vals (hx/json (cond-> {:name (fc/field-name)}
|
||||
client-id (assoc :client-id client-id)))
|
||||
:x-dispatch:changed "simpleAccountId"
|
||||
:hx-trigger "changed"
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes ::route/location-select)
|
||||
:hx-target "find *"
|
||||
:hx-swap "outerHTML"}
|
||||
(location-select*
|
||||
{:name (fc/field-name)
|
||||
:account-location (:account/location account-id)
|
||||
:client-locations (pull-attr (dc/db conn) :client/locations client-id)
|
||||
:value location-val})))
|
||||
;; hidden amount — full transaction total
|
||||
(fc/with-field :transaction-account/amount
|
||||
(let [total (Math/abs (or (-> request :entity :transaction/amount)
|
||||
(:transaction/amount snapshot)
|
||||
0.0))]
|
||||
(com/hidden {:name (fc/field-name) :value total})))]))
|
||||
;; toggle link
|
||||
[:div.mt-1
|
||||
[:a.text-sm.text-blue-600.hover:underline.cursor-pointer
|
||||
{:hx-get (bidi/path-for ssr-routes/only-routes ::route/edit-wizard-toggle-mode)
|
||||
:hx-include "closest form"
|
||||
:hx-target "#manual-coding-section"
|
||||
:hx-swap "outerHTML"}
|
||||
"Switch to advanced mode"]]]))
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the file has no parse errors**
|
||||
|
||||
```bash
|
||||
clj-nrepl-eval -p 9000 "(require '[auto-ap.ssr.transaction.edit] :reload)"
|
||||
```
|
||||
|
||||
Expected: no errors.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/clj/auto_ap/ssr/transaction/edit.clj
|
||||
git commit -m "feat: add simple-mode-fields* for transaction edit modal"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Extract `manual-coding-section*` and update `LinksStep`
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/clj/auto_ap/ssr/transaction/edit.clj`
|
||||
|
||||
Currently the manual coding block (vendor field + account grid) is inlined inside `LinksStep/render-step`. Extract it into `manual-coding-section*` which selects mode and renders accordingly. This also adds the `mode` hidden input and wraps the section in `#manual-coding-section`.
|
||||
|
||||
- [ ] **Step 1: Add `manual-mode-initial` helper** (determines initial mode from snapshot)
|
||||
|
||||
Add this function after `simple-mode-fields*`:
|
||||
|
||||
```clojure
|
||||
(defn- manual-mode-initial
|
||||
"Returns :simple or :advanced based on existing account row count."
|
||||
[snapshot]
|
||||
(let [rows (seq (:transaction/accounts snapshot))]
|
||||
(if (and rows (> (count rows) 1))
|
||||
:advanced
|
||||
:simple)))
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add `manual-coding-section*`**
|
||||
|
||||
Add after `manual-mode-initial`:
|
||||
|
||||
```clojure
|
||||
(defn manual-coding-section*
|
||||
"Renders the vendor field + account/location section for the manual tab.
|
||||
mode is :simple or :advanced."
|
||||
[mode request]
|
||||
(let [snapshot (-> request :multi-form-state :snapshot)
|
||||
row-count (count (:transaction/accounts snapshot))]
|
||||
[:div#manual-coding-section
|
||||
;; hidden mode input — carried by all hx-include=\"closest form\" calls
|
||||
(com/hidden {:name "mode" :value (name mode)})
|
||||
;; vendor field
|
||||
[:div {:hx-trigger "change"
|
||||
:hx-post (bidi/path-for ssr-routes/only-routes ::route/edit-vendor-changed)
|
||||
:hx-target "#manual-coding-section"
|
||||
:hx-swap "outerHTML"
|
||||
:hx-include "closest form"}
|
||||
(fc/with-field :transaction/vendor
|
||||
(com/validated-field
|
||||
{:label "Vendor" :errors (fc/field-errors)}
|
||||
[:div.w-96
|
||||
(com/typeahead {:name (fc/field-name)
|
||||
:error? (fc/error?)
|
||||
:class "w-96"
|
||||
:placeholder "Search..."
|
||||
:url (bidi/path-for ssr-routes/only-routes :vendor-search)
|
||||
:value (fc/field-value)
|
||||
:content-fn (fn [c] (pull-attr (dc/db conn) :vendor/name c))})]))]
|
||||
;; account/location section
|
||||
(if (= mode :simple)
|
||||
[:div {:x-data (hx/json {:simpleAccountId
|
||||
(-> snapshot :transaction/accounts first
|
||||
:transaction-account/account)})}
|
||||
(fc/start-form (:multi-form-state request) nil
|
||||
(fc/with-field :step-params
|
||||
(simple-mode-fields* request)))]
|
||||
;; advanced mode
|
||||
[:div
|
||||
(when (<= row-count 1)
|
||||
[:div.mb-2
|
||||
[:a.text-sm.text-blue-600.hover:underline.cursor-pointer
|
||||
{:hx-get (bidi/path-for ssr-routes/only-routes ::route/edit-wizard-toggle-mode)
|
||||
:hx-include "closest form"
|
||||
:hx-target "#manual-coding-section"
|
||||
:hx-swap "outerHTML"}
|
||||
"Switch to simple mode"]])
|
||||
(fc/start-form (:multi-form-state request) nil
|
||||
(fc/with-field :step-params
|
||||
(fc/with-field :transaction/accounts
|
||||
[:div#account-grid-body
|
||||
(account-grid-body* request)])))])]))
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Update `LinksStep/render-step` to use `manual-coding-section*`**
|
||||
|
||||
In `LinksStep/render-step` (around line 826), replace the entire `[:div {}` block inside `[:div {:x-show "activeForm === 'manual'" ...}]` (which currently contains the vendor typeahead + approval status + `account-grid-body*`) with:
|
||||
|
||||
```clojure
|
||||
[:div {:x-show "activeForm === 'manual'", :x-transition:enter "transition ease-out duration-500", :x-transition:enter-start "opacity-0 transform scale-95", :x-transition:enter-end "opacity-100 transform scale-100"}
|
||||
[:div {}
|
||||
(manual-coding-section* (manual-mode-initial snapshot) request)
|
||||
;; Approval status field
|
||||
(fc/with-field :transaction/approval-status
|
||||
(com/validated-field
|
||||
{:label "Status"
|
||||
:errors (fc/field-errors)}
|
||||
(let [current-value (name (or (fc/field-value) :transaction-approval-status/unapproved))]
|
||||
[:div {:x-data (hx/json {:approvalStatus current-value})}
|
||||
(com/hidden {:name (fc/field-name)
|
||||
:value current-value
|
||||
":value" "approvalStatus"})
|
||||
[:div {:class "inline-flex rounded-md shadow-sm", :role "group"}
|
||||
(com/button-group-button {"@click" "approvalStatus = 'approved'"
|
||||
":class" "{ '!bg-primary-200 text-primary-800': approvalStatus === 'approved' }"
|
||||
:class "rounded-l-lg"}
|
||||
"Approved")
|
||||
(com/button-group-button {"@click" "approvalStatus = 'unapproved'"
|
||||
":class" "{ '!bg-primary-200 text-primary-800': approvalStatus === 'unapproved' }"
|
||||
:class "rounded-r-lg"}
|
||||
"Unapproved")
|
||||
(com/button-group-button {"@click" "approvalStatus = 'suppressed'"
|
||||
":class" "{ '!bg-primary-200 text-primary-800': approvalStatus === 'suppressed' }"
|
||||
:class "rounded-r-lg"}
|
||||
"Client Review")]]]))]]]
|
||||
```
|
||||
|
||||
Also remove the now-redundant `(fc/with-field :transaction/accounts ...)` wrapper that previously wrapped `account-grid-body*` (it is now handled inside `manual-coding-section*`).
|
||||
|
||||
- [ ] **Step 4: Verify the file compiles**
|
||||
|
||||
```bash
|
||||
clj-nrepl-eval -p 9000 "(require '[auto-ap.ssr.transaction.edit] :reload)"
|
||||
```
|
||||
|
||||
Expected: no errors.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/clj/auto_ap/ssr/transaction/edit.clj
|
||||
git commit -m "feat: extract manual-coding-section* with simple/advanced mode selection"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Add `edit-wizard-toggle-mode-handler`
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/clj/auto_ap/ssr/transaction/edit.clj`
|
||||
|
||||
This handler re-renders `#manual-coding-section` in the opposite mode. It reads `mode` from the form params (via `step-params` in the decoded multi-form-state) and flips it.
|
||||
|
||||
- [ ] **Step 1: Add the handler function** (add after `edit-vendor-changed-handler`):
|
||||
|
||||
```clojure
|
||||
(defn edit-wizard-toggle-mode-handler [request]
|
||||
(let [step-params (-> request :multi-form-state :step-params)
|
||||
current-mode (keyword (or (:mode step-params) "simple"))
|
||||
target-mode (if (= current-mode :simple) :advanced :simple)
|
||||
snapshot (-> request :multi-form-state :snapshot)
|
||||
;; When switching simple→advanced, promote simple-mode values into accounts
|
||||
render-request
|
||||
(if (and (= target-mode :advanced)
|
||||
(= current-mode :simple))
|
||||
;; carry the simple-mode single row into snapshot so the table shows it
|
||||
(let [accounts (or (seq (:transaction/accounts step-params))
|
||||
(seq (:transaction/accounts snapshot)))]
|
||||
(-> request
|
||||
(assoc-in [:multi-form-state :snapshot :transaction/accounts]
|
||||
(vec accounts))
|
||||
(assoc-in [:multi-form-state :step-params :transaction/accounts]
|
||||
(vec accounts))))
|
||||
;; advanced→simple: take first row only
|
||||
(let [first-row (first (or (seq (:transaction/accounts step-params))
|
||||
(seq (:transaction/accounts snapshot))))]
|
||||
(-> request
|
||||
(assoc-in [:multi-form-state :snapshot :transaction/accounts]
|
||||
(if first-row [first-row] []))
|
||||
(assoc-in [:multi-form-state :step-params :transaction/accounts]
|
||||
(if first-row [first-row] [])))))]
|
||||
(html-response
|
||||
(fc/start-form (:multi-form-state render-request) nil
|
||||
(fc/with-field :step-params
|
||||
(manual-coding-section* target-mode render-request))))))
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Register the handler in `key->handler`**
|
||||
|
||||
In the `key->handler` map (around line 1357), add after the `::route/edit-wizard-new-account` entry:
|
||||
|
||||
```clojure
|
||||
::route/edit-wizard-toggle-mode (-> edit-wizard-toggle-mode-handler
|
||||
(mm/wrap-wizard edit-wizard)
|
||||
(wrap-entity [:multi-form-state :snapshot :db/id] d-transactions/default-read)
|
||||
(mm/wrap-decode-multi-form-state))
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify the file compiles**
|
||||
|
||||
```bash
|
||||
clj-nrepl-eval -p 9000 "(require '[auto-ap.ssr.transaction.edit] :reload)"
|
||||
```
|
||||
|
||||
Expected: no errors.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add src/clj/auto_ap/ssr/transaction/edit.clj
|
||||
git commit -m "feat: add edit-wizard-toggle-mode-handler"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Update `edit-vendor-changed-handler` to support both modes
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/clj/auto_ap/ssr/transaction/edit.clj`
|
||||
|
||||
Currently this handler always returns `[:div#account-grid-body ...]`. It must now return `#manual-coding-section` in the correct mode.
|
||||
|
||||
- [ ] **Step 1: Replace `edit-vendor-changed-handler`**
|
||||
|
||||
Replace the entire `edit-vendor-changed-handler` function body with:
|
||||
|
||||
```clojure
|
||||
(defn edit-vendor-changed-handler [request]
|
||||
(let [multi-form-state (:multi-form-state request)
|
||||
snapshot (:snapshot multi-form-state)
|
||||
step-params (:step-params multi-form-state)
|
||||
mode (keyword (or (:mode step-params) "simple"))
|
||||
client-id (or (:transaction/client snapshot)
|
||||
(-> request :entity :transaction/client :db/id))
|
||||
vendor-id (or (:transaction/vendor step-params)
|
||||
(:transaction/vendor snapshot))
|
||||
total (Math/abs (or (-> request :entity :transaction/amount)
|
||||
(:transaction/amount snapshot)
|
||||
0.0))
|
||||
amount-mode (or (:amount-mode snapshot) "$")
|
||||
existing-accounts (or (seq (:transaction/accounts step-params))
|
||||
(seq (:transaction/accounts snapshot)))
|
||||
default-account (when (and (empty? existing-accounts) vendor-id client-id)
|
||||
(vendor-default-account vendor-id client-id))
|
||||
render-request
|
||||
(if (and (empty? existing-accounts) vendor-id client-id)
|
||||
(let [new-account (cond-> {:db/id (str (java.util.UUID/randomUUID))
|
||||
:transaction-account/location (or (:account/location default-account) "Shared")
|
||||
:transaction-account/amount (if (= amount-mode "%") 100.0 total)}
|
||||
default-account (assoc :transaction-account/account (:db/id default-account)))]
|
||||
(-> request
|
||||
(assoc-in [:multi-form-state :snapshot :transaction/accounts] [new-account])
|
||||
(assoc-in [:multi-form-state :step-params :transaction/accounts] [new-account])))
|
||||
request)]
|
||||
(html-response
|
||||
(fc/start-form (:multi-form-state render-request) nil
|
||||
(fc/with-field :step-params
|
||||
(manual-coding-section* mode render-request))))))
|
||||
```
|
||||
|
||||
Note: the `hx-target` on the vendor field in `manual-coding-section*` must point to `#manual-coding-section` (not `#account-grid-body`) — this was set correctly in Task 3.
|
||||
|
||||
- [ ] **Step 2: Verify the file compiles**
|
||||
|
||||
```bash
|
||||
clj-nrepl-eval -p 9000 "(require '[auto-ap.ssr.transaction.edit] :reload)"
|
||||
```
|
||||
|
||||
Expected: no errors.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/clj/auto_ap/ssr/transaction/edit.clj
|
||||
git commit -m "feat: update edit-vendor-changed-handler to support simple/advanced mode"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Fix `fc/with-cursor-index` usage in `simple-mode-fields*`
|
||||
|
||||
**Context:** The simple-mode fields need to emit form names like `step-params[transaction/accounts][0][transaction-account/account]`. The existing `fc/cursor-map` used in `account-grid-body*` handles this automatically. For the single-row simple mode we need to manually set index 0.
|
||||
|
||||
Look up how `fc/with-cursor-index` (or equivalent) works in `src/clj/auto_ap/ssr/form_cursor.clj` before writing the code in Task 2. If no such helper exists, use `fc/cursor-nth` or replicate the index manually via the form cursor API.
|
||||
|
||||
- [ ] **Step 1: Inspect the form cursor API**
|
||||
|
||||
```bash
|
||||
clj-nrepl-eval -p 9000 "(clj-mcp.repl-tools/list-vars 'auto-ap.ssr.form-cursor)"
|
||||
```
|
||||
|
||||
Note the available functions.
|
||||
|
||||
- [ ] **Step 2: Update `simple-mode-fields*` if needed**
|
||||
|
||||
If `fc/with-cursor-index` does not exist, replace the `fc/with-cursor-index 0` call in Task 2 with the correct form-cursor idiom. The key requirement is that the hidden `db/id`, `transaction-account/account`, `transaction-account/location`, and `transaction-account/amount` fields emit names matching index 0 of `transaction/accounts`.
|
||||
|
||||
A known working pattern from `account-grid-body*`:
|
||||
|
||||
```clojure
|
||||
(fc/cursor-map #(transaction-account-row* {:value % ...}))
|
||||
```
|
||||
|
||||
For simple mode with a single synthetic row, build a one-element vector in the snapshot and let `fc/cursor-map` iterate it — but render a flat div instead of a table. Or pass the cursor manually:
|
||||
|
||||
```clojure
|
||||
(fc/with-field :transaction/accounts
|
||||
(let [row-cursor (fc/cursor-nth 0)] ; adjust to actual API
|
||||
(fc/with-cursor row-cursor
|
||||
...field rendering...)))
|
||||
```
|
||||
|
||||
Verify field names are correct in a browser after implementation.
|
||||
|
||||
- [ ] **Step 3: Verify the file compiles**
|
||||
|
||||
```bash
|
||||
clj-nrepl-eval -p 9000 "(require '[auto-ap.ssr.transaction.edit] :reload)"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit if any changes were made**
|
||||
|
||||
```bash
|
||||
git add src/clj/auto_ap/ssr/transaction/edit.clj
|
||||
git commit -m "fix: correct form-cursor indexing for simple-mode account field"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Manual smoke test
|
||||
|
||||
Before writing automated tests, verify the UI works end-to-end in a browser.
|
||||
|
||||
- [ ] **Step 1: Start the application**
|
||||
|
||||
```bash
|
||||
INTEGREAT_JOB="" lein run
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Open a transaction with no accounts**
|
||||
|
||||
Navigate to a transaction with no coded accounts. Open the edit modal. Verify it opens in simple mode with blank account and location fields.
|
||||
|
||||
- [ ] **Step 3: Test vendor selection in simple mode**
|
||||
|
||||
Select a vendor. Verify the account field is populated with the vendor's default account and the location is set appropriately.
|
||||
|
||||
- [ ] **Step 4: Test toggle to advanced**
|
||||
|
||||
Click "Switch to advanced mode". Verify the full split table appears with one pre-populated row.
|
||||
|
||||
- [ ] **Step 5: Test toggle back to simple**
|
||||
|
||||
With 1 row, click "Switch to simple mode". Verify the single account/location fields appear with that row's values.
|
||||
|
||||
- [ ] **Step 6: Test with a split transaction**
|
||||
|
||||
Open a transaction that already has 2+ accounts. Verify it opens in advanced mode. Verify the "Switch to simple mode" link is absent.
|
||||
|
||||
- [ ] **Step 7: Test save round-trip**
|
||||
|
||||
In simple mode, set a vendor, account, and location. Save. Re-open. Verify the same values are pre-populated in simple mode.
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Write e2e tests
|
||||
|
||||
**Files:**
|
||||
- Create: `test/clj/auto_ap/ssr/transaction/edit_simple_advanced_mode_test.clj`
|
||||
|
||||
Check how existing e2e tests are structured first:
|
||||
|
||||
```bash
|
||||
clj-nrepl-eval -p 9000 "(clj-mcp.repl-tools/list-ns)"
|
||||
```
|
||||
|
||||
Look for test namespaces matching `auto-ap.ssr.transaction.*` or `auto-ap.e2e.*`. Follow the same fixture/helper patterns.
|
||||
|
||||
The test file should cover all 20 acceptance criteria from the spec. Group them with `testing` blocks:
|
||||
|
||||
```clojure
|
||||
(ns auto-ap.ssr.transaction.edit-simple-advanced-mode-test
|
||||
(:require
|
||||
[clojure.test :refer [deftest is testing use-fixtures]]
|
||||
;; ... project-specific test helpers ...
|
||||
))
|
||||
|
||||
(deftest simple-advanced-mode-initial-state
|
||||
(testing "AC1: uncoded transaction opens in simple mode"
|
||||
;; create a transaction with no accounts
|
||||
;; open edit modal
|
||||
;; verify #manual-coding-section has mode=simple hidden input
|
||||
;; verify no #account-grid-body present
|
||||
(is ...))
|
||||
|
||||
(testing "AC2: single-account transaction opens in simple mode with values pre-populated"
|
||||
...)
|
||||
|
||||
(testing "AC3: multi-account transaction opens in advanced mode"
|
||||
...))
|
||||
|
||||
(deftest simple-mode-vendor-selection
|
||||
(testing "AC4: selecting vendor populates account and location"
|
||||
...)
|
||||
(testing "AC5: selecting vendor does not overwrite manually chosen account"
|
||||
...))
|
||||
|
||||
(deftest mode-toggle
|
||||
(testing "AC9: switching to advanced carries account/location into first row"
|
||||
...)
|
||||
(testing "AC10: switching to advanced from blank simple gives empty table"
|
||||
...)
|
||||
(testing "AC11: switch-to-simple link visible with 0 or 1 rows"
|
||||
...)
|
||||
(testing "AC12: switch-to-simple link absent with 2+ rows"
|
||||
...)
|
||||
(testing "AC13: switching to simple pre-populates from first row"
|
||||
...))
|
||||
|
||||
(deftest save-round-trip
|
||||
(testing "AC6: save in simple mode persists vendor/account/location"
|
||||
...)
|
||||
(testing "AC18: switching modes mid-edit then saving produces valid transaction"
|
||||
...)
|
||||
(testing "AC19: split transaction re-opens in advanced mode with splits intact"
|
||||
...)
|
||||
(testing "AC20: single-account transaction re-opens in simple mode"
|
||||
...))
|
||||
```
|
||||
|
||||
Fill in actual test bodies using the project's test infrastructure (browser automation or ring mock depending on what exists).
|
||||
|
||||
- [ ] **Step 1: Check existing test conventions**
|
||||
|
||||
```bash
|
||||
clj-nrepl-eval -p 9000 "(require '[auto-ap.ssr.testing-conventions] :reload)"
|
||||
```
|
||||
|
||||
Also load the testing-conventions skill for guidance:
|
||||
|
||||
```
|
||||
Load skill: testing-conventions
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write the test file** following project conventions
|
||||
|
||||
- [ ] **Step 3: Run the tests**
|
||||
|
||||
```bash
|
||||
clj-nrepl-eval -p 9000 "(clojure.test/run-tests 'auto-ap.ssr.transaction.edit-simple-advanced-mode-test)"
|
||||
```
|
||||
|
||||
Expected: all tests pass.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add test/clj/auto_ap/ssr/transaction/edit_simple_advanced_mode_test.clj
|
||||
git commit -m "test: add e2e acceptance tests for simple/advanced mode"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Self-Review Checklist (completed inline)
|
||||
|
||||
- **Spec coverage:** All 20 ACs addressed — Tasks 2–5 implement the behaviour; Task 8 tests all 20.
|
||||
- **Placeholder scan:** Task 6 and Task 8 contain some "fill in" guidance — this is intentional because they depend on runtime API discovery. The instructions tell the engineer exactly where to look and what to verify.
|
||||
- **Type consistency:** `manual-coding-section*` is used consistently by `LinksStep/render-step`, `edit-vendor-changed-handler`, and `edit-wizard-toggle-mode-handler`. `#manual-coding-section` is the swap target throughout. `mode` hidden input uses `(name mode)` for string serialization and `(keyword ...)` for deserialization.
|
||||
@@ -0,0 +1,152 @@
|
||||
# Transaction Account Amount Mode Toggle - Design Spec
|
||||
|
||||
**Date:** 2026-05-20
|
||||
**Feature:** Global $/% Toggle for Transaction Accounts (Manual Action)
|
||||
|
||||
## Overview
|
||||
|
||||
In the transaction edit modal's "manual" action view, replace the static "$" column header with a sliding toggle that allows users to switch between viewing amounts as dollar values or percentages. When toggled, the entire account grid re-renders via HTMX with converted values. Percentages are multiplied by 100 (e.g., $200 on a $200 transaction → 100%). When switching back to dollars, use `spread-cents` to ensure accurate cent distribution.
|
||||
|
||||
## Motivation
|
||||
|
||||
The cljs master version supports per-row $/% toggles. Users want this capability in the SSR version, but with a single global toggle in the table header for simplicity and consistency with the bulk coding interface.
|
||||
|
||||
## Schema Changes
|
||||
|
||||
### Form State
|
||||
|
||||
Add `amount-mode` to the edit form's step params:
|
||||
|
||||
```clojure
|
||||
[:amount-mode [:enum "$" "%"] {:default "$"}]
|
||||
```
|
||||
|
||||
Stored in `multi-form-state` alongside existing transaction data. Not persisted to Datomic—purely a UI preference.
|
||||
|
||||
## UI Design
|
||||
|
||||
### Table Header
|
||||
|
||||
Replace the static `"$"` header cell (line ~739 in `edit.clj`) with a radio toggle:
|
||||
|
||||
```clojure
|
||||
(com/radio-card {:options [{:value "$" :content "$"}
|
||||
{:value "%" :content "%"}]
|
||||
:value (or amount-mode "$")
|
||||
:name "step-params[amount-mode]"
|
||||
:hx-post (bidi/path-for ssr-routes/only-routes ::route/toggle-amount-mode)
|
||||
:hx-target "#account-grid-body"
|
||||
:hx-swap "outerHTML"
|
||||
:hx-include "closest form"})
|
||||
```
|
||||
|
||||
### Grid Body
|
||||
|
||||
Wrap the account grid rows in a container with id `account-grid-body`:
|
||||
|
||||
```clojure
|
||||
[:div#account-grid-body
|
||||
(fc/cursor-map #(transaction-account-row* ...))
|
||||
...total/balance rows...]
|
||||
```
|
||||
|
||||
When toggled, only this container re-renders. All other form fields (vendor, memo, approval status) are preserved.
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Toggle Request (HTMX)
|
||||
|
||||
1. User clicks toggle
|
||||
2. HTMX serializes entire form via `hx-include "closest form"`
|
||||
3. POST to `::route/toggle-amount-mode`
|
||||
4. Server:
|
||||
- Merges form params into existing `multi-form-state`
|
||||
- Extracts old mode and new mode
|
||||
- Converts all `:transaction-account/amount` values:
|
||||
- If old="$" new="%": multiply by 100/total
|
||||
- If old="%" new="$": use `percentages->dollars` (see Conversion Logic)
|
||||
- Updates `amount-mode` in state
|
||||
- Re-renders `#account-grid-body`
|
||||
5. Client swaps grid body. Tab order preserved.
|
||||
|
||||
### Conversion Logic
|
||||
|
||||
**$ → %:**
|
||||
```clojure
|
||||
(defn ->percentage [amount total]
|
||||
(when (and amount total (not= total 0))
|
||||
(* 100.0 (/ amount total))))
|
||||
```
|
||||
|
||||
**% → $ (using spread-cents):**
|
||||
```clojure
|
||||
(defn percentages->dollars [percentages total]
|
||||
(let [total-cents (int (* 100 (Math/abs total)))
|
||||
pct-sum (reduce + 0 percentages)
|
||||
;; Normalize percentages to sum to 100
|
||||
normalized-pcts (if (zero? pct-sum)
|
||||
(repeat (count percentages) 0)
|
||||
(map #(* (/ % pct-sum) 100) percentages))
|
||||
;; Convert each pct to its share of cents
|
||||
individual-cents (map #(int (* total-cents (/ % 100))) normalized-pcts)
|
||||
short-by (- total-cents (reduce + 0 individual-cents))
|
||||
;; Distribute remainder using spread-cents pattern
|
||||
adjustments (concat (take short-by (repeat 1)) (repeat 0))
|
||||
final-cents (map + individual-cents adjustments)]
|
||||
(map #(* 0.01 %) final-cents)))
|
||||
```
|
||||
|
||||
Example: One account at 100% of $200.00 → `total-cents=20000`, `individual-cents=[20000]`, result: `[200.00]`
|
||||
|
||||
### Save Handling
|
||||
|
||||
Before validation in `save-handler :manual`:
|
||||
|
||||
```clojure
|
||||
(let [snapshot (:snapshot multi-form-state)
|
||||
accounts (:transaction/accounts snapshot)
|
||||
total (Math/abs (:transaction/amount existing-tx))
|
||||
mode (:amount-mode snapshot "$")
|
||||
;; If in % mode, convert back to $ before saving
|
||||
accounts' (if (= "%" mode)
|
||||
(let [percentages (map :transaction-account/amount accounts)
|
||||
dollar-amounts (percentages->dollars percentages total)]
|
||||
(map #(assoc %1 :transaction-account/amount %2) accounts dollar-amounts))
|
||||
accounts)]
|
||||
...)
|
||||
```
|
||||
|
||||
## Form Preservation
|
||||
|
||||
The HTMX toggle is designed to preserve:
|
||||
- **Tab order:** All inputs remain in DOM with same `tabindex` attributes
|
||||
- **Other form fields:** Vendor, memo, approval status are outside `#account-grid-body`
|
||||
- **Alpine.js state:** `x-data` on rows uses `data-key="show"` for animation—this is re-established on re-render
|
||||
- **Field names:** Account/location/amount field names follow `step-params[transaction/accounts][N][...]` pattern
|
||||
|
||||
## Error Handling
|
||||
|
||||
- **Zero transaction amount:** If total is $0, percentages are all 0%. Toggle is disabled or shows error.
|
||||
- **Percentage sum ≠ 100:** After editing in % mode, if percentages don't sum to 100, normalize proportionally before converting back to $.
|
||||
- **Invalid input:** If user types non-numeric in % mode, existing form validation catches it on submit.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. **Toggle $→%:** 200/200 transaction shows 100.0
|
||||
2. **Toggle %→$:** 100% on 200 transaction shows 200.00
|
||||
3. **Multiple accounts:** 50/50 split on 200 → 100.00/100.00 after conversion
|
||||
4. **Cent distribution:** 33.33/33.33/33.34% on $100 → uses spread-cents for accurate distribution
|
||||
5. **Form preservation:** Toggle doesn't lose vendor/memo data
|
||||
6. **Save in % mode:** Correctly converts back to $ before Datomic transaction
|
||||
|
||||
## Files to Modify
|
||||
|
||||
- `src/clj/auto_ap/ssr/transaction/edit.clj` — main implementation
|
||||
- `src/clj/auto_ap/routes/transactions.clj` — add `::route/toggle-amount-mode`
|
||||
- `src/clj/auto_ap/ssr/transaction/edit.clj` routes map — register handler
|
||||
|
||||
## Future Considerations
|
||||
|
||||
- This pattern could be extracted for reuse in invoice expense accounts
|
||||
- Consider persisting user's last-used mode preference in localStorage
|
||||
- Could add visual indicator when percentages don't sum to 100%
|
||||
@@ -0,0 +1,85 @@
|
||||
# Bulk Coding Transactions - Requirements Document
|
||||
|
||||
Based on analysis of the master cljs implementation (`src/cljs/auto_ap/views/pages/transactions/bulk_updates.cljs`) and GraphQL resolver (`src/clj/auto_ap/graphql/transactions.clj`).
|
||||
|
||||
## Feature Overview
|
||||
|
||||
Bulk coding allows admin users to apply vendor, approval status, and expense account allocations to multiple transactions simultaneously from the transactions grid page.
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
### 1. Access Control
|
||||
- **FR-1.1**: Bulk coding must be restricted to admin users only
|
||||
- **FR-1.2**: The bulk code button should only be visible/enabled when transactions are selected
|
||||
|
||||
### 2. Transaction Selection
|
||||
- **FR-2.1**: Users can select specific transactions via checkboxes in the grid
|
||||
- **FR-2.2**: Users can select all visible transactions via a header checkbox
|
||||
- **FR-2.3**: The system must filter out locked transactions (where client's `locked-until` date is after transaction date)
|
||||
- **FR-2.4**: The modal must display the count of transactions that will actually be coded (after filtering locked ones)
|
||||
|
||||
### 3. Bulk Code Form Fields
|
||||
- **FR-3.1**: **Vendor** (optional): Searchable typeahead to select a vendor
|
||||
- **FR-3.2**: **Approval Status** (optional): Select from:
|
||||
- No Change (empty)
|
||||
- Approved
|
||||
- Unapproved
|
||||
- Suppressed
|
||||
- Requires Feedback
|
||||
- **FR-3.3**: **Expense Accounts** (optional): One or more account allocations with:
|
||||
- Account: Searchable typeahead for expense accounts
|
||||
- Location: Dropdown with "Shared" and client-specific locations
|
||||
- Percentage: Numeric input (0-100), must total exactly 100% across all accounts
|
||||
|
||||
### 4. Account Location Validation
|
||||
- **FR-4.1**: If an account has a fixed location configured, the selected location MUST match it
|
||||
- **FR-4.2**: If an account has no fixed location, the selected location must be either "Shared" or one of the client's locations
|
||||
- **FR-4.3**: Invalid locations must be rejected with a clear error message
|
||||
|
||||
### 5. Percentage Validation
|
||||
- **FR-5.1**: When accounts are provided, the sum of all percentages must equal exactly 100%
|
||||
- **FR-5.2**: Values must be between 0 and 100
|
||||
- **FR-5.3**: Invalid totals must be rejected with a clear error message showing the actual total
|
||||
|
||||
### 6. Amount Distribution
|
||||
- **FR-6.1**: Percentages are converted to dollar amounts per transaction based on each transaction's amount
|
||||
- **FR-6.2**: For "Shared" location, amounts are distributed evenly across all client locations (with proper cent handling)
|
||||
- **FR-6.3**: Rounding errors are absorbed by the last account row
|
||||
- **FR-6.4**: Each transaction gets its own set of transaction-account entities
|
||||
|
||||
### 7. Submission Behavior
|
||||
- **FR-7.1**: Submitting with no accounts, no vendor, and no status should be a no-op (or rejected)
|
||||
- **FR-7.2**: On success, all selected non-locked transactions are updated
|
||||
- **FR-7.3**: Success response triggers a table refresh
|
||||
- **FR-7.4**: Modal closes on success
|
||||
|
||||
## UI/UX Requirements (from Master)
|
||||
|
||||
### SSR-Specific Adaptations
|
||||
- The SSR version uses a modal wizard with HTMX instead of a re-frame modal
|
||||
- Form state is managed server-side via `multi-form-state`
|
||||
- Percentage inputs display as whole numbers (50 for 50%) but are stored as decimals (0.5)
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### Happy Path
|
||||
1. Select single transaction, code with 100% to one account
|
||||
2. Select multiple transactions, code with vendor + status + accounts
|
||||
3. Select all visible transactions via header checkbox
|
||||
|
||||
### Validation
|
||||
4. Submit without any changes (no vendor, no status, no accounts)
|
||||
5. Submit with accounts totaling < 100%
|
||||
6. Submit with accounts totaling > 100%
|
||||
7. Submit with invalid location for account
|
||||
8. Submit with location not belonging to client
|
||||
|
||||
### Edge Cases
|
||||
9. All selected transactions are locked (count should be 0)
|
||||
10. Mix of locked and unlocked transactions (only unlocked should be coded)
|
||||
11. "Shared" location distributes across multiple client locations
|
||||
|
||||
## Known Issues to Verify
|
||||
|
||||
1. **Missing location validation**: The SSR version (`bulk_code.clj`) does not validate account locations against client locations or account fixed locations (present in GraphQL version)
|
||||
2. **Approval status options**: Verify "excluded" vs "suppressed" naming consistency
|
||||
@@ -0,0 +1,66 @@
|
||||
# Design: Memo and Description Filters for Transaction Page
|
||||
|
||||
## Overview
|
||||
|
||||
Add a new **Memo** filter to the transaction page and enhance the existing **Description** filter to support wildcard matching. Both filters should be case-insensitive.
|
||||
|
||||
## Changes
|
||||
|
||||
### 1. New Memo Filter
|
||||
|
||||
- Add a text input field in the filter sidebar
|
||||
- Search against `:transaction/memo` attribute
|
||||
- Convert user input to regex pattern `.*input.*` with `(?i)` flag
|
||||
- Use Datomic `re-find` for matching
|
||||
- **Place this filter towards the end of the filter list** since regex matching is expensive
|
||||
|
||||
### 2. Enhanced Description Filter
|
||||
|
||||
- Change from `.contains` substring matching to `re-find` with `(?i)` flag
|
||||
- Wrap user input with `.*` on both ends: `.*input.*`
|
||||
- Maintains existing UI placement
|
||||
|
||||
## Files Modified
|
||||
|
||||
- `src/clj/auto_ap/ssr/transaction/common.clj`
|
||||
|
||||
### Query Schema Changes
|
||||
|
||||
Add `:memo` key to the `query-schema` map:
|
||||
```clojure
|
||||
[:memo {:optional true} [:maybe [:string {:decode/string strip}]]]
|
||||
```
|
||||
|
||||
### Filter UI Changes
|
||||
|
||||
Add memo filter input in the `filters` function, placed **after** the Amount filter and **before** the Linking filter.
|
||||
|
||||
### Query Logic Changes
|
||||
|
||||
In `fetch-ids`, add memo filter condition in the `cond->` chain (placed after other cheaper filters like description).
|
||||
|
||||
### Description Filter Update
|
||||
|
||||
Change the description filter from:
|
||||
```clojure
|
||||
'[(clojure.string/lower-case ?do) ?do2]
|
||||
'[(.contains ?do2 ?description)]]
|
||||
```
|
||||
|
||||
To:
|
||||
```clojure
|
||||
'[(re-find ?description-regex ?do)]]
|
||||
```
|
||||
with args: `[(re-pattern (str "(?i).*" description ".*"))]`
|
||||
|
||||
## Behavior
|
||||
|
||||
- Both filters are optional (only applied when user enters text)
|
||||
- Both are case-insensitive
|
||||
- Both support substring matching (e.g., "rent" matches "Monthly Rent Payment")
|
||||
- Empty or whitespace-only input is ignored
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Memo filter is placed towards the end of the filter chain since regex operations are more expensive than exact matches
|
||||
- Description filter also uses regex, but since it's an existing filter being enhanced, it stays in its current position in the query
|
||||
@@ -0,0 +1,132 @@
|
||||
# Transaction Edit Modal: Simple / Advanced Mode
|
||||
|
||||
**Date:** 2026-05-27
|
||||
**Status:** Approved
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The transaction editing modal gains a two-mode interface. **Simple mode** replaces the account/location split table with two single fields (account typeahead + location dropdown), suitable for the common case of a single-account transaction. **Advanced mode** exposes the existing split table for multi-account allocations. The mode is selected automatically on open based on the transaction's current state, and the user can toggle between modes via a server-rendered swap.
|
||||
|
||||
---
|
||||
|
||||
## Layout
|
||||
|
||||
### Simple mode
|
||||
|
||||
```
|
||||
[ Vendor typeahead ]
|
||||
[ Account typeahead ] [ Location ▼ ]
|
||||
Switch to advanced mode →
|
||||
[ Memo ]
|
||||
[ Approval status buttons ]
|
||||
```
|
||||
|
||||
### Advanced mode
|
||||
|
||||
```
|
||||
[ Vendor typeahead ]
|
||||
Switch to simple mode →
|
||||
[ Account | Location | $ / % | ✕ ]
|
||||
[ Account | Location | $ / % | ✕ ]
|
||||
[ + Add row ]
|
||||
[ Memo ]
|
||||
[ Approval status buttons ]
|
||||
```
|
||||
|
||||
The toggle link sits directly below the vendor field. It reads "Switch to advanced mode" in simple mode and "Switch to simple mode" in advanced mode. In advanced mode with 2+ rows, the "Switch to simple mode" link is hidden (the user must remove rows manually before they can return to simple mode). The toggle link fires `hx-get` on the `::route/edit-wizard-toggle-mode` endpoint with `hx-include="closest form"` so the current form state (including `mode`) is carried in the request.
|
||||
|
||||
---
|
||||
|
||||
## Mode Selection on Open
|
||||
|
||||
The server determines initial mode when rendering the `LinksStep` body:
|
||||
|
||||
- **0 or 1 existing account rows** → render simple mode, pre-populate the account/location fields from the existing row (blank if none).
|
||||
- **2+ existing account rows** → render advanced mode with all rows populated.
|
||||
|
||||
---
|
||||
|
||||
## Toggle Mechanism (Option B — HTMX swap)
|
||||
|
||||
Clicking the toggle link fires an `hx-get` request to a new endpoint that re-renders the editable body of the modal in the target mode. Mode is passed as a query param (e.g., `?mode=advanced` or `?mode=simple`).
|
||||
|
||||
**Simple → Advanced:** The current account and location values from the simple fields are carried into the first row of the advanced table (100% of transaction total, or full dollar amount). Any additional rows previously added to the table are preserved via the multi-form-state snapshot.
|
||||
|
||||
**Advanced → Simple:** Only available when there is exactly 0 or 1 row in the table. The toggle link is absent when 2+ rows exist.
|
||||
|
||||
The swapped fragment replaces the entire editable body div (`#links-step-body` or equivalent target), keeping the side panel and modal chrome intact.
|
||||
|
||||
The current mode is tracked as a hidden `<input name="mode" value="simple|advanced">` inside the form. This ensures all HTMX calls that `hx-include` the form (vendor change, toggle, submit) carry the mode value without requiring it to be a separate query param.
|
||||
|
||||
---
|
||||
|
||||
## Vendor Selection Behaviour
|
||||
|
||||
### In simple mode
|
||||
|
||||
When the vendor typeahead fires its `change` event, the existing `edit-vendor-changed` HTMX endpoint is called. The response re-renders the simple-mode body with:
|
||||
|
||||
- The account field populated with the vendor's default account (clientized for the transaction's client).
|
||||
- The location field set to the account's fixed location, or "Shared" if the account has no fixed location.
|
||||
- Fields are editable; the user may override both.
|
||||
|
||||
The vendor default only applies when there are no existing accounts (matching existing server-side logic in `edit-vendor-changed-handler`). If the user has already manually chosen an account, changing vendor does not overwrite it.
|
||||
|
||||
### In advanced mode
|
||||
|
||||
Vendor change behaviour is unchanged from the current implementation: if no rows exist, a single row is created with the vendor's default account and location at 100% / full amount. If rows already exist, the vendor change has no effect on the table.
|
||||
|
||||
---
|
||||
|
||||
## Form Submission
|
||||
|
||||
Both modes submit to the same `edit-submit` endpoint. Simple mode submits the single account and location as a one-element `transaction/accounts` vector, identical in shape to what the advanced table produces today. No schema or handler changes are needed for submission.
|
||||
|
||||
---
|
||||
|
||||
## New Routes
|
||||
|
||||
| Method | Route key | Purpose |
|
||||
|--------|-----------|---------|
|
||||
| `GET` | `::route/edit-wizard-toggle-mode` | Re-renders the editable body in the requested mode. Reads current form state from `hx-include`'d form fields; the `mode` hidden input indicates the target mode (the endpoint flips it). |
|
||||
|
||||
The `edit-vendor-changed` endpoint reads the `mode` hidden input from the included form to determine whether to return simple-mode or advanced-mode HTML.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
These are the expected behaviours to be verified by e2e tests:
|
||||
|
||||
1. **Verify** that when a transaction has no coded accounts, it opens in simple mode with blank account and location fields.
|
||||
2. **Verify** that when a transaction has exactly one coded account, it opens in simple mode with that account and location pre-selected.
|
||||
3. **Verify** that when a transaction has two or more coded accounts, it opens in advanced mode showing the full split table.
|
||||
4. **Verify** that in simple mode, selecting a vendor replaces the account field with the vendor's default account (clientized for the transaction's client) and sets the location to the account's fixed location or "Shared".
|
||||
5. **Verify** that in simple mode, selecting a vendor does not overwrite an account the user has already manually chosen (i.e., the account typeahead already has a value when the vendor change fires).
|
||||
6. **Verify** that after saving in simple mode, re-opening the transaction shows the same vendor, account, and location that were saved.
|
||||
7. **Verify** that in simple mode, the account field is a typeahead that respects the same allowance rules as the advanced table (`:account/default-allowance`).
|
||||
8. **Verify** that in simple mode, the location dropdown shows the account's fixed location (sole option) if the account has one, or the full list of client locations plus "Shared" if it does not.
|
||||
9. **Verify** that clicking "Switch to advanced mode" from simple mode re-renders the form in advanced mode with one table row pre-populated from the simple-mode account and location fields.
|
||||
10. **Verify** that clicking "Switch to advanced mode" from a blank simple mode (no account selected) re-renders the form in advanced mode with an empty table (no rows, just the "Add row" button).
|
||||
11. **Verify** that the "Switch to simple mode" link is visible in advanced mode when there is exactly 0 or 1 row.
|
||||
12. **Verify** that the "Switch to simple mode" link is absent in advanced mode when there are 2 or more rows.
|
||||
13. **Verify** that clicking "Switch to simple mode" from advanced mode (1 row) re-renders the form in simple mode with that row's account and location pre-populated.
|
||||
14. **Verify** that in advanced mode, selecting a vendor when there are no rows creates a single row with the vendor's default account, correct location, and 100% (or full dollar amount) allocation.
|
||||
15. **Verify** that in advanced mode, selecting a vendor when rows already exist does not modify the existing rows.
|
||||
16. **Verify** that the vendor default account is determined by clientizing the vendor for the client the transaction belongs to (client-specific account override takes precedence over the global vendor default).
|
||||
17. **Verify** that the approval status, memo, and vendor fields are present and functional in both simple and advanced modes.
|
||||
18. **Verify** that switching modes mid-edit and then saving produces a valid transaction (no orphaned or duplicated account rows).
|
||||
19. **Verify** that a transaction saved in advanced mode with splits can be re-opened and remains in advanced mode with all splits intact.
|
||||
20. **Verify** that a transaction saved in simple mode (single account) can be re-opened in simple mode and the single account/location are correctly pre-populated.
|
||||
|
||||
---
|
||||
|
||||
## Files Affected
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/clj/auto_ap/ssr/transaction/edit.clj` | Add simple-mode rendering functions; add `edit-wizard-toggle-mode` handler; update `edit-vendor-changed-handler` to support both modes; update `LinksStep` body render to select initial mode |
|
||||
| `src/cljc/auto_ap/routes/transactions.cljc` | Add `::edit-wizard-toggle-mode` route |
|
||||
| E2E test file (to be created) | Acceptance criteria tests for all 20 items above |
|
||||
550
e2e/bulk-code-transactions.spec.ts
Normal file
550
e2e/bulk-code-transactions.spec.ts
Normal file
@@ -0,0 +1,550 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
let testInfoCache: any = null;
|
||||
|
||||
async function getTestInfo(page: any) {
|
||||
const response = await page.request.get('/test-info');
|
||||
testInfoCache = await response.json();
|
||||
return testInfoCache;
|
||||
}
|
||||
|
||||
async function navigateToTransactions(page: any, clientMode: string = 'mine') {
|
||||
await page.setExtraHTTPHeaders({
|
||||
'x-clients': clientMode === 'all' ? '"all"' : '"mine"'
|
||||
});
|
||||
await page.goto('/transaction2');
|
||||
await page.waitForSelector('table tbody tr');
|
||||
}
|
||||
|
||||
async function selectTransactionByIndex(page: any, index: number) {
|
||||
const rows = page.locator('table tbody tr');
|
||||
const row = rows.nth(index);
|
||||
const checkbox = row.locator('input[type="checkbox"][name="id"]').first();
|
||||
await checkbox.click();
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
async function selectAllTransactions(page: any) {
|
||||
const headerCheckbox = page.locator('input#checkbox-all').first();
|
||||
await headerCheckbox.click();
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
async function openBulkCodeModal(page: any) {
|
||||
const codeButton = page.locator('button:has-text("Code")').first();
|
||||
await codeButton.click();
|
||||
await page.waitForSelector('#modal-holder[x-show="open"]', { state: 'visible' });
|
||||
await page.waitForSelector('#wizardmodal');
|
||||
}
|
||||
|
||||
async function closeBulkCodeModal(page: any) {
|
||||
// The success response swaps the modal content, but the modal holder stays open
|
||||
// Wait for the success message to appear
|
||||
await page.waitForSelector('text=Successfully coded', { timeout: 10000 });
|
||||
}
|
||||
|
||||
async function selectAccountFromTypeahead(page: any, rowIndex: number, accountKey: string) {
|
||||
const testInfo = await getTestInfo(page);
|
||||
const accountId = testInfo.accounts[accountKey];
|
||||
|
||||
if (!accountId) {
|
||||
throw new Error(`Could not find account with key ${accountKey}`);
|
||||
}
|
||||
|
||||
const allRows = page.locator('#account-entries tbody tr');
|
||||
const rowCount = await allRows.count();
|
||||
|
||||
let accountRow = null;
|
||||
let accountRowIndex = 0;
|
||||
for (let i = 0; i < rowCount; i++) {
|
||||
const row = allRows.nth(i);
|
||||
const hasAccountInput = await row.locator('input[name*="account"]').count() > 0;
|
||||
if (hasAccountInput) {
|
||||
if (accountRowIndex === rowIndex) {
|
||||
accountRow = row;
|
||||
break;
|
||||
}
|
||||
accountRowIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!accountRow) {
|
||||
throw new Error(`Could not find account row at index ${rowIndex}`);
|
||||
}
|
||||
|
||||
const hiddenInput = accountRow.locator('input[type="hidden"][name*="[account]"]').first();
|
||||
|
||||
await hiddenInput.evaluate((el: HTMLInputElement, value: string) => {
|
||||
// Replace the Alpine-managed hidden input with a plain one
|
||||
const newInput = document.createElement('input');
|
||||
newInput.type = 'hidden';
|
||||
newInput.name = el.name;
|
||||
newInput.value = value;
|
||||
|
||||
el.parentNode.replaceChild(newInput, el);
|
||||
}, accountId.toString());
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Trigger the location select reload by dispatching 'changed' event
|
||||
const locationContainer = accountRow.locator('[x-dispatch\\:changed]').first();
|
||||
if (await locationContainer.count() > 0) {
|
||||
await locationContainer.evaluate((el: HTMLElement) => {
|
||||
el.dispatchEvent(new CustomEvent('changed', { bubbles: true }));
|
||||
});
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
}
|
||||
|
||||
async function findAccountRow(page: any, rowIndex: number) {
|
||||
const allRows = page.locator('#account-entries tbody tr');
|
||||
const rowCount = await allRows.count();
|
||||
|
||||
let accountRowIndex = 0;
|
||||
for (let i = 0; i < rowCount; i++) {
|
||||
const row = allRows.nth(i);
|
||||
const hasAccountInput = await row.locator('input[name*="account"]').count() > 0;
|
||||
if (hasAccountInput) {
|
||||
if (accountRowIndex === rowIndex) {
|
||||
return row;
|
||||
}
|
||||
accountRowIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Could not find account row at index ${rowIndex}`);
|
||||
}
|
||||
|
||||
async function setAccountPercentage(page: any, rowIndex: number, percentage: string) {
|
||||
const row = await findAccountRow(page, rowIndex);
|
||||
const percentageInput = row.locator('input[name*="percentage"]').first();
|
||||
await percentageInput.fill(percentage);
|
||||
await percentageInput.dispatchEvent('change');
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
|
||||
async function setAccountLocation(page: any, rowIndex: number, location: string) {
|
||||
const row = await findAccountRow(page, rowIndex);
|
||||
const locationSelect = row.locator('select[name*="location"]').first();
|
||||
|
||||
// If the option doesn't exist, add it (for testing invalid locations)
|
||||
const optionExists = await locationSelect.locator(`option[value="${location}"]`).count() > 0;
|
||||
if (!optionExists) {
|
||||
await locationSelect.evaluate((el: HTMLSelectElement, value: string) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = value;
|
||||
option.textContent = value;
|
||||
el.appendChild(option);
|
||||
}, location);
|
||||
}
|
||||
|
||||
await locationSelect.selectOption(location);
|
||||
await locationSelect.dispatchEvent('change');
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
|
||||
async function addNewAccount(page: any) {
|
||||
const newAccountButton = page.locator('a:has-text("New account")').first();
|
||||
await newAccountButton.click();
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async function submitBulkCodeForm(page: any) {
|
||||
const form = page.locator('#wizard-form');
|
||||
await form.evaluate((el: HTMLFormElement) => {
|
||||
el.dispatchEvent(new Event('submit', { bubbles: true }));
|
||||
});
|
||||
}
|
||||
|
||||
async function getModalErrorText(page: any) {
|
||||
const errorElement = page.locator('#form-errors .error-content');
|
||||
try {
|
||||
await errorElement.waitFor({ state: 'visible', timeout: 3000 });
|
||||
return await errorElement.textContent();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
test.describe('Bulk Code Transactions - Happy Path', () => {
|
||||
test('should bulk code a single transaction with vendor, status, and 100% account', async ({ page }) => {
|
||||
await navigateToTransactions(page);
|
||||
await selectTransactionByIndex(page, 0);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
// Verify modal shows correct count
|
||||
await expect(page.locator('text=Bulk editing 1 transactions')).toBeVisible();
|
||||
|
||||
// Select vendor
|
||||
const vendorHidden = page.locator('input[type="hidden"][name="step-params[vendor]"]').first();
|
||||
const testInfo = await getTestInfo(page);
|
||||
await vendorHidden.evaluate((el: HTMLInputElement, value: string) => {
|
||||
const newInput = document.createElement('input');
|
||||
newInput.type = 'hidden';
|
||||
newInput.name = el.name;
|
||||
newInput.value = value;
|
||||
el.parentNode.replaceChild(newInput, el);
|
||||
}, testInfo.accounts.vendor.toString());
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Select approval status
|
||||
const statusSelect = page.locator('select[name="step-params[approval-status]"]').first();
|
||||
await statusSelect.selectOption('approved');
|
||||
|
||||
// Add account
|
||||
await addNewAccount(page);
|
||||
await selectAccountFromTypeahead(page, 0, 'test-account');
|
||||
await setAccountLocation(page, 0, 'Shared');
|
||||
await setAccountPercentage(page, 0, '100');
|
||||
|
||||
// Submit
|
||||
await submitBulkCodeForm(page);
|
||||
await closeBulkCodeModal(page);
|
||||
|
||||
// Verify success by checking table refreshed
|
||||
await page.waitForSelector('table tbody tr');
|
||||
});
|
||||
|
||||
test('should bulk code multiple selected transactions', async ({ page }) => {
|
||||
await navigateToTransactions(page);
|
||||
await selectTransactionByIndex(page, 0);
|
||||
await selectTransactionByIndex(page, 1);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
// Should show count of selected transactions
|
||||
await expect(page.locator('text=Bulk editing 2 transactions')).toBeVisible();
|
||||
|
||||
// Add account at 100%
|
||||
await addNewAccount(page);
|
||||
await selectAccountFromTypeahead(page, 0, 'test-account');
|
||||
await setAccountLocation(page, 0, 'Shared');
|
||||
await setAccountPercentage(page, 0, '100');
|
||||
|
||||
await submitBulkCodeForm(page);
|
||||
await closeBulkCodeModal(page);
|
||||
|
||||
await page.waitForSelector('table tbody tr');
|
||||
});
|
||||
|
||||
test('should bulk code all visible transactions via header checkbox', async ({ page }) => {
|
||||
await navigateToTransactions(page);
|
||||
await selectAllTransactions(page);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
// Should show all transactions
|
||||
await expect(page.locator('text=Bulk editing 6 transactions')).toBeVisible();
|
||||
|
||||
// Add account at 100%
|
||||
await addNewAccount(page);
|
||||
await selectAccountFromTypeahead(page, 0, 'test-account');
|
||||
await setAccountLocation(page, 0, 'Shared');
|
||||
await setAccountPercentage(page, 0, '100');
|
||||
|
||||
await submitBulkCodeForm(page);
|
||||
await closeBulkCodeModal(page);
|
||||
|
||||
await page.waitForSelector('table tbody tr');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Bulk Code Transactions - Validation', () => {
|
||||
test('should reject when no vendor, status, or accounts provided', async ({ page }) => {
|
||||
await navigateToTransactions(page);
|
||||
await selectTransactionByIndex(page, 0);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
// Submit without any changes
|
||||
await submitBulkCodeForm(page);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Modal should still be open
|
||||
await expect(page.locator('#modal-holder[x-show="open"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should preserve vendor and status on validation error', async ({ page }) => {
|
||||
await navigateToTransactions(page);
|
||||
await selectTransactionByIndex(page, 0);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
// Select vendor
|
||||
const testInfo = await getTestInfo(page);
|
||||
const vendorId = testInfo.accounts.vendor;
|
||||
|
||||
const vendorContainer = page.locator('div[hx-post*="vendor-changed"]').first();
|
||||
const vendorHidden = vendorContainer.locator('input[type="hidden"]').first();
|
||||
|
||||
await vendorHidden.evaluate((el: HTMLInputElement, value: string) => {
|
||||
const newInput = document.createElement('input');
|
||||
newInput.type = 'hidden';
|
||||
newInput.name = el.name;
|
||||
newInput.value = value;
|
||||
el.parentNode.replaceChild(newInput, el);
|
||||
}, vendorId.toString());
|
||||
|
||||
await vendorContainer.evaluate((el: HTMLElement) => {
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
|
||||
await page.waitForResponse(response => response.url().includes('/vendor-changed') && response.status() === 200);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Select approval status
|
||||
const statusSelect = page.locator('select[name="step-params[approval-status]"]').first();
|
||||
await statusSelect.selectOption('approved');
|
||||
|
||||
// Vendor selection pre-populated a default account row at 100%.
|
||||
// Modify its percentage to 50% (invalid - doesn't total 100%).
|
||||
await setAccountPercentage(page, 0, '50');
|
||||
|
||||
await submitBulkCodeForm(page);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Modal should still be open
|
||||
await expect(page.locator('#modal-holder[x-show="open"]')).toBeVisible();
|
||||
|
||||
// Vendor should still be selected
|
||||
const vendorHiddenAfter = page.locator('input[type="hidden"][name="step-params[vendor]"]').first();
|
||||
const vendorValueAfter = await vendorHiddenAfter.inputValue();
|
||||
expect(vendorValueAfter).toBe(vendorId.toString());
|
||||
|
||||
// Status should still be selected
|
||||
const statusValueAfter = await statusSelect.inputValue();
|
||||
expect(statusValueAfter).toBe('approved');
|
||||
|
||||
// Should show validation error
|
||||
const errorText = await getModalErrorText(page);
|
||||
expect(errorText).toContain('does not equal 100%');
|
||||
});
|
||||
|
||||
test('should reject when account percentages total less than 100%', async ({ page }) => {
|
||||
await navigateToTransactions(page);
|
||||
await selectTransactionByIndex(page, 0);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
await addNewAccount(page);
|
||||
await selectAccountFromTypeahead(page, 0, 'test-account');
|
||||
await setAccountLocation(page, 0, 'Shared');
|
||||
await setAccountPercentage(page, 0, '50');
|
||||
|
||||
await submitBulkCodeForm(page);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Modal should still be open
|
||||
await expect(page.locator('#modal-holder[x-show="open"]')).toBeVisible();
|
||||
|
||||
// Should show validation error
|
||||
const errorText = await getModalErrorText(page);
|
||||
expect(errorText).toContain('does not equal 100%');
|
||||
});
|
||||
|
||||
test('should reject when account percentages total more than 100%', async ({ page }) => {
|
||||
await navigateToTransactions(page);
|
||||
await selectTransactionByIndex(page, 0);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
await addNewAccount(page);
|
||||
await selectAccountFromTypeahead(page, 0, 'test-account');
|
||||
await setAccountLocation(page, 0, 'Shared');
|
||||
await setAccountPercentage(page, 0, '150');
|
||||
|
||||
await submitBulkCodeForm(page);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Modal should still be open
|
||||
await expect(page.locator('#modal-holder[x-show="open"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should reject invalid location for account with fixed location', async ({ page }) => {
|
||||
await navigateToTransactions(page);
|
||||
await selectTransactionByIndex(page, 0);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
await addNewAccount(page);
|
||||
// Use the fixed-location account
|
||||
await selectAccountFromTypeahead(page, 0, 'fixed-location-account');
|
||||
// Try to set wrong location (account is fixed to "DT", try "INVALID")
|
||||
await setAccountLocation(page, 0, 'INVALID');
|
||||
await setAccountPercentage(page, 0, '100');
|
||||
|
||||
await submitBulkCodeForm(page);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Modal should still be open
|
||||
await expect(page.locator('#modal-holder[x-show="open"]')).toBeVisible();
|
||||
|
||||
// Should show validation error about location mismatch
|
||||
const errorText = await getModalErrorText(page);
|
||||
expect(errorText).toContain('location');
|
||||
});
|
||||
|
||||
test('should reject location not belonging to client', async ({ page }) => {
|
||||
await navigateToTransactions(page);
|
||||
await selectTransactionByIndex(page, 0);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
await addNewAccount(page);
|
||||
await selectAccountFromTypeahead(page, 0, 'test-account');
|
||||
// Client only has "DT" location, try "GR"
|
||||
await setAccountLocation(page, 0, 'GR');
|
||||
await setAccountPercentage(page, 0, '100');
|
||||
|
||||
await submitBulkCodeForm(page);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Modal should still be open
|
||||
await expect(page.locator('#modal-holder[x-show="open"]')).toBeVisible();
|
||||
|
||||
// Should show validation error
|
||||
const errorText = await getModalErrorText(page);
|
||||
expect(errorText).toContain('location');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Bulk Code Transactions - Account Distribution', () => {
|
||||
test('should split 50/50 between two accounts', async ({ page }) => {
|
||||
await navigateToTransactions(page);
|
||||
await selectTransactionByIndex(page, 0);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
// First account at 50%
|
||||
await addNewAccount(page);
|
||||
await selectAccountFromTypeahead(page, 0, 'test-account');
|
||||
await setAccountLocation(page, 0, 'DT');
|
||||
await setAccountPercentage(page, 0, '50');
|
||||
|
||||
// Second account at 50%
|
||||
await addNewAccount(page);
|
||||
await selectAccountFromTypeahead(page, 1, 'second-account');
|
||||
await setAccountLocation(page, 1, 'DT');
|
||||
await setAccountPercentage(page, 1, '50');
|
||||
|
||||
await submitBulkCodeForm(page);
|
||||
await closeBulkCodeModal(page);
|
||||
|
||||
await page.waitForSelector('table tbody tr');
|
||||
});
|
||||
|
||||
test('should allow Shared location for account without fixed location', async ({ page }) => {
|
||||
await navigateToTransactions(page);
|
||||
await selectTransactionByIndex(page, 0);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
await addNewAccount(page);
|
||||
await selectAccountFromTypeahead(page, 0, 'test-account');
|
||||
await setAccountLocation(page, 0, 'Shared');
|
||||
await setAccountPercentage(page, 0, '100');
|
||||
|
||||
await submitBulkCodeForm(page);
|
||||
await closeBulkCodeModal(page);
|
||||
|
||||
await page.waitForSelector('table tbody tr');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Bulk Code Transactions - Vendor Pre-population', () => {
|
||||
test('should pre-populate default account when vendor is selected', async ({ page }) => {
|
||||
// Ensure single-client mode
|
||||
await page.request.get('/test-set-client-mode?mode=single-client');
|
||||
|
||||
await navigateToTransactions(page);
|
||||
await selectTransactionByIndex(page, 0);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
// Select vendor (test vendor has default-account set to test-account)
|
||||
const testInfo = await getTestInfo(page);
|
||||
const vendorId = testInfo.accounts.vendor;
|
||||
|
||||
// The vendor typeahead dispatches change from its parent div
|
||||
// We need to set the hidden input and dispatch change on the container
|
||||
const vendorContainer = page.locator('div[hx-post*="vendor-changed"]').first();
|
||||
const vendorHidden = vendorContainer.locator('input[type="hidden"]').first();
|
||||
|
||||
await vendorHidden.evaluate((el: HTMLInputElement, value: string) => {
|
||||
const newInput = document.createElement('input');
|
||||
newInput.type = 'hidden';
|
||||
newInput.name = el.name;
|
||||
newInput.value = value;
|
||||
el.parentNode.replaceChild(newInput, el);
|
||||
}, vendorId.toString());
|
||||
|
||||
// Dispatch change on the container to trigger HTMX
|
||||
await vendorContainer.evaluate((el: HTMLElement) => {
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
|
||||
// Wait for HTMX response
|
||||
await page.waitForResponse(response => response.url().includes('/vendor-changed') && response.status() === 200);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Account should be pre-populated - check for account row
|
||||
const accountRows = page.locator('#account-entries tbody tr');
|
||||
const rowCount = await accountRows.count();
|
||||
|
||||
// Should have at least 1 account row (the default account) plus the new-row button
|
||||
expect(rowCount).toBeGreaterThanOrEqual(2);
|
||||
|
||||
// The account should have a hidden input with the test-account ID
|
||||
const accountHidden = page.locator('input[type="hidden"][name*="[account]"]').first();
|
||||
const accountValue = await accountHidden.inputValue();
|
||||
expect(accountValue).toBe(testInfo.accounts['test-account'].toString());
|
||||
|
||||
// Percentage should be 100
|
||||
const percentageInput = page.locator('input[name*="percentage"]').first();
|
||||
const percentageValue = await percentageInput.inputValue();
|
||||
expect(percentageValue).toBe('100');
|
||||
|
||||
// Submit should succeed
|
||||
await submitBulkCodeForm(page);
|
||||
await closeBulkCodeModal(page);
|
||||
|
||||
await page.waitForSelector('table tbody tr');
|
||||
});
|
||||
|
||||
test('should pre-populate non-clientized default account when user has multiple clients', async ({ page }) => {
|
||||
// Switch to multi-client mode
|
||||
await page.request.get('/test-set-client-mode?mode=multi-client');
|
||||
|
||||
// Use 'all' to see all clients in the database
|
||||
await navigateToTransactions(page, 'all');
|
||||
await selectTransactionByIndex(page, 0);
|
||||
await openBulkCodeModal(page);
|
||||
|
||||
// Select vendor
|
||||
const testInfo = await getTestInfo(page);
|
||||
const vendorId = testInfo.accounts.vendor;
|
||||
|
||||
const vendorContainer = page.locator('div[hx-post*="vendor-changed"]').first();
|
||||
const vendorHidden = vendorContainer.locator('input[type="hidden"]').first();
|
||||
|
||||
await vendorHidden.evaluate((el: HTMLInputElement, value: string) => {
|
||||
const newInput = document.createElement('input');
|
||||
newInput.type = 'hidden';
|
||||
newInput.name = el.name;
|
||||
newInput.value = value;
|
||||
el.parentNode.replaceChild(newInput, el);
|
||||
}, vendorId.toString());
|
||||
|
||||
// Dispatch change on the container to trigger HTMX
|
||||
await vendorContainer.evaluate((el: HTMLElement) => {
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
|
||||
// Wait for HTMX response
|
||||
await page.waitForResponse(response => response.url().includes('/vendor-changed') && response.status() === 200);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Should pre-populate the vendor's default account (non-clientized) plus the "New account" row
|
||||
const accountInputs = page.locator('#account-entries input[type="hidden"][name*="[account]"]');
|
||||
const accountInputCount = await accountInputs.count();
|
||||
expect(accountInputCount).toBe(1);
|
||||
|
||||
// The pre-populated account should be the vendor's raw default account (test-account)
|
||||
const accountValue = await accountInputs.first().inputValue();
|
||||
expect(accountValue).toBe(testInfo.accounts['test-account'].toString());
|
||||
|
||||
// Switch back to single-client mode for other tests
|
||||
await page.request.get('/test-set-client-mode?mode=single-client');
|
||||
});
|
||||
});
|
||||
483
e2e/transaction-edit.spec.ts
Normal file
483
e2e/transaction-edit.spec.ts
Normal file
@@ -0,0 +1,483 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
async function openEditModal(page: any, transactionIndex: number = 0) {
|
||||
// Navigate to transactions page
|
||||
await page.goto('/transaction2');
|
||||
|
||||
// Wait for the table to load
|
||||
await page.waitForSelector('table tbody tr');
|
||||
|
||||
// Find and click the edit button for the specified transaction
|
||||
const editButton = page.locator('button[hx-get*="/transaction2/"][hx-get*="/edit"]').nth(transactionIndex);
|
||||
await editButton.click();
|
||||
|
||||
// Wait for the modal to open
|
||||
await page.waitForSelector('#modal-holder[x-show="open"]', { state: 'visible' });
|
||||
await page.waitForSelector('#wizardmodal');
|
||||
|
||||
// The modal is now single-page (Edit Transaction). Click "Manual" tab to ensure
|
||||
// the manual account coding form is active.
|
||||
await page.click('button:has-text("Manual")');
|
||||
|
||||
// Wait for the manual form to appear
|
||||
await page.waitForSelector('#account-grid-body');
|
||||
}
|
||||
|
||||
let testInfoCache: any = null;
|
||||
|
||||
async function getTestInfo(page: any) {
|
||||
// Always fetch fresh to handle server restarts
|
||||
const response = await page.request.get('/test-info');
|
||||
testInfoCache = await response.json();
|
||||
return testInfoCache;
|
||||
}
|
||||
|
||||
async function selectAccountFromTypeahead(page: any, rowIndex: number, accountName: string) {
|
||||
// The account search uses Solr which isn't available in tests.
|
||||
// Instead, we directly set the hidden input value via JavaScript.
|
||||
|
||||
// Get all rows except the new-row, total, balance, and transaction total rows
|
||||
const allRows = page.locator('#account-grid-body tbody tr');
|
||||
const rowCount = await allRows.count();
|
||||
|
||||
// Find the row that has a hidden input for account (actual account rows)
|
||||
let accountRow = null;
|
||||
let accountRowIndex = 0;
|
||||
for (let i = 0; i < rowCount; i++) {
|
||||
const row = allRows.nth(i);
|
||||
const hasAccountInput = await row.locator('input[name*="transaction-account/account"]').count() > 0;
|
||||
if (hasAccountInput) {
|
||||
if (accountRowIndex === rowIndex) {
|
||||
accountRow = row;
|
||||
break;
|
||||
}
|
||||
accountRowIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!accountRow) {
|
||||
throw new Error(`Could not find account row at index ${rowIndex}`);
|
||||
}
|
||||
|
||||
// Find the hidden input for the account
|
||||
const hiddenInput = accountRow.locator('input[type="hidden"][name*="transaction-account/account"]').first();
|
||||
|
||||
// Get account IDs from test-info endpoint
|
||||
const testInfo = await getTestInfo(page);
|
||||
const accountKey = accountName === 'Test' ? 'test-account' : 'second-account';
|
||||
const accountId = testInfo.accounts[accountKey];
|
||||
|
||||
if (!accountId) {
|
||||
throw new Error(`Could not find account with name ${accountName}`);
|
||||
}
|
||||
|
||||
// Set the hidden input value and trigger change
|
||||
// Also update Alpine.js data to prevent it from overwriting our value
|
||||
await hiddenInput.evaluate((el: HTMLInputElement, value: string) => {
|
||||
// Set the DOM value
|
||||
el.value = value;
|
||||
|
||||
// Update Alpine.js component data
|
||||
const alpineEl = el.closest('[x-data]');
|
||||
if (alpineEl && (alpineEl as any).__x) {
|
||||
(alpineEl as any).__x.$data.value.value = parseInt(value);
|
||||
(alpineEl as any).__x.$data.value.label = 'Selected Account';
|
||||
}
|
||||
|
||||
// Also update any parent Alpine model (accountId)
|
||||
const rowEl = el.closest('tr[x-data]');
|
||||
if (rowEl && (rowEl as any).__x) {
|
||||
(rowEl as any).__x.$data.accountId = parseInt(value);
|
||||
}
|
||||
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}, accountId.toString());
|
||||
|
||||
// Wait for any HTMX updates
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
|
||||
async function findAccountRow(page: any, rowIndex: number) {
|
||||
const accountRows = page.locator('#account-grid-body tbody tr.account-row');
|
||||
const rowCount = await accountRows.count();
|
||||
|
||||
if (rowIndex >= rowCount) {
|
||||
throw new Error(`Could not find account row at index ${rowIndex}. Only ${rowCount} account rows found.`);
|
||||
}
|
||||
|
||||
return accountRows.nth(rowIndex);
|
||||
}
|
||||
|
||||
async function setAccountAmount(page: any, rowIndex: number, amount: string) {
|
||||
const row = await findAccountRow(page, rowIndex);
|
||||
const amountInput = row.locator('.account-amount-field');
|
||||
await amountInput.fill(amount);
|
||||
await amountInput.dispatchEvent('change');
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
|
||||
async function addNewAccount(page: any) {
|
||||
// Click the "New account" button
|
||||
await page.click('text=New account');
|
||||
|
||||
// Wait for the new row to be added
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
async function setAccountLocation(page: any, rowIndex: number, location: string) {
|
||||
const row = await findAccountRow(page, rowIndex);
|
||||
const locationSelect = row.locator('select[name*="location"]').first();
|
||||
|
||||
// If the option doesn't exist, add it (for testing invalid locations)
|
||||
const optionExists = await locationSelect.locator(`option[value="${location}"]`).count() > 0;
|
||||
if (!optionExists) {
|
||||
await locationSelect.evaluate((el: HTMLSelectElement, value: string) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = value;
|
||||
option.textContent = value;
|
||||
el.appendChild(option);
|
||||
}, location);
|
||||
}
|
||||
|
||||
await locationSelect.selectOption(location);
|
||||
await locationSelect.dispatchEvent('change');
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
|
||||
async function getAccountLocation(page: any, rowIndex: number): Promise<string> {
|
||||
const row = await findAccountRow(page, rowIndex);
|
||||
const locationSelect = row.locator('select[name*="location"]').first();
|
||||
return await locationSelect.inputValue();
|
||||
}
|
||||
|
||||
async function removeAllAccounts(page: any) {
|
||||
const accountRows = page.locator('#account-grid-body tbody tr.account-row');
|
||||
const rowCount = await accountRows.count();
|
||||
|
||||
for (let i = rowCount - 1; i >= 0; i--) {
|
||||
const row = accountRows.nth(i);
|
||||
const removeButton = row.locator('.account-remove-action');
|
||||
await removeButton.click();
|
||||
// Wait for the Alpine.js removal animation (500ms + buffer)
|
||||
await page.waitForTimeout(700);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveTransaction(page: any) {
|
||||
// Click the save button to submit the form via HTMX
|
||||
await page.locator('.wizard-save-action').click();
|
||||
|
||||
// Wait for the modal to close (longer timeout for parallel test load)
|
||||
await page.waitForSelector('#modal-holder[x-show="open"]', { state: 'hidden', timeout: 20000 });
|
||||
}
|
||||
|
||||
async function toggleToPercentMode(page: any) {
|
||||
const percentRadio = page.locator('input[name="step-params[amount-mode]"][value="%"]');
|
||||
await percentRadio.click();
|
||||
|
||||
// Wait for HTMX to swap the grid body
|
||||
await page.waitForResponse(response =>
|
||||
response.url().includes('/toggle-amount-mode') && response.status() === 200
|
||||
);
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
async function toggleToDollarMode(page: any) {
|
||||
const dollarRadio = page.locator('input[name="step-params[amount-mode]"][value="$"]');
|
||||
await dollarRadio.click();
|
||||
|
||||
// Wait for HTMX to swap the grid body
|
||||
await page.waitForResponse(response =>
|
||||
response.url().includes('/toggle-amount-mode') && response.status() === 200
|
||||
);
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
test.describe('Transaction Edit Shared Location', () => {
|
||||
test('should spread Shared location to client locations on save and display correctly on reopen', async ({ page }) => {
|
||||
// Use the second transaction to avoid interfering with other tests
|
||||
const transactionIndex = 1;
|
||||
|
||||
// Step 1: Open edit modal and add an account with Shared location
|
||||
await openEditModal(page, transactionIndex);
|
||||
|
||||
// Remove any existing accounts from previous tests
|
||||
await removeAllAccounts(page);
|
||||
|
||||
// Add a new account row
|
||||
await addNewAccount(page);
|
||||
|
||||
// Select the account
|
||||
await selectAccountFromTypeahead(page, 0, 'Test');
|
||||
|
||||
// Set location to Shared
|
||||
await setAccountLocation(page, 0, 'Shared');
|
||||
|
||||
// Set amount to $200 (the full transaction amount for the second transaction)
|
||||
await setAccountAmount(page, 0, '200');
|
||||
|
||||
// Save the transaction
|
||||
await saveTransaction(page);
|
||||
|
||||
// Step 2: Re-open and verify location is not "Shared" but the actual client location
|
||||
await openEditModal(page, transactionIndex);
|
||||
|
||||
// Wait for accounts to load
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Get the location of the first account
|
||||
const location = await getAccountLocation(page, 0);
|
||||
|
||||
// The location should be the actual client location ("DT" in test data), not "Shared"
|
||||
expect(location).not.toBe('Shared');
|
||||
expect(location).toBe('DT');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Transaction Edit Full Workflow', () => {
|
||||
test('should code transaction with vendor using percentage, then split 50/50, then switch to dollars', async ({ page }) => {
|
||||
// Step 1: Open edit modal and code with 100% to one account
|
||||
await openEditModal(page);
|
||||
|
||||
// Switch to percentage mode first (this re-renders the grid from server state)
|
||||
await toggleToPercentMode(page);
|
||||
|
||||
// Check if there's already an account from previous tests
|
||||
const allRows = page.locator('#account-grid-body tbody tr');
|
||||
const hasExistingAccount = await allRows.locator('input[name*="transaction-account/account"]').count() > 0;
|
||||
|
||||
if (!hasExistingAccount) {
|
||||
// Add a new account row if none exist
|
||||
await addNewAccount(page);
|
||||
}
|
||||
|
||||
// Select the account
|
||||
await selectAccountFromTypeahead(page, 0, 'Test');
|
||||
|
||||
// Set amount to 100%
|
||||
await setAccountAmount(page, 0, '100');
|
||||
|
||||
// Save the transaction
|
||||
await saveTransaction(page);
|
||||
|
||||
// Step 2: Re-open and split 50/50 with two accounts
|
||||
await openEditModal(page);
|
||||
|
||||
// Note: amount-mode is UI-only state, so it resets to $ when re-opening
|
||||
// Switch back to percentage mode
|
||||
await toggleToPercentMode(page);
|
||||
|
||||
// The existing account from step 1 should already be there
|
||||
// Change its amount from 100% to 50%
|
||||
await setAccountAmount(page, 0, '50');
|
||||
|
||||
// Add a second account at 50%
|
||||
await addNewAccount(page);
|
||||
await page.waitForTimeout(1000);
|
||||
await selectAccountFromTypeahead(page, 1, 'Second');
|
||||
await setAccountAmount(page, 1, '50');
|
||||
|
||||
// Save
|
||||
await saveTransaction(page);
|
||||
|
||||
// Step 3: Re-open and verify dollar amounts
|
||||
await openEditModal(page);
|
||||
|
||||
// The accounts should be persisted from the previous save
|
||||
// Wait for accounts to load
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify we're in dollar mode (default)
|
||||
const dollarRadio = page.locator('input[name="step-params[amount-mode]"][value="$"]');
|
||||
await expect(dollarRadio).toBeChecked();
|
||||
|
||||
// Verify amounts are in dollars (converted from percentages on save)
|
||||
const row0 = await findAccountRow(page, 0);
|
||||
const row1 = await findAccountRow(page, 1);
|
||||
|
||||
const amount0 = row0.locator('.account-amount-field');
|
||||
const amount1 = row1.locator('.account-amount-field');
|
||||
|
||||
// Each should be $50.00 (or close to it)
|
||||
const val0 = await amount0.inputValue();
|
||||
const val1 = await amount1.inputValue();
|
||||
|
||||
expect(parseFloat(val0)).toBeCloseTo(50.0, 1);
|
||||
expect(parseFloat(val1)).toBeCloseTo(50.0, 1);
|
||||
|
||||
// Save
|
||||
await saveTransaction(page);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Transaction Edit Validation', () => {
|
||||
test('should show validation error when account totals do not match transaction amount', async ({ page }) => {
|
||||
// Use the third transaction to avoid interference from other tests
|
||||
await openEditModal(page, 2);
|
||||
|
||||
// Stay in dollar mode (default)
|
||||
// Remove any existing accounts from previous tests
|
||||
await removeAllAccounts(page);
|
||||
await page.waitForTimeout(2000);
|
||||
// Add an account
|
||||
await addNewAccount(page);
|
||||
await selectAccountFromTypeahead(page, 0, 'Test');
|
||||
|
||||
// Set amount to $50 (which doesn't match the $300 transaction)
|
||||
await setAccountAmount(page, 0, '50');
|
||||
|
||||
// Try to save - this should fail because $50 != $300
|
||||
// Click the save button to submit the form via HTMX
|
||||
await page.locator('.wizard-save-action').click();
|
||||
|
||||
// Wait for the response (longer timeout for parallel test load)
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Modal should still be open (save failed)
|
||||
await expect(page.locator('#modal-holder[x-show="open"]')).toBeVisible();
|
||||
|
||||
// The form should still be present
|
||||
const form = page.locator('#wizard-form');
|
||||
await expect(form).toBeVisible();
|
||||
|
||||
// Verify the account row is still there with our $50 value
|
||||
const amountInput = page.locator('.account-amount-field').first();
|
||||
const value = await amountInput.inputValue();
|
||||
expect(parseFloat(value)).toBeCloseTo(50.0, 1);
|
||||
|
||||
// Verify the user-friendly error message is displayed
|
||||
const errorElement = page.locator('#form-errors .error-content');
|
||||
await expect(errorElement).toBeVisible();
|
||||
const errorText = await errorElement.textContent();
|
||||
expect(errorText).toContain('The total of your expense accounts ($50.00) must equal the transaction amount ($300.00)');
|
||||
});
|
||||
});
|
||||
|
||||
async function openEditModalForTransaction(page: any, description: string) {
|
||||
// Navigate to transactions page
|
||||
await page.goto('/transaction2');
|
||||
|
||||
// Wait for the table to load
|
||||
await page.waitForSelector('table tbody tr');
|
||||
|
||||
// Find the row with the specific description and click its edit button
|
||||
const row = page.locator('table tbody tr', { hasText: description }).first();
|
||||
const editButton = row.locator('button[hx-get*="/transaction2/"][hx-get*="/edit"]').first();
|
||||
await editButton.click();
|
||||
|
||||
// Wait for the modal to open
|
||||
await page.waitForSelector('#modal-holder[x-show="open"]', { state: 'visible' });
|
||||
await page.waitForSelector('#wizardmodal');
|
||||
|
||||
// Click Next to go to the links step (button says "Transaction Actions")
|
||||
await page.click('button:has-text("Transaction Actions")');
|
||||
|
||||
// Wait for the links step to load
|
||||
await page.waitForSelector('text=Transaction Actions', { state: 'visible' });
|
||||
}
|
||||
|
||||
async function selectVendorFromTypeahead(page: any, vendorName: string) {
|
||||
const testInfo = await getTestInfo(page);
|
||||
const vendorId = testInfo.accounts.vendor;
|
||||
|
||||
if (!vendorId) {
|
||||
throw new Error(`Could not find vendor with name ${vendorName}`);
|
||||
}
|
||||
|
||||
const vendorContainer = page.locator('div[hx-post*="edit-vendor-changed"]').first();
|
||||
const vendorHidden = vendorContainer.locator('input[type="hidden"]').first();
|
||||
|
||||
await vendorHidden.evaluate((el: HTMLInputElement, value: string) => {
|
||||
const newInput = document.createElement('input');
|
||||
newInput.type = 'hidden';
|
||||
newInput.name = el.name;
|
||||
newInput.value = value;
|
||||
el.parentNode.replaceChild(newInput, el);
|
||||
}, vendorId.toString());
|
||||
|
||||
await vendorContainer.evaluate((el: HTMLElement) => {
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
|
||||
await page.waitForResponse(response => response.url().includes('/edit-vendor-changed') && response.status() === 200);
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
test.describe('Transaction Edit Vendor Pre-population', () => {
|
||||
test('should start with no account rows when transaction has no accounts', async ({ page }) => {
|
||||
await openEditModal(page, 3);
|
||||
|
||||
await page.click('button:has-text("Manual")');
|
||||
await page.waitForSelector('#account-grid-body');
|
||||
|
||||
// Remove any existing accounts from previous tests
|
||||
await removeAllAccounts(page);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const accountRows = page.locator('#account-grid-body tbody tr.account-row');
|
||||
const rowCount = await accountRows.count();
|
||||
|
||||
expect(rowCount).toBe(0);
|
||||
});
|
||||
|
||||
test('should pre-populate default account when vendor is selected', async ({ page }) => {
|
||||
await openEditModal(page, 3);
|
||||
|
||||
await page.click('button:has-text("Manual")');
|
||||
await page.waitForSelector('#account-grid-body');
|
||||
|
||||
// Remove any existing accounts from previous tests
|
||||
await removeAllAccounts(page);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const accountRows = page.locator('#account-grid-body tbody tr.account-row');
|
||||
const initialRowCount = await accountRows.count();
|
||||
expect(initialRowCount).toBe(0);
|
||||
|
||||
await selectVendorFromTypeahead(page, 'Test Vendor');
|
||||
|
||||
const rowsAfterVendor = page.locator('#account-grid-body tbody tr.account-row');
|
||||
const rowCountAfter = await rowsAfterVendor.count();
|
||||
|
||||
expect(rowCountAfter).toBe(1);
|
||||
|
||||
const accountHidden = page.locator('input[type="hidden"][name*="transaction-account/account"]').first();
|
||||
const accountValue = await accountHidden.inputValue();
|
||||
|
||||
const testInfo = await getTestInfo(page);
|
||||
expect(accountValue).toBe(testInfo.accounts['test-account'].toString());
|
||||
|
||||
const amountInput = page.locator('.account-amount-field').first();
|
||||
const amountValue = await amountInput.inputValue();
|
||||
expect(parseFloat(amountValue)).toBeCloseTo(400.0, 1);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Transaction Link Date Display', () => {
|
||||
test('should show payment date when linking to payment', async ({ page }) => {
|
||||
await openEditModalForTransaction(page, 'Transaction for payment link');
|
||||
|
||||
// Click on "Link to payment" tab
|
||||
await page.click('button:has-text("Link to payment")');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify the payment option shows the date
|
||||
await expect(page.locator('#payment-matches')).toContainText('Available Payments');
|
||||
await expect(page.locator('#payment-matches')).toContainText('06/14/2023');
|
||||
});
|
||||
|
||||
test('should show invoice date when linking to unpaid invoice', async ({ page }) => {
|
||||
await openEditModalForTransaction(page, 'Transaction for unpaid invoice link');
|
||||
|
||||
// Click on "Link to unpaid invoices" tab
|
||||
await page.click('button:has-text("Link to unpaid invoices")');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify the invoice option shows the date
|
||||
await expect(page.locator('text=Available Unpaid Invoices')).toBeVisible();
|
||||
await expect(page.locator('text=UNPAID-001')).toBeVisible();
|
||||
await expect(page.locator('text=07/19/2023')).toBeVisible();
|
||||
});
|
||||
});
|
||||
209
e2e/transaction-navigation.spec.ts
Normal file
209
e2e/transaction-navigation.spec.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
async function navigateToTransactions(page: any, path: string = '/transaction2') {
|
||||
await page.setExtraHTTPHeaders({
|
||||
'x-clients': '"mine"'
|
||||
});
|
||||
await page.goto(path);
|
||||
await page.waitForSelector('table tbody tr');
|
||||
}
|
||||
|
||||
async function setAmountFilter(page: any, gte: string, lte: string) {
|
||||
const amountGte = page.locator('input[name="amount-gte"]').first();
|
||||
const amountLte = page.locator('input[name="amount-lte"]').first();
|
||||
|
||||
await amountGte.fill(gte);
|
||||
await amountLte.fill(lte);
|
||||
|
||||
// Trigger the filter form submission via change event
|
||||
await amountLte.dispatchEvent('change');
|
||||
|
||||
// Wait for HTMX to update the table and push URL
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
async function getTableRowCount(page: any): Promise<number> {
|
||||
const rows = page.locator('table tbody tr');
|
||||
return await rows.count();
|
||||
}
|
||||
|
||||
async function clickTransactionNavLink(page: any, linkText: string) {
|
||||
const link = page.locator(`a:has-text("${linkText}")`).first();
|
||||
await link.click({ force: true });
|
||||
|
||||
// Wait for navigation and table to load
|
||||
await page.waitForTimeout(1000);
|
||||
await page.waitForSelector('table tbody tr', { timeout: 10000 });
|
||||
}
|
||||
|
||||
test.describe('Transaction Navigation - Amount Filter Persistence', () => {
|
||||
test('should persist amount filter when navigating from All to Unapproved', async ({ page }) => {
|
||||
// Step 1: Navigate to All transactions page
|
||||
await navigateToTransactions(page, '/transaction2');
|
||||
|
||||
// Step 2: Set amount filter
|
||||
await setAmountFilter(page, '250', '');
|
||||
|
||||
// Step 3: Verify the URL updated with the filter
|
||||
await page.waitForURL(url => url.search.includes('amount-gte=250'), { timeout: 5000 });
|
||||
|
||||
// Step 4: Click the Unapproved nav link
|
||||
await clickTransactionNavLink(page, 'Unapproved');
|
||||
|
||||
// Step 5: Verify amount filter is preserved in URL after navigation
|
||||
const unapprovedUrl = page.url();
|
||||
expect(unapprovedUrl).toContain('amount-gte=250');
|
||||
|
||||
// Step 6: Verify filter still applies (only 1 row - the 300 transaction)
|
||||
const filteredCount = await getTableRowCount(page);
|
||||
expect(filteredCount).toBe(1);
|
||||
});
|
||||
|
||||
test('should persist amount filter when navigating from Unapproved to All', async ({ page }) => {
|
||||
// Step 1: Navigate to Unapproved page with amount filter already in URL
|
||||
await navigateToTransactions(page, '/transaction2/unapproved?amount-gte=200');
|
||||
|
||||
// Step 2: Click All nav link
|
||||
await clickTransactionNavLink(page, 'All');
|
||||
|
||||
// Step 3: Verify amount filter is preserved
|
||||
const allUrl = page.url();
|
||||
expect(allUrl).toContain('amount-gte=200');
|
||||
});
|
||||
|
||||
test('should persist amount filter when navigating to Client Review', async ({ page }) => {
|
||||
// Step 1: Navigate to All page and set amount filter
|
||||
await navigateToTransactions(page, '/transaction2');
|
||||
await setAmountFilter(page, '', '500');
|
||||
|
||||
// Step 2: Wait for URL to update
|
||||
await page.waitForURL(url => url.search.includes('amount-lte=500'), { timeout: 5000 });
|
||||
|
||||
// Step 3: Click Client Review nav link
|
||||
await clickTransactionNavLink(page, 'Client Review');
|
||||
|
||||
// Step 4: Verify filter persisted
|
||||
const feedbackUrl = page.url();
|
||||
expect(feedbackUrl).toContain('amount-lte=500');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Transaction Navigation - Date Filter Persistence', () => {
|
||||
test('should persist date-range preset when navigating between pages', async ({ page }) => {
|
||||
// Step 1: Navigate with date-range=all (includes 2022 test data)
|
||||
await navigateToTransactions(page, '/transaction2?date-range=all');
|
||||
|
||||
// Step 2: Click Unapproved nav link
|
||||
await clickTransactionNavLink(page, 'Unapproved');
|
||||
|
||||
// Step 3: Verify date-range persisted
|
||||
const unapprovedUrl = page.url();
|
||||
expect(unapprovedUrl).toContain('date-range=all');
|
||||
});
|
||||
});
|
||||
|
||||
async function setTextFilter(page: any, name: string, value: string) {
|
||||
const input = page.locator(`input[name="${name}"]`).first();
|
||||
await input.fill(value);
|
||||
await input.dispatchEvent('change');
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
test.describe('Transaction Filters - Description and Memo', () => {
|
||||
test('should filter by description with case-insensitive wildcard matching', async ({ page }) => {
|
||||
await navigateToTransactions(page, '/transaction2');
|
||||
|
||||
// Filter by "second" (lowercase) - should match "Second transaction"
|
||||
await setTextFilter(page, 'description', 'second');
|
||||
|
||||
// Wait for URL to update
|
||||
await page.waitForURL(url => url.search.includes('description=second'), { timeout: 5000 });
|
||||
|
||||
// Should show only 1 row (the "Second transaction")
|
||||
const rowCount = await getTableRowCount(page);
|
||||
expect(rowCount).toBe(1);
|
||||
|
||||
// Verify the row contains "Second transaction"
|
||||
const rowText = await page.locator('table tbody tr').first().textContent();
|
||||
expect(rowText).toContain('Second transaction');
|
||||
});
|
||||
|
||||
test('should filter by memo with case-insensitive wildcard matching', async ({ page }) => {
|
||||
await navigateToTransactions(page, '/transaction2');
|
||||
|
||||
// Filter by "rent" (lowercase) - should match "Monthly rent payment"
|
||||
await setTextFilter(page, 'memo', 'rent');
|
||||
|
||||
// Wait for URL to update
|
||||
await page.waitForURL(url => url.search.includes('memo=rent'), { timeout: 5000 });
|
||||
|
||||
// Should show only 1 row (the transaction with "Monthly rent payment" memo)
|
||||
const rowCount = await getTableRowCount(page);
|
||||
expect(rowCount).toBe(1);
|
||||
|
||||
// Verify the row contains "Test transaction" (the one with the rent memo)
|
||||
const rowText = await page.locator('table tbody tr').first().textContent();
|
||||
expect(rowText).toContain('Test transaction');
|
||||
});
|
||||
|
||||
test('should filter by description and memo together', async ({ page }) => {
|
||||
await navigateToTransactions(page, '/transaction2');
|
||||
|
||||
// Set both filters - should match "Test transaction" which has memo "Monthly rent payment"
|
||||
await setTextFilter(page, 'description', 'test');
|
||||
await setTextFilter(page, 'memo', 'rent');
|
||||
|
||||
// Wait for URL to update
|
||||
await page.waitForURL(url => url.search.includes('description=test') && url.search.includes('memo=rent'), { timeout: 5000 });
|
||||
|
||||
// Should show only 1 row
|
||||
const rowCount = await getTableRowCount(page);
|
||||
expect(rowCount).toBe(1);
|
||||
|
||||
// Verify it's the "Test transaction" row
|
||||
const rowText = await page.locator('table tbody tr').first().textContent();
|
||||
expect(rowText).toContain('Test transaction');
|
||||
});
|
||||
|
||||
test('should show no results when filter does not match', async ({ page }) => {
|
||||
await navigateToTransactions(page, '/transaction2');
|
||||
|
||||
// Filter by something that doesn't exist
|
||||
await setTextFilter(page, 'description', 'nonexistent');
|
||||
|
||||
// Wait for URL to update
|
||||
await page.waitForURL(url => url.search.includes('description=nonexistent'), { timeout: 5000 });
|
||||
|
||||
// Should show no rows
|
||||
const rowCount = await getTableRowCount(page);
|
||||
expect(rowCount).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Transaction Sort - Default Newest First', () => {
|
||||
test('should show transactions sorted by date descending by default', async ({ page }) => {
|
||||
await navigateToTransactions(page, '/transaction2');
|
||||
|
||||
// Verify no explicit sort in URL initially
|
||||
expect(page.url()).not.toContain('sort=');
|
||||
|
||||
// The default sort should be newest first (descending by date)
|
||||
// We can verify this by checking that clicking Date header toggles to asc
|
||||
const dateHeader = page.locator('th').filter({ hasText: 'Date' }).first();
|
||||
await dateHeader.click();
|
||||
|
||||
// Wait for HTMX to update
|
||||
await page.waitForTimeout(800);
|
||||
|
||||
// The URL should now have an explicit sort parameter (ascending because we toggled from default desc)
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).toContain('sort=date%3Aasc');
|
||||
|
||||
// Click again to toggle to descending
|
||||
await dateHeader.click();
|
||||
await page.waitForTimeout(800);
|
||||
|
||||
const toggledUrl = page.url();
|
||||
expect(toggledUrl).toContain('sort=date%3Adesc');
|
||||
});
|
||||
});
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn dollars= [amt1 amt2]
|
||||
(dollars-0? (- amt1 amt2) ))
|
||||
(dollars-0? (- amt1 amt2)))
|
||||
|
||||
(defn localize [d]
|
||||
(time/to-time-zone d (time/time-zone-for-id "America/Los_Angeles")))
|
||||
@@ -22,7 +22,6 @@
|
||||
(defn local-now []
|
||||
(localize (time/now)))
|
||||
|
||||
|
||||
(defn recent-date
|
||||
([]
|
||||
(recent-date 90))
|
||||
@@ -32,16 +31,16 @@
|
||||
(def excel-formatter (f/with-zone (f/formatter "MM/dd/yyyy") (time/time-zone-for-id "America/Los_Angeles")))
|
||||
(defn excel-date [d]
|
||||
(->> d
|
||||
(coerce/to-date-time)
|
||||
localize
|
||||
(f/unparse excel-formatter )))
|
||||
(coerce/to-date-time)
|
||||
localize
|
||||
(f/unparse excel-formatter)))
|
||||
|
||||
(def iso-formatter (f/with-zone (f/formatter "yyyy-MM-dd") (time/time-zone-for-id "America/Los_Angeles")))
|
||||
(defn iso-date [d]
|
||||
(->> d
|
||||
(coerce/to-date-time)
|
||||
localize
|
||||
(f/unparse iso-formatter )))
|
||||
(coerce/to-date-time)
|
||||
localize
|
||||
(f/unparse iso-formatter)))
|
||||
|
||||
(defn sales-orders-in-range [db client start end]
|
||||
(let [end (or end #inst "2050-01-01")]
|
||||
@@ -53,9 +52,6 @@
|
||||
[client start]
|
||||
[client end]))))
|
||||
|
||||
|
||||
|
||||
|
||||
(defn can-see-client? [identity client]
|
||||
(when (not client)
|
||||
(println "WARNING - permission checking for null client"))
|
||||
@@ -63,11 +59,9 @@
|
||||
((set (map :db/id (:user/clients identity))) (:db/id client))
|
||||
((set (map :db/id (:user/clients identity))) client)))
|
||||
|
||||
|
||||
(defn ->pattern [x]
|
||||
(. java.util.regex.Pattern (compile x java.util.regex.Pattern/CASE_INSENSITIVE)))
|
||||
|
||||
|
||||
(defn dom [^java.util.Date x]
|
||||
(-> x
|
||||
(.toInstant)
|
||||
@@ -85,8 +79,8 @@
|
||||
:let [c (entid db c)]
|
||||
r (seq (dc/index-range db
|
||||
:sales-order/client+date
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00")]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00")]))]
|
||||
[(:e r) (first (:v r)) (second (:v r))]))
|
||||
|
||||
(defn scan-charges [db clients start end]
|
||||
@@ -94,8 +88,8 @@
|
||||
:let [c (entid db c)]
|
||||
r (seq (dc/index-range db
|
||||
:charge/client+date
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00")]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00")]))]
|
||||
[(:e r) (first (:v r)) (second (:v r))]))
|
||||
|
||||
(defn scan-sales-refunds [db clients start end]
|
||||
@@ -103,8 +97,8 @@
|
||||
:let [c (entid db c)]
|
||||
r (seq (dc/index-range db
|
||||
:sales-refund/client+date
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00")]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00")]))]
|
||||
[(:e r) (first (:v r)) (second (:v r))]))
|
||||
|
||||
(defn scan-expected-deposits [db clients start end]
|
||||
@@ -112,8 +106,8 @@
|
||||
:let [c (entid db c)]
|
||||
r (seq (dc/index-range db
|
||||
:expected-deposit/client+date
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00")]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00")]))]
|
||||
[(:e r) (first (:v r)) (second (:v r))]))
|
||||
|
||||
(defn scan-cash-drawer-shifts [db clients start end]
|
||||
@@ -121,8 +115,8 @@
|
||||
:let [c (entid db c)]
|
||||
r (seq (dc/index-range db
|
||||
:cash-drawer-shift/client+date
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00")]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00")]))]
|
||||
[(:e r) (first (:v r)) (second (:v r))]))
|
||||
|
||||
(defn scan-invoices [db clients start end]
|
||||
@@ -130,17 +124,17 @@
|
||||
:let [c (entid db c)]
|
||||
r (seq (dc/index-range db
|
||||
:invoice/client+date
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00")]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00")]))]
|
||||
[(:e r) (first (:v r)) (second (:v r))]))
|
||||
|
||||
(defn scan-transactions [db clients start end]
|
||||
(for [c clients
|
||||
:let [c (entid db c)]
|
||||
r (seq (dc/index-range db
|
||||
:transaction/client+date
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
|
||||
:transaction/client+date
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00")]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00")]))]
|
||||
[(:e r) (first (:v r)) (second (:v r))]))
|
||||
|
||||
(defn scan-ledger [db clients start end]
|
||||
@@ -148,8 +142,8 @@
|
||||
:let [c (entid db c)]
|
||||
r (seq (dc/index-range db
|
||||
:journal-entry/client+date
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00")]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00")]))]
|
||||
[(:e r) (first (:v r)) (second (:v r))]))
|
||||
|
||||
(defn scan-payments [db clients start end]
|
||||
@@ -157,15 +151,14 @@
|
||||
:let [c (entid db c)]
|
||||
r (seq (dc/index-range db
|
||||
:payment/client+date
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
|
||||
[c (or start #inst "2001-01-01T08:00:00.000-00:00")]
|
||||
[c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00")]))]
|
||||
[(:e r) (first (:v r)) (second (:v r))]))
|
||||
|
||||
(defn ident [x]
|
||||
(:db/ident x))
|
||||
|
||||
(deftype Line [^Long id ^Long client-id ^Long account-id ^String location ^java.util.Date date ^Double debit ^Double credit ^Double running-balance]
|
||||
)
|
||||
(deftype Line [^Long id ^Long client-id ^Long account-id ^String location ^java.util.Date date ^Double debit ^Double credit ^Double running-balance])
|
||||
|
||||
(defmethod print-method Line [entity writer]
|
||||
(.write writer (format "Line %d: client:%d account:%d location:%s date:%s"
|
||||
@@ -175,18 +168,16 @@
|
||||
(.-location entity)
|
||||
(iso-date (.-date entity)))))
|
||||
|
||||
|
||||
(defn ->line [{[current-client current-account current-location current-date debit credit running-balance]
|
||||
:v
|
||||
id :e}]
|
||||
(Line. id current-client current-account current-location current-date debit credit running-balance)
|
||||
)
|
||||
(Line. id current-client current-account current-location current-date debit credit running-balance))
|
||||
|
||||
(defn compare-account [^Line l1 ^Line l2]
|
||||
|
||||
(defn compare-account [^Line l1 ^Line l2]
|
||||
|
||||
(let [a (compare (.-date l1) (.-date l2))]
|
||||
(if (not= 0 a)
|
||||
a
|
||||
a
|
||||
(compare (.-id l1) (.-id l2)))))
|
||||
|
||||
(defn account-sets [db client-id]
|
||||
@@ -194,7 +185,7 @@
|
||||
(seq)
|
||||
(map ->line)
|
||||
(partition-by (fn set-partition [^Line l]
|
||||
[(.-account-id l) (.-location l)]))) ]
|
||||
[(.-account-id l) (.-location l)])))]
|
||||
(->> running-balance-set
|
||||
(sort compare-account))))
|
||||
|
||||
@@ -205,35 +196,35 @@
|
||||
(take-while (fn until-date [^Line l]
|
||||
(let [^java.util.Date d (.-date l)]
|
||||
(<= (.compareTo ^java.util.Date d end) 0))))
|
||||
last) ]
|
||||
last)]
|
||||
:when (and z (.-id z))]
|
||||
[(.-client-id z) (.-account-id z) (.-location z) (.-date z) (.-running-balance z)]))
|
||||
|
||||
#_(doseq [[ n] (dc/q '[:find ?cd :where [?c :client/code ?cd] [?c :client/groups "NTG"]] (dc/db auto-ap.datomic/conn))]
|
||||
(println n)
|
||||
(dc/q '[:find ?code ?name ?afc ?an ?l ?d2 ?balance
|
||||
:in $ ?end ?group
|
||||
:where
|
||||
[(clj-time.coerce/to-date-time ?end) ?end2]
|
||||
[(iol-ion.query/localize ?end2) ?end3]
|
||||
[(clj-time.coerce/to-date ?end3) ?end4]
|
||||
(or
|
||||
[?c :client/groups ?group]
|
||||
[?c :client/code ?group])
|
||||
[?c :client/name ?name]
|
||||
[?c :client/code ?code]
|
||||
[?c :client/bank-accounts ?b]
|
||||
[(iol-ion.query/account-snapshot $ ?c ?end4) [?x ...]]
|
||||
[(untuple ?x) [_ ?a ?l ?date ?balance]]
|
||||
[(not= nil ?a)]
|
||||
[(iol-ion.query/excel-date ?date) ?d2]
|
||||
(or-join [?a ?afc ?an]
|
||||
(and [?a :account/name ?an]
|
||||
[?a :account/numeric-code ?afc])
|
||||
(and [?a :bank-account/name ?an]
|
||||
[?a :bank-account/numeric-code ?afc]))]
|
||||
(dc/db auto-ap.datomic/conn)
|
||||
#inst "2024-10-10" n))
|
||||
#_(doseq [[n] (dc/q '[:find ?cd :where [?c :client/code ?cd] [?c :client/groups "NTG"]] (dc/db auto-ap.datomic/conn))]
|
||||
(println n)
|
||||
(dc/q '[:find ?code ?name ?afc ?an ?l ?d2 ?balance
|
||||
:in $ ?end ?group
|
||||
:where
|
||||
[(clj-time.coerce/to-date-time ?end) ?end2]
|
||||
[(iol-ion.query/localize ?end2) ?end3]
|
||||
[(clj-time.coerce/to-date ?end3) ?end4]
|
||||
(or
|
||||
[?c :client/groups ?group]
|
||||
[?c :client/code ?group])
|
||||
[?c :client/name ?name]
|
||||
[?c :client/code ?code]
|
||||
[?c :client/bank-accounts ?b]
|
||||
[(iol-ion.query/account-snapshot $ ?c ?end4) [?x ...]]
|
||||
[(untuple ?x) [_ ?a ?l ?date ?balance]]
|
||||
[(not= nil ?a)]
|
||||
[(iol-ion.query/excel-date ?date) ?d2]
|
||||
(or-join [?a ?afc ?an]
|
||||
(and [?a :account/name ?an]
|
||||
[?a :account/numeric-code ?afc])
|
||||
(and [?a :bank-account/name ?an]
|
||||
[?a :bank-account/numeric-code ?afc]))]
|
||||
(dc/db auto-ap.datomic/conn)
|
||||
#inst "2024-10-10" n))
|
||||
|
||||
(defn detailed-account-snapshot
|
||||
([db client-id ^java.util.Date end]
|
||||
@@ -266,12 +257,11 @@
|
||||
:credits 0.0
|
||||
:current-balance 0.0})))]
|
||||
:when client-id]
|
||||
(do
|
||||
(do
|
||||
[client-id account-id location debits credits current-balance count sample]))))
|
||||
|
||||
|
||||
(comment
|
||||
(->>
|
||||
(comment
|
||||
(->>
|
||||
(detailed-account-snapshot (dc/db auto-ap.datomic/conn)
|
||||
(auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn)
|
||||
[:client/code "NGOP"])
|
||||
@@ -280,65 +270,65 @@
|
||||
(into #{})
|
||||
seq)
|
||||
|
||||
(account-snapshot (dc/db auto-ap.datomic/conn)
|
||||
(auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn)
|
||||
[:client/code "NGOP"])
|
||||
#inst "2022-01-01")
|
||||
(account-snapshot (dc/db auto-ap.datomic/conn)
|
||||
(auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn)
|
||||
[:client/code "NGOP"])
|
||||
#inst "2022-01-01")
|
||||
|
||||
(def orig (->> [:client/code "NGOP"]
|
||||
(auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn))
|
||||
(account-sets (dc/db auto-ap.datomic/conn))
|
||||
(mapcat (fn [ls]
|
||||
ls))
|
||||
(filter (fn [l] (nil? (.-location l))))
|
||||
(into #{})))
|
||||
(def orig (->> [:client/code "NGOP"]
|
||||
(auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn))
|
||||
(account-sets (dc/db auto-ap.datomic/conn))
|
||||
(mapcat (fn [ls]
|
||||
ls))
|
||||
(filter (fn [l] (nil? (.-location l))))
|
||||
(into #{})))
|
||||
|
||||
(.-location orig)
|
||||
(.-location orig)
|
||||
|
||||
(def orig (into [] (take 5000 (mapcat (fn [ls]
|
||||
(map #(.-id %) ls)) (account-sets (dc/db auto-ap.datomic/conn)
|
||||
(auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn)
|
||||
[:client/code "NGOP"]))))))
|
||||
(def orig (into [] (take 5000 (mapcat (fn [ls]
|
||||
(map #(.-id %) ls)) (account-sets (dc/db auto-ap.datomic/conn)
|
||||
(auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn)
|
||||
[:client/code "NGOP"]))))))
|
||||
|
||||
(def n (into [] (take 5000 (mapcat (fn [ls]
|
||||
(map #(.-id %) ls)) (account-sets (dc/db auto-ap.datomic/conn)
|
||||
(auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn)
|
||||
[:client/code "NGOP"]))))))
|
||||
|
||||
(def n (into [] (take 5000 (mapcat (fn [ls]
|
||||
(map #(.-id %) ls)) (account-sets (dc/db auto-ap.datomic/conn)
|
||||
(auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn)
|
||||
[:client/code "NGOP"]))))))
|
||||
|
||||
(= orig n)
|
||||
|
||||
#_(seq (dc/q '[:find ?c ?a ?l ?date ?balance
|
||||
:in $
|
||||
:where [?c :client/code "NGOP"]
|
||||
[(iol-ion.query/account-snapshot $ ?c #inst "2023-01-01") [?x ...]]
|
||||
[(untuple ?x) [_ ?a ?l ?date ?balance]]]
|
||||
(dc/db auto-ap.datomic/conn)))
|
||||
#_(seq (dc/q '[:find ?c ?a ?l ?date ?balance
|
||||
:in $
|
||||
:where [?c :client/code "NGOP"]
|
||||
[(iol-ion.query/account-snapshot $ ?c #inst "2023-01-01") [?x ...]]
|
||||
[(untuple ?x) [_ ?a ?l ?date ?balance]]]
|
||||
(dc/db auto-ap.datomic/conn)))
|
||||
|
||||
#_(->> (seq (dc/q '[:find ?code ?name ?afc ?an ?l ?d2 ?balance ?end4
|
||||
:in $ ?end ?group
|
||||
:where
|
||||
[(clj-time.coerce/to-date-time ?end) ?end2]
|
||||
[(iol-ion.query/localize ?end2) ?end3]
|
||||
[(clj-time.coerce/to-date ?end3) ?end4]
|
||||
(or
|
||||
[?c :client/groups ?group]
|
||||
[?c :client/code ?group])
|
||||
[?c :client/name ?name]
|
||||
[?c :client/code ?code]
|
||||
[?c :client/bank-accounts ?b]
|
||||
[(iol-ion.query/account-snapshot $ ?c ?end4) [?x ...]]
|
||||
[(untuple ?x) [_ ?a ?l ?date ?balance]]
|
||||
[(iol-ion.query/excel-date ?date) ?d2]
|
||||
[(not= nil ?a)]
|
||||
(or-join [?a ?afc ?an]
|
||||
(and [?a :account/name ?an]
|
||||
[?a :account/numeric-code ?afc])
|
||||
(and [?a :bank-account/name ?an]
|
||||
[?a :bank-account/numeric-code ?afc]))]
|
||||
(dc/db auto-ap.datomic/conn)
|
||||
#inst "2024-09-23"
|
||||
"NGKG"))
|
||||
(filter (fn [[_ _ afc]]
|
||||
(= 12990 afc)))
|
||||
(map (fn [[_ _ _ _ _ _ a]]
|
||||
(Math/round a)))))
|
||||
#_(->> (seq (dc/q '[:find ?code ?name ?afc ?an ?l ?d2 ?balance ?end4
|
||||
:in $ ?end ?group
|
||||
:where
|
||||
[(clj-time.coerce/to-date-time ?end) ?end2]
|
||||
[(iol-ion.query/localize ?end2) ?end3]
|
||||
[(clj-time.coerce/to-date ?end3) ?end4]
|
||||
(or
|
||||
[?c :client/groups ?group]
|
||||
[?c :client/code ?group])
|
||||
[?c :client/name ?name]
|
||||
[?c :client/code ?code]
|
||||
[?c :client/bank-accounts ?b]
|
||||
[(iol-ion.query/account-snapshot $ ?c ?end4) [?x ...]]
|
||||
[(untuple ?x) [_ ?a ?l ?date ?balance]]
|
||||
[(iol-ion.query/excel-date ?date) ?d2]
|
||||
[(not= nil ?a)]
|
||||
(or-join [?a ?afc ?an]
|
||||
(and [?a :account/name ?an]
|
||||
[?a :account/numeric-code ?afc])
|
||||
(and [?a :bank-account/name ?an]
|
||||
[?a :bank-account/numeric-code ?afc]))]
|
||||
(dc/db auto-ap.datomic/conn)
|
||||
#inst "2024-09-23"
|
||||
"NGKG"))
|
||||
(filter (fn [[_ _ afc]]
|
||||
(= 12990 afc)))
|
||||
(map (fn [[_ _ _ _ _ _ a]]
|
||||
(Math/round a)))))
|
||||
@@ -11,7 +11,6 @@
|
||||
(def pull-many iol-ion.utils/pull-many)
|
||||
(def remove-nils iol-ion.utils/remove-nils)
|
||||
|
||||
|
||||
;; TODO expected-deposit ledger entry
|
||||
#_(defmethod entity-change->ledger :expected-deposit
|
||||
[db [type id]]
|
||||
@@ -33,9 +32,6 @@
|
||||
:location "A"
|
||||
:account :account/ccp}]}))
|
||||
|
||||
|
||||
|
||||
|
||||
(defn regenerate-literals []
|
||||
(require 'com.github.ivarref.gen-fn)
|
||||
(spit
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
(:invoice/invoice-number invoice)
|
||||
(:invoice/client invoice)
|
||||
(:invoice/vendor invoice))))
|
||||
[ locked-until] (first (dc/q '[:find ?locked-until
|
||||
:in $ ?c
|
||||
:where [?c :client/locked-until ?locked-until]]
|
||||
db
|
||||
(:invoice/client invoice)))
|
||||
[locked-until] (first (dc/q '[:find ?locked-until
|
||||
:in $ ?c
|
||||
:where [?c :client/locked-until ?locked-until]]
|
||||
db
|
||||
(:invoice/client invoice)))
|
||||
is-locked? (cond
|
||||
(not locked-until) false
|
||||
(not (:invoice/date invoice)) true
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
(defn reset-rels [db e a vs]
|
||||
(assert (every? :db/id vs) (format "In order to reset attribute %s, every value must have :db/id" a))
|
||||
(let [ids (when-not (string? e)
|
||||
(->> (dc/q '[:find ?z
|
||||
:in $ ?e ?a
|
||||
:where [?e ?a ?z]]
|
||||
db e a)
|
||||
(map first)))
|
||||
(->> (dc/q '[:find ?z
|
||||
:in $ ?e ?a
|
||||
:where [?e ?a ?z]]
|
||||
db e a)
|
||||
(map first)))
|
||||
new-id-set (set (map :db/id vs))
|
||||
retract-ids (filter (complement new-id-set) ids)
|
||||
{is-component? :db/isComponent} (dc/pull db [:db/isComponent] a)
|
||||
@@ -16,6 +16,6 @@
|
||||
(-> []
|
||||
(into (map (fn [i] (if is-component?
|
||||
[:db/retractEntity i]
|
||||
[:db/retract e a i ])) retract-ids))
|
||||
[:db/retract e a i])) retract-ids))
|
||||
(into (map (fn [i] [:db/add e a i]) new-rels))
|
||||
(into (map (fn [i] [:upsert-entity i]) vs)))))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
(:require [datomic.api :as dc]))
|
||||
|
||||
(defn reset-scalars [db e a vs]
|
||||
|
||||
|
||||
(let [extant (when-not (string? e)
|
||||
(->> (dc/q '[:find ?z
|
||||
:in $ ?e ?a
|
||||
@@ -12,5 +12,5 @@
|
||||
retracts (filter (complement (set vs)) extant)
|
||||
new (filter (complement (set extant)) vs)]
|
||||
(-> []
|
||||
(into (map (fn [i] [:db/retract e a i ]) retracts))
|
||||
(into (map (fn [i] [:db/retract e a i]) retracts))
|
||||
(into (map (fn [i] [:db/add e a i]) new)))))
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
)
|
||||
(:import [java.util UUID]))
|
||||
|
||||
|
||||
(defn -random-tempid []
|
||||
(str (UUID/randomUUID)))
|
||||
|
||||
@@ -36,7 +35,6 @@
|
||||
;; :else
|
||||
;; v))
|
||||
|
||||
|
||||
(defn upsert-entity [db entity]
|
||||
(when-not (or (:db/id entity)
|
||||
(:db/ident entity))
|
||||
@@ -90,7 +88,7 @@
|
||||
ops
|
||||
|
||||
;; reset relationships if it's refs, and not a lookup (i.e., seq of maps, or empty seq)
|
||||
|
||||
|
||||
(and (sequential? v) (= :db.type/tuple (ident->value-type a)) (not (= :db.cardinality/many (ident->cardinality a))))
|
||||
(conj ops [:db/add e a v])
|
||||
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
|
||||
(defn -remove-nils [m]
|
||||
(let [result (reduce-kv
|
||||
(fn [m k v]
|
||||
(if (not (nil? v))
|
||||
(assoc m k v)
|
||||
m
|
||||
))
|
||||
{}
|
||||
m)]
|
||||
(fn [m k v]
|
||||
(if (not (nil? v))
|
||||
(assoc m k v)
|
||||
m))
|
||||
{}
|
||||
m)]
|
||||
(if (seq result)
|
||||
result
|
||||
nil)))
|
||||
@@ -33,41 +32,38 @@
|
||||
invoice-id)
|
||||
credit-invoice? (< (:invoice/total entity 0.0) 0.0)]
|
||||
(when-not (or
|
||||
(not (:invoice/total entity))
|
||||
(= true (:invoice/exclude-from-ledger entity))
|
||||
(= :import-status/pending (:db/ident (:invoice/import-status entity)))
|
||||
(= :invoice-status/voided (:db/ident (:invoice/status entity)))
|
||||
(< -0.001 (:invoice/total entity) 0.001))
|
||||
|
||||
(-remove-nils
|
||||
{:journal-entry/source "invoice"
|
||||
:journal-entry/client (:db/id (:invoice/client entity))
|
||||
:journal-entry/date (:invoice/date entity)
|
||||
:journal-entry/original-entity raw-invoice-id
|
||||
:journal-entry/vendor (:db/id (:invoice/vendor entity))
|
||||
:journal-entry/amount (Math/abs (:invoice/total entity))
|
||||
(not (:invoice/total entity))
|
||||
(= true (:invoice/exclude-from-ledger entity))
|
||||
(= :import-status/pending (:db/ident (:invoice/import-status entity)))
|
||||
(= :invoice-status/voided (:db/ident (:invoice/status entity)))
|
||||
(< -0.001 (:invoice/total entity) 0.001))
|
||||
|
||||
:journal-entry/line-items (into [(cond-> {:db/id (str raw-invoice-id "-" 0)
|
||||
:journal-entry-line/account :account/accounts-payable
|
||||
:journal-entry-line/location "A"
|
||||
}
|
||||
credit-invoice? (assoc :journal-entry-line/debit (Math/abs (:invoice/total entity)))
|
||||
(not credit-invoice?) (assoc :journal-entry-line/credit (Math/abs (:invoice/total entity))))]
|
||||
(map-indexed (fn [i ea]
|
||||
(cond->
|
||||
{:db/id (str raw-invoice-id "-" (inc i))
|
||||
:journal-entry-line/account (:db/id (:invoice-expense-account/account ea))
|
||||
:journal-entry-line/location (or (:invoice-expense-account/location ea) "HQ")
|
||||
}
|
||||
credit-invoice? (assoc :journal-entry-line/credit (Math/abs (:invoice-expense-account/amount ea)))
|
||||
(not credit-invoice?) (assoc :journal-entry-line/debit (Math/abs (:invoice-expense-account/amount ea)))))
|
||||
(:invoice/expense-accounts entity)))
|
||||
:journal-entry/cleared (and (< (:invoice/outstanding-balance entity) 0.01)
|
||||
(every? #(= :payment-status/cleared (:payment/status %)) (:invoice/payments entity))
|
||||
)}))))
|
||||
(-remove-nils
|
||||
{:journal-entry/source "invoice"
|
||||
:journal-entry/client (:db/id (:invoice/client entity))
|
||||
:journal-entry/date (:invoice/date entity)
|
||||
:journal-entry/original-entity raw-invoice-id
|
||||
:journal-entry/vendor (:db/id (:invoice/vendor entity))
|
||||
:journal-entry/amount (Math/abs (:invoice/total entity))
|
||||
|
||||
:journal-entry/line-items (into [(cond-> {:db/id (str raw-invoice-id "-" 0)
|
||||
:journal-entry-line/account :account/accounts-payable
|
||||
:journal-entry-line/location "A"}
|
||||
credit-invoice? (assoc :journal-entry-line/debit (Math/abs (:invoice/total entity)))
|
||||
(not credit-invoice?) (assoc :journal-entry-line/credit (Math/abs (:invoice/total entity))))]
|
||||
(map-indexed (fn [i ea]
|
||||
(cond->
|
||||
{:db/id (str raw-invoice-id "-" (inc i))
|
||||
:journal-entry-line/account (:db/id (:invoice-expense-account/account ea))
|
||||
:journal-entry-line/location (or (:invoice-expense-account/location ea) "HQ")}
|
||||
credit-invoice? (assoc :journal-entry-line/credit (Math/abs (:invoice-expense-account/amount ea)))
|
||||
(not credit-invoice?) (assoc :journal-entry-line/debit (Math/abs (:invoice-expense-account/amount ea)))))
|
||||
(:invoice/expense-accounts entity)))
|
||||
:journal-entry/cleared (and (< (:invoice/outstanding-balance entity) 0.01)
|
||||
(every? #(= :payment-status/cleared (:payment/status %)) (:invoice/payments entity)))}))))
|
||||
|
||||
(defn current-date [db]
|
||||
(let [ last-tx (dc/t->tx (dc/basis-t 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
|
||||
@@ -80,15 +76,15 @@
|
||||
invoice-id (or (-> with-invoice :tempids (get (:db/id invoice)))
|
||||
(:db/id invoice))
|
||||
journal-entry (invoice->journal-entry (:db-after with-invoice)
|
||||
invoice-id
|
||||
(:db/id invoice))
|
||||
client-id (-> (dc/pull (:db-after with-invoice)
|
||||
[{:invoice/client [:db/id]}]
|
||||
invoice-id
|
||||
(:db/id invoice))
|
||||
client-id (-> (dc/pull (:db-after with-invoice)
|
||||
[{:invoice/client [:db/id]}]
|
||||
invoice-id)
|
||||
:invoice/client
|
||||
:invoice/client
|
||||
:db/id)]
|
||||
(into upserted-entity
|
||||
(if journal-entry
|
||||
(if journal-entry
|
||||
[[:upsert-ledger journal-entry]]
|
||||
[[:db/retractEntity [:journal-entry/original-entity (:db/id invoice)]]
|
||||
{:db/id client-id
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
next-jel (->> (dc/index-pull db {:index :avet
|
||||
:selector [:db/id :journal-entry-line/client+account+location+date]
|
||||
:start [:journal-entry-line/client+account+location+date
|
||||
(:journal-entry-line/client+account+location+date jel)
|
||||
(:journal-entry-line/client+account+location+date jel)
|
||||
(:db/id jel)]})
|
||||
(take-while (fn line-must-match-client-account-location [result]
|
||||
(and
|
||||
@@ -24,9 +24,8 @@
|
||||
|
||||
(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))
|
||||
(let [last-tx (dc/t->tx (dc/basis-t db))
|
||||
[[date]] (seq (dc/q '[:find ?ti :in $ ?tx
|
||||
:where [?tx :db/txInstant ?ti]]
|
||||
db
|
||||
@@ -51,7 +50,7 @@
|
||||
(let [extant-entry (or (when-let [original-entity (:journal-entry/original-entity ledger-entry)]
|
||||
(dc/pull db extant-read [:journal-entry/original-entity original-entity]))
|
||||
(when-let [external-id (:journal-entry/external-id ledger-entry)]
|
||||
(dc/pull db extant-read [:journal-entry/external-id external-id]))) ]
|
||||
(dc/pull db extant-read [:journal-entry/external-id external-id])))]
|
||||
|
||||
(cond->
|
||||
[[:upsert-entity (into (-> ledger-entry
|
||||
@@ -59,11 +58,11 @@
|
||||
(:db/id ledger-entry)
|
||||
(:db/id extant-entry)
|
||||
(-random-tempid)))
|
||||
(update :journal-entry/line-items
|
||||
(update :journal-entry/line-items
|
||||
(fn [lis]
|
||||
(mapv #(-> %
|
||||
(assoc :journal-entry-line/date (:journal-entry/date ledger-entry))
|
||||
(assoc :journal-entry-line/client (:journal-entry/client ledger-entry)))
|
||||
(assoc :journal-entry-line/client (:journal-entry/client ledger-entry)))
|
||||
lis)))))]
|
||||
{:db/id (:journal-entry/client ledger-entry)
|
||||
:client/ledger-last-change (current-date db)}])))
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
(update m (:db/ident (:ledger-mapped/ledger-side item)) (fnil + 0.0) (:ledger-mapped/amount item 0.0)))
|
||||
{:account account}
|
||||
acc-items))))
|
||||
_ (clojure.pprint/pprint aggregated)
|
||||
line-items (mapv (fn [{:keys [account] :as m}]
|
||||
(cond-> {:db/id (str (java.util.UUID/randomUUID))
|
||||
:journal-entry-line/account account
|
||||
@@ -27,11 +26,9 @@
|
||||
(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-debits (reduce + 0.0 (map #(get % :ledger-side/debit 0.0) aggregated))
|
||||
total-credits (reduce + 0.0 (map #(get % :ledger-side/credit 0.0) aggregated))
|
||||
_ (clojure.pprint/pprint [total-debits total-credits])
|
||||
]
|
||||
total-credits (reduce + 0.0 (map #(get % :ledger-side/credit 0.0) aggregated))]
|
||||
(when (and (seq line-items)
|
||||
(= (Math/round (* 1000 total-debits))
|
||||
(Math/round (* 1000 total-credits))))
|
||||
@@ -60,11 +57,10 @@ _ (clojure.pprint/pprint [total-debits total-credits])
|
||||
journal-entry (summary->journal-entry db-after summary-id)]
|
||||
upserted-summary
|
||||
#_(into upserted-summary
|
||||
(if journal-entry
|
||||
[[:upsert-ledger journal-entry]]
|
||||
(concat
|
||||
[[:db/retractEntity [:journal-entry/original-entity (:db/id summary)]]]
|
||||
(if journal-entry
|
||||
[[:upsert-ledger journal-entry]]
|
||||
(concat
|
||||
[[:db/retractEntity [:journal-entry/original-entity (:db/id summary)]]]
|
||||
|
||||
|
||||
(when client-id [{:db/id client-id
|
||||
:client/ledger-last-change (current-date db)}]))))))
|
||||
(when client-id [{:db/id client-id
|
||||
:client/ledger-last-change (current-date db)}]))))))
|
||||
|
||||
@@ -81,73 +81,70 @@
|
||||
[[:upsert-ledger journal-entry]]
|
||||
[[:db/retractEntity [:journal-entry/original-entity (:db/id transaction)]]]))))
|
||||
|
||||
|
||||
#_(comment
|
||||
|
||||
;; If transactions are failing, it is likely that there are multiple bank accounts linked
|
||||
;; to yodlee or plaid. here is how i debugged
|
||||
(upsert-transaction (dc/db auto-ap.datomic/conn) {:transaction/matched-rule 17592233159891,
|
||||
:db/id "34411061-4656-4e77-8cc0-2f2769b4324c",
|
||||
:transaction/status "POSTED",
|
||||
:transaction/description-original "Rotten Robbie #03",
|
||||
:transaction/approval-status {:db/id 17592231963877,
|
||||
:db/ident :transaction-approval-status/approved},
|
||||
:transaction/plaid-merchant {:db/id "223ceae4-d9e7-4e7f-92be-4fb00676088b",
|
||||
:plaid-merchant/name "Rotten Robbie"},
|
||||
:transaction/bank-account 17592232681223,
|
||||
:transaction/vendor 17592232627053,
|
||||
:transaction/date #inst "2024-02-24T08:00:00Z",
|
||||
:transaction/client 17592232577980,
|
||||
:transaction/id "11a4a13e713d63f476009027e9a53e217e13d0192a37df8ab96c0eed4bdbe996",
|
||||
:transaction/amount -84.43,
|
||||
:transaction/accounts [{:db/id "cad8463f-2dfe-47dc-ab17-831e87a633d5",
|
||||
:transaction-account/account 17592231963549,
|
||||
:transaction-account/location "CB",
|
||||
:transaction-account/amount 84.43}],
|
||||
:transaction/raw-id "gQypbv5946F08op74wZmidDg8qD8Q1fM6gEBP"})
|
||||
(upsert-transaction (dc/db auto-ap.datomic/conn) {:transaction/matched-rule 17592233159891,
|
||||
:db/id "34411061-4656-4e77-8cc0-2f2769b4324c",
|
||||
:transaction/status "POSTED",
|
||||
:transaction/description-original "Rotten Robbie #03",
|
||||
:transaction/approval-status {:db/id 17592231963877,
|
||||
:db/ident :transaction-approval-status/approved},
|
||||
:transaction/plaid-merchant {:db/id "223ceae4-d9e7-4e7f-92be-4fb00676088b",
|
||||
:plaid-merchant/name "Rotten Robbie"},
|
||||
:transaction/bank-account 17592232681223,
|
||||
:transaction/vendor 17592232627053,
|
||||
:transaction/date #inst "2024-02-24T08:00:00Z",
|
||||
:transaction/client 17592232577980,
|
||||
:transaction/id "11a4a13e713d63f476009027e9a53e217e13d0192a37df8ab96c0eed4bdbe996",
|
||||
:transaction/amount -84.43,
|
||||
:transaction/accounts [{:db/id "cad8463f-2dfe-47dc-ab17-831e87a633d5",
|
||||
:transaction-account/account 17592231963549,
|
||||
:transaction-account/location "CB",
|
||||
:transaction-account/amount 84.43}],
|
||||
:transaction/raw-id "gQypbv5946F08op74wZmidDg8qD8Q1fM6gEBP"})
|
||||
|
||||
["upsert-transaction"]
|
||||
(user/init-repl)
|
||||
["upsert-transaction"]
|
||||
(user/init-repl)
|
||||
|
||||
(def my-transaction {:transaction/bank-account 17592232681223,
|
||||
:transaction/date #inst "2024-02-24T08:00:00.000-00:00",
|
||||
:transaction/matched-rule 17592233159891,
|
||||
:transaction/client 17592232577980,
|
||||
:transaction/status "POSTED",
|
||||
:transaction/plaid-merchant
|
||||
{:plaid-merchant/name "Rotten Robbie", :db/id "b2776792-9e2b-46e8-a9c8-bf80abea359e"},
|
||||
:db/id "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc",
|
||||
:transaction/id "11a4a13e713d63f476009027e9a53e217e13d0192a37df8ab96c0eed4bdbe996",
|
||||
:transaction/description-original "Rotten Robbie #03",
|
||||
:transaction/approval-status {:db/id 17592231963877, :db/ident :transaction-approval-status/approved}, :transaction/amount -84.43,
|
||||
:transaction/accounts [{:db/id "c402c7b3-c11b-484b-b670-bd48f79a3e5f", :transaction-account/account 17592231963549, :transaction-account/amount 84.43, :transaction-account/location "CB"}],
|
||||
:transaction/raw-id "gQypbv5946F08op74wZmidDg8qD8Q1fM6gEBP",
|
||||
:transaction/vendor 17592232627053})
|
||||
(def my-transaction {:transaction/bank-account 17592232681223,
|
||||
:transaction/date #inst "2024-02-24T08:00:00.000-00:00",
|
||||
:transaction/matched-rule 17592233159891,
|
||||
:transaction/client 17592232577980,
|
||||
:transaction/status "POSTED",
|
||||
:transaction/plaid-merchant
|
||||
{:plaid-merchant/name "Rotten Robbie", :db/id "b2776792-9e2b-46e8-a9c8-bf80abea359e"},
|
||||
:db/id "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc",
|
||||
:transaction/id "11a4a13e713d63f476009027e9a53e217e13d0192a37df8ab96c0eed4bdbe996",
|
||||
:transaction/description-original "Rotten Robbie #03",
|
||||
:transaction/approval-status {:db/id 17592231963877, :db/ident :transaction-approval-status/approved}, :transaction/amount -84.43,
|
||||
:transaction/accounts [{:db/id "c402c7b3-c11b-484b-b670-bd48f79a3e5f", :transaction-account/account 17592231963549, :transaction-account/amount 84.43, :transaction-account/location "CB"}],
|
||||
:transaction/raw-id "gQypbv5946F08op74wZmidDg8qD8Q1fM6gEBP",
|
||||
:transaction/vendor 17592232627053})
|
||||
|
||||
(def my-journal {:journal-entry/alternate-description "Rotten Robbie #03",
|
||||
:journal-entry/date #inst "2024-02-24T08:00:00.000-00:00",
|
||||
:journal-entry/original-entity "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc",
|
||||
:journal-entry/client 17592232577980,
|
||||
:journal-entry/line-items [{:journal-entry-line/credit 84.43, :journal-entry-line/account 17592232681223, :db/id "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-0", :journal-entry-line/location "A"}
|
||||
{:journal-entry-line/account 17592231963549, :db/id "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-1", :journal-entry-line/debit 84.43, :journal-entry-line/location "CB"}
|
||||
{:journal-entry-line/account 17592231963549, :db/id "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-2", :journal-entry-line/debit 84.43, :journal-entry-line/location "CB"}],
|
||||
:journal-entry/source "transaction",
|
||||
:journal-entry/cleared true,
|
||||
:journal-entry/amount 84.43,
|
||||
:journal-entry/vendor 17592232627053})
|
||||
(dc/pull (dc/db auto-ap.datomic/conn) '[*] [:transaction/id "11a4a13e713d63f476009027e9a53e217e13d0192a37df8ab96c0eed4bdbe996"])
|
||||
wl
|
||||
(user/init-repl)
|
||||
(def my-journal {:journal-entry/alternate-description "Rotten Robbie #03",
|
||||
:journal-entry/date #inst "2024-02-24T08:00:00.000-00:00",
|
||||
:journal-entry/original-entity "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc",
|
||||
:journal-entry/client 17592232577980,
|
||||
:journal-entry/line-items [{:journal-entry-line/credit 84.43, :journal-entry-line/account 17592232681223, :db/id "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-0", :journal-entry-line/location "A"}
|
||||
{:journal-entry-line/account 17592231963549, :db/id "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-1", :journal-entry-line/debit 84.43, :journal-entry-line/location "CB"}
|
||||
{:journal-entry-line/account 17592231963549, :db/id "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-2", :journal-entry-line/debit 84.43, :journal-entry-line/location "CB"}],
|
||||
:journal-entry/source "transaction",
|
||||
:journal-entry/cleared true,
|
||||
:journal-entry/amount 84.43,
|
||||
:journal-entry/vendor 17592232627053})
|
||||
(dc/pull (dc/db auto-ap.datomic/conn) '[*] [:transaction/id "11a4a13e713d63f476009027e9a53e217e13d0192a37df8ab96c0eed4bdbe996"])
|
||||
wl
|
||||
(user/init-repl)
|
||||
|
||||
(or (when-let [original-entity (:journal-entry/original-entity my-journal)]
|
||||
(dc/pull (dc/db auto-ap.datomic/conn) iol-ion.tx.upsert-ledger/extant-read [:journal-entry/original-entity original-entity]))
|
||||
(when-let [external-id (:journal-entry/external-id my-journal)]
|
||||
(dc/pull (dc/db auto-ap.datomic/conn) iol-ion.tx.upsert-ledger/extant-read [:journal-entry/external-id external-id])))
|
||||
(or (when-let [original-entity (:journal-entry/original-entity my-journal)]
|
||||
(dc/pull (dc/db auto-ap.datomic/conn) iol-ion.tx.upsert-ledger/extant-read [:journal-entry/original-entity original-entity]))
|
||||
(when-let [external-id (:journal-entry/external-id my-journal)]
|
||||
(dc/pull (dc/db auto-ap.datomic/conn) iol-ion.tx.upsert-ledger/extant-read [:journal-entry/external-id external-id])))
|
||||
|
||||
@(dc/transact auto-ap.datomic/conn [[:upsert-entity my-transaction]
|
||||
[:upsert-ledger my-journal]])
|
||||
@(dc/transact auto-ap.datomic/conn [[:upsert-entity my-transaction]
|
||||
[:upsert-ledger my-journal]])
|
||||
|
||||
(auto-ap.datomic/pull-attr (dc/db auto-ap.datomic/conn) :bank-account/code 17592232681223)
|
||||
(auto-ap.datomic/pull-attr (dc/db auto-ap.datomic/conn) :bank-account/code 17592232681228)
|
||||
|
||||
)
|
||||
(auto-ap.datomic/pull-attr (dc/db auto-ap.datomic/conn) :bank-account/code 17592232681223)
|
||||
(auto-ap.datomic/pull-attr (dc/db auto-ap.datomic/conn) :bank-account/code 17592232681228))
|
||||
@@ -10,11 +10,11 @@
|
||||
(by f identity xs))
|
||||
([f fv xs]
|
||||
(reduce
|
||||
#(assoc %1 (f %2) (fv %2))
|
||||
{}
|
||||
xs)))
|
||||
#(assoc %1 (f %2) (fv %2))
|
||||
{}
|
||||
xs)))
|
||||
|
||||
(defn pull-many [db read ids ]
|
||||
(defn pull-many [db read ids]
|
||||
(->> (dc/q '[:find (pull ?e r)
|
||||
:in $ [?e ...] r]
|
||||
db
|
||||
@@ -24,13 +24,12 @@
|
||||
|
||||
(defn remove-nils [m]
|
||||
(let [result (reduce-kv
|
||||
(fn [m k v]
|
||||
(if (not (nil? v))
|
||||
(assoc m k v)
|
||||
m
|
||||
))
|
||||
{}
|
||||
m)]
|
||||
(fn [m k v]
|
||||
(if (not (nil? v))
|
||||
(assoc m k v)
|
||||
m))
|
||||
{}
|
||||
m)]
|
||||
(if (seq result)
|
||||
result
|
||||
nil)))
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"agent": {
|
||||
"clojure-author": {
|
||||
"prompt": "You are an expert Clojure developer. Follow these rules:\n\nStructural Editing: Use the clojure-mcp tools for all code changes. When editing clojure, you may only use clojure_edit, clojure_edit_replace_sexp, file_edit, file_write, for modifications from the clojure mcp server. You should also prefer to use read_file from the clojure mcp server. Never use\n sed, Write, or raw text replacement for Clojure files. Use clj-repair-parens (via clojure_mcp_paren_repair) whenever a file has unbalanced delimiters\n before making other edits.\n Code Style: Write pure functions by default. Avoid side effects, mutable state, and overly clever code. Favor let bindings over nested calls. Keep\n functions small and composable.\nKnowledge: When you need to verify a library API, standard library behavior, or Clojure semantics, consult context7 first. Use web search as a\n fallback when context7 lacks coverage.\n Evaluation: Use clojure_mcp_clojure_eval to test expressions and verify behavior before suggesting code changes.",
|
||||
"permission": {"edit": "deny", "bash": "deny"}
|
||||
}
|
||||
},
|
||||
"command": {
|
||||
"resolve_pr_parallel": {
|
||||
"description": "Resolve all PR comments using parallel processing",
|
||||
@@ -108,7 +114,16 @@
|
||||
"url": "https://mcp.context7.com/mcp",
|
||||
"enabled": true
|
||||
},
|
||||
|
||||
"clojure-mcp": {
|
||||
"type": "local",
|
||||
"command": ["clojure", "-Tmcp", "start", ":config-profile", ":cli-assist"],
|
||||
"enabled": true
|
||||
} ,
|
||||
"tavily": {
|
||||
"type": "remote",
|
||||
"url": "https://mcp.tavily.com/mcp/?tavilyApiKey=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",
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"permission": {
|
||||
"read": "allow",
|
||||
|
||||
98
package-lock.json
generated
98
package-lock.json
generated
@@ -26,6 +26,7 @@
|
||||
"recharts": "^2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.60.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
@@ -189,6 +190,22 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
|
||||
"integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.60.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
@@ -1916,6 +1933,53 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
|
||||
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.60.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
|
||||
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright/node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.35",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
|
||||
@@ -3335,6 +3399,15 @@
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"@playwright/test": {
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
|
||||
"integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"playwright": "1.60.0"
|
||||
}
|
||||
},
|
||||
"@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
@@ -4655,6 +4728,31 @@
|
||||
"find-up": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"playwright": {
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
|
||||
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.60.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
|
||||
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.35",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
|
||||
|
||||
@@ -24,12 +24,16 @@
|
||||
"recharts": "^2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.60.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:server": "clojure -M:test -m auto-ap.test-server"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
26
playwright.config.ts
Normal file
26
playwright.config.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: 'html',
|
||||
use: {
|
||||
baseURL: 'http://localhost:3333',
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
webServer: {
|
||||
command: 'lein run -m auto-ap.test-server',
|
||||
url: 'http://localhost:3333/test-info',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120000,
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -119,6 +119,7 @@
|
||||
:aliases {"build" ["do" ["uberjar"]]
|
||||
#_#_"fig:dev" ["run" "-m" "figwheel.main" "-b" "dev" "-r"]
|
||||
"build-dev" ["trampoline" "run" "-m" "figwheel.main" "-b" "dev" "-r"]
|
||||
"mcp-repl" ["trampoline" "run" "-m" "dev-mcp"]
|
||||
#_#_"fig:min" ["run" "-m" "figwheel.main" "-O" "whitespace" "-bo" "min"]}
|
||||
|
||||
|
||||
|
||||
652
resources/InvoicesList (45).csv
Normal file
652
resources/InvoicesList (45).csv
Normal file
@@ -0,0 +1,652 @@
|
||||
Invoice,Amount Due,Original Amount,Invoice Date,Invoice Due Date,Invoice Status,Ship To Name,Ship To Number,Bill To Name,Bill To Number,Line Item,Item Description,Original Quantity,Current Quantity,Unit Price,Tax Amount,Total Amount,Unit of Measure,Weight,Split Item Indicator
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7661388","LID PLAS FLAT F/12-22 OZ","1","1","25.41","0","25.41","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7452585","NAPKIN 2PLY INTR FOLD 6.3X8.26","1","1","22.32","0","22.32","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4613026","CUP PORTION PLAS CLR 1.50 OZ","1","1","26.15","0","26.15","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","1","1","43.99","0","43.99","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","1","1","41.25","0","41.25","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354127","CUP PAPER COLD 22 OZ LOGO NTG","1","1","47.6","0","47.6","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354120","CONTAINER PAPER 1/30 OZ NTG","1","1","30.6","0","30.6","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354119","CONTAINER PAPER 4/110OZ NTG","1","1","27.6","0","27.6","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","3","3","37.6","0","112.8","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","2","2","46.07","0","92.14","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5723808","WRAP DELI WHT 12X12 GRS RESIST","1","0","24.46","0","24.46","N","0.0","S"
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8492330","WATER PURIFIED BTL PET LSE DW","1","1","20.56","0","20.56","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7189422","SODA CHERRY VISSINADA GRK PLAS","1","1","14.84","0","14.84","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9910355","SODA LEMON LEMONADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7206974","JUICE CONC STRAWB DRAGON","1","1","172.37","0","172.37","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7459969","SYRUP DR PPR DIET BIB","1","1","62.48","0","62.48","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4273553","SYRUP DR PEPPER BIB","1","1","117.3","0","117.3","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9911236","SPICE OREGANO LEAF RUBBED","1","1","79.83","0","79.83","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7296407","GLOVE NITRILE BLK PEDRFREE LRG","1","1","39.41","3.85","39.41","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7301949","RICE MIX NICKS","1","1","31.07","0","31.07","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","2","2","54.84","0","109.68","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","5","5","23.65","0","118.25","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108399","DRESSING VINAIGRETTE LOGO","1","1","75.49","0","75.49","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7360056","DIP GARLIC TOUM","1","1","29.4","0","29.4","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","1","1","38.85","0","38.85","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","8","8","26.36","0","210.88","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","2","2","56.72","0","113.44","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","2","2","95.98","0","191.96","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","3","3","89.32","0","267.96","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","2","1","54.82","0","109.64","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455027","SPANAKOPITA SPINACH COOKED","2","2","69.37","0","138.74","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","404.25","Y","53.5",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7211838","PORK SLI GYRO CONE","1","1","87.76","0","87.76","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","9","9","86.97","0","782.73","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","7","7","92.53","0","647.71","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","2","2","49.53","0","99.06","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","1","1","57.66","0","57.66","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7274591","APTZR VEG FALAFEL PUCK HALAL","1","1","97.94","0","97.94","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9477498","ALLOWANCE FOR DROP SIZE","1","1","-7.32","0","-7.32","N","0.0",
|
||||
"850081745","$4,631.61","$4,631.61","2026-04-02","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","4.17","0","4.17","N","0.0",
|
||||
"850087188","$434.66","$434.66","2026-04-04","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","1","1","46.07","0","46.07","N","0.0",
|
||||
"850087188","$434.66","$434.66","2026-04-04","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","1","1","41.25","0","41.25","N","0.0",
|
||||
"850087188","$434.66","$434.66","2026-04-04","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8936379","SODA PEPSI COLA","1","1","34.01","0","34.01","N","0.0",
|
||||
"850087188","$434.66","$434.66","2026-04-04","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0635005","SODA COLA PEPSI ZERO SUGAR","1","1","34.01","0","34.01","N","0.0",
|
||||
"850087188","$434.66","$434.66","2026-04-04","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1336403","SODA DR PPR REG","1","1","34.01","0","34.01","N","0.0",
|
||||
"850087188","$434.66","$434.66","2026-04-04","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","1","1","56.72","0","56.72","N","0.0",
|
||||
"850087188","$434.66","$434.66","2026-04-04","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171302","DRESSING MARINADE SOUVLAKI","1","1","72.44","0","72.44","N","0.0",
|
||||
"850087188","$434.66","$434.66","2026-04-04","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213703","SAUCE SPICY YOGURT LOGO","1","1","95.98","0","95.98","N","0.0",
|
||||
"850087188","$434.66","$434.66","2026-04-04","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706813","FORK PLAS PP X-HVY BLK","1","1","16.03","0","16.03","N","0.0",
|
||||
"850087188","$434.66","$434.66","2026-04-04","2026-05-29","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","0.54","0","0.54","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7452585","NAPKIN 2PLY INTR FOLD 6.3X8.26","1","1","22.32","0","22.32","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0191567","STRAW PLAS TRANS JMB WRPD 7.75","1","0","5.09","0","5.09","N","0.0","S"
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","1","1","43.99","0","43.99","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7131355","CUP PLAS CLR RPET 12-14 OZ","0","0","28.25","0","0","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354120","CONTAINER PAPER 1/30 OZ NTG","1","1","30.6","0","30.6","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354119","CONTAINER PAPER 4/110OZ NTG","1","1","27.6","0","27.6","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","2","2","37.6","0","75.2","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","1","1","46.07","0","46.07","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1428869","TEA ICED UNSWEET PURELEAF","1","1","21.84","0","21.84","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8492330","WATER PURIFIED BTL PET LSE DW","1","1","20.56","0","20.56","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7153421","SYRUP COLA PEPSI ZERO BIB","0","0","71.94","0","0","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9495920","SYRUP LEMONADE PNK BIB","1","1","115.95","0","115.95","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6937767","FOIL ALMN ROLL HVY WGT 500 FT","1","1","39.46","3.85","39.46","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7301949","RICE MIX NICKS","1","1","31.07","0","31.07","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","4","4","23.65","0","94.6","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108399","DRESSING VINAIGRETTE LOGO","1","1","75.49","0","75.49","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5934302","OIL OLIVE BLEND 80/20","1","1","78.45","0","78.45","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4062337","BEAN GARBANZO FCY NO SULFITE","1","1","31.8","0","31.8","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4372932","PEPPER BANANA MILD RING","1","1","40.85","0","40.85","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7360056","DIP GARLIC TOUM","1","1","29.4","0","29.4","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4693715","PASTE HERB HARISSA MOROCCAN","1","1","28.05","0","28.05","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","2","2","38.85","0","77.7","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171302","DRESSING MARINADE SOUVLAKI","1","1","72.44","0","72.44","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","4","4","26.36","0","105.44","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","1","1","56.72","0","56.72","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7253487","CHEESE FETA RW","1","1","2.428","0","93.14","Y","38.36",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","1","1","95.98","0","95.98","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","3","3","89.32","0","267.96","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","1","1","54.82","0","54.82","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8142366","BEEF GRND CHUCK FINE 80/20FRSH","1","1","5.245","0","319.95","Y","61.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","405.76","Y","53.7",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7211838","PORK SLI GYRO CONE","1","1","87.76","0","87.76","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","5","5","86.97","0","434.85","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","5","5","92.53","0","462.65","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7324922","PASTRY BEIGNET MN FLD CHOCCRML","1","1","53.82","0","53.82","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7274591","APTZR VEG FALAFEL PUCK HALAL","1","1","97.94","0","97.94","N","0.0",
|
||||
"850091209","$3,608.82","$3,608.82","2026-04-06","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","3.01","0","3.01","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706813","FORK PLAS PP X-HVY BLK","1","1","16.03","0","16.03","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7790795","LID PLAS CLR F/1.5-2.5OZ PRTN","1","1","30.05","0","30.05","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","2","2","43.99","0","87.98","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","2","2","41.25","0","82.5","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7131355","CUP PLAS CLR RPET 12-14 OZ","1","1","28.25","0","28.25","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354120","CONTAINER PAPER 1/30 OZ NTG","1","1","30.6","0","30.6","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","2","2","37.6","0","75.2","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","2","2","46.07","0","92.14","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0635005","SODA COLA PEPSI ZERO SUGAR","1","1","34.01","0","34.01","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7153421","SYRUP COLA PEPSI ZERO BIB","1","1","71.94","0","71.94","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5167711","SODA ORANGE CRSH","1","1","34.01","0","34.01","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8492330","WATER PURIFIED BTL PET LSE DW","1","1","20.56","0","20.56","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9911228","SODA ORANGE PORTOKALADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7189422","SODA CHERRY VISSINADA GRK PLAS","1","1","14.84","0","14.84","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9911229","WATER MINERAL CARNONATED GREEK","1","1","26.57","0","26.57","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7228761","DRINK ENERGY TROPICAL VIBE","1","1","24.48","0","24.48","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7228477","DRINK ENERGY ORANGE SPRKLNG","1","1","24.48","0","24.48","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7228764","DRINK ENERGY PEACH VIBE SPRKLG","1","1","24.48","0","24.48","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8847824","SYRUP COLA PEPSI BIB","1","1","115.95","0","115.95","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2102479","SKEWER BAMBOO 10IN","1","0","7.82","0","7.82","N","0.0","S"
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","3","3","54.84","0","164.52","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","5","5","23.65","0","118.25","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108399","DRESSING VINAIGRETTE LOGO","1","1","75.49","0","75.49","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7360056","DIP GARLIC TOUM","2","2","29.4","0","58.8","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","3","3","38.85","0","116.55","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","6","6","26.36","0","158.16","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7485170","BUTTER SOLID USDA AA UNSLTD","1","1","68.55","0","68.55","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","2","2","56.72","0","113.44","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7253487","CHEESE FETA RW","1","1","2.429","0","95.92","Y","39.49",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","2","2","95.98","0","191.96","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213703","SAUCE SPICY YOGURT LOGO","1","1","95.98","0","95.98","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","3","3","89.32","0","267.96","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","3","3","54.82","0","164.46","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7007041","BUN BRIOCHE HOMESTYLE 4.25","1","1","30.83","0","30.83","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455027","SPANAKOPITA SPINACH COOKED","1","1","69.37","0","69.37","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","368.73","Y","48.8",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","9","9","86.97","0","782.73","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","8","8","92.53","0","740.24","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","1","1","49.53","0","49.53","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8936379","SODA PEPSI COLA","1","1","34.01","0","34.01","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9477498","ALLOWANCE FOR DROP SIZE","1","1","-7.81","0","-7.81","N","0.0",
|
||||
"850098462","$4,627.94","$4,627.94","2026-04-09","2026-06-05","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","4.45","0","4.45","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7452585","NAPKIN 2PLY INTR FOLD 6.3X8.26","1","1","22.32","0","22.32","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0191567","STRAW PLAS TRANS JMB WRPD 7.75","1","0","5.09","0","5.09","N","0.0","S"
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706813","FORK PLAS PP X-HVY BLK","1","1","16.03","0","16.03","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4613026","CUP PORTION PLAS CLR 1.50 OZ","1","1","26.15","0","26.15","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","1","1","43.99","0","43.99","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354119","CONTAINER PAPER 4/110OZ NTG","1","1","27.6","0","27.6","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","1","1","37.6","0","37.6","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","1","1","46.07","0","46.07","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7365006","WRAP PAPER 14X14 LOGO VER2","1","1","91.04","0","91.04","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1763853","LINER REPRO 40X46 1.5 ML BLK","1","1","39.47","3.85","39.47","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1336403","SODA DR PPR REG","1","1","34.01","0","34.01","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7301949","RICE MIX NICKS","1","1","31.07","0","31.07","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4062337","BEAN GARBANZO FCY NO SULFITE","1","1","31.8","0","31.8","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","1","1","38.85","0","38.85","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4136768","KETCHUP PACKET FCY","1","1","34","0","34","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171302","DRESSING MARINADE SOUVLAKI","1","1","72.44","0","72.44","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7377725","PASTE TAHINI DRESSING","1","1","37.51","0","37.51","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","4","4","26.36","0","105.44","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","1","1","56.72","0","56.72","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9910846","OLIVE KALAMATA PTD BRNE 22 LB","1","1","79.42","0","79.42","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","1","1","95.98","0","95.98","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","3","3","89.32","0","267.96","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455027","SPANAKOPITA SPINACH COOKED","1","1","69.37","0","69.37","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","438.25","Y","58.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7211838","PORK SLI GYRO CONE","1","1","87.76","0","87.76","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","6","6","86.97","0","521.82","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","4","4","92.76","0","371.04","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","2","2","49.53","0","99.06","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","2","2","57.66","0","115.32","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7274591","APTZR VEG FALAFEL PUCK HALAL","1","1","97.94","0","97.94","N","0.0",
|
||||
"850108204","$3,103.61","$3,103.61","2026-04-13","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","2.6","0","2.6","N","0.0",
|
||||
"850111098","$0.00","-$54.72","2026-04-15","2026-04-15","Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","-1","-1","54.82","0","-54.82","N","0.0",
|
||||
"850111098","$0.00","-$54.72","2026-04-15","2026-04-15","Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9477498","ALLOWANCE FOR DROP SIZE","1","1","0.1","0","0.1","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706946","SPOON PLAS TEA PP X-HVY BLK","1","1","18.01","0","18.01","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","2","2","43.99","0","87.98","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","2","2","41.25","0","82.5","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354120","CONTAINER PAPER 1/30 OZ NTG","1","1","30.6","0","30.6","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354119","CONTAINER PAPER 4/110OZ NTG","1","1","27.6","0","27.6","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","2","2","37.6","0","75.2","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","1","1","46.07","0","46.07","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7321470","CANDY MILK CHOC SHELLS","1","1","133.28","0","133.28","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1428869","TEA ICED UNSWEET PURELEAF","1","1","21.84","0","21.84","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8492330","WATER PURIFIED BTL PET LSE DW","1","1","20.56","0","20.56","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9910355","SODA LEMON LEMONADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9911228","SODA ORANGE PORTOKALADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7044106","JUICE CONC MANDARIN CARDAMOM","1","1","172.37","0","172.37","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7459969","SYRUP DR PPR DIET BIB","1","1","62.48","0","62.48","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7296407","GLOVE NITRILE BLK PEDRFREE LRG","1","1","39.41","3.84","39.41","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7435266","FILM PVC 18X2000 ROLL","1","1","21.82","2.14","21.82","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","2","2","54.84","0","109.68","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","7","7","23.65","0","165.55","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108399","DRESSING VINAIGRETTE LOGO","2","2","75.49","0","150.98","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4113049","VINEGAR DISTILLED WHITE 5%","1","1","16.48","0","16.48","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7360056","DIP GARLIC TOUM","1","1","29.4","0","29.4","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","2","2","38.85","0","77.7","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171302","DRESSING MARINADE SOUVLAKI","1","1","72.44","0","72.44","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108544","SAUCE MUSTARD","1","1","81.42","0","81.42","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","5","5","26.36","0","131.8","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","2","2","56.72","0","113.44","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","2","2","95.98","0","191.96","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213703","SAUCE SPICY YOGURT LOGO","1","1","95.98","0","95.98","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","3","3","89.32","0","267.96","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","3","3","54.98","0","164.94","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455027","SPANAKOPITA SPINACH COOKED","1","1","69.37","0","69.37","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","371.76","Y","49.2",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","1","1","49.53","0","49.53","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","7","7","86.97","0","608.79","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","5","5","92.76","0","463.8","N","0.0",
|
||||
"850113429","$4,115.36","$4,115.36","2026-04-16","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","3.82","0","3.82","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","1","1","46.07","0","46.07","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455080","CHOCOLATE DUBAI PISTCHO KUNFEH","1","0","119.09","0","119.09","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7206974","JUICE CONC STRAWB DRAGON","1","1","172.37","0","172.37","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","2","2","26.36","0","52.72","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","1","1","89.32","0","89.32","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","1","1","86.97","0","86.97","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","2","2","92.76","0","185.52","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7274591","APTZR VEG FALAFEL PUCK HALAL","1","1","97.94","0","97.94","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","1","1","57.66","0","57.66","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7324922","PASTRY BEIGNET MN FLD CHOCCRML","1","1","53.82","0","53.82","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7301949","RICE MIX NICKS","1","1","31.07","0","31.07","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7321835","BOX CATERING 21X13X4.25 LOGO","1","1","68.25","0","68.25","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7360056","DIP GARLIC TOUM","1","1","29.4","0","29.4","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","1","1","95.98","0","95.98","N","0.0",
|
||||
"850121152","$1,242.02","$1,242.02","2026-04-18","2026-06-12","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","1","0","1","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455080","CHOCOLATE DUBAI PISTCHO KUNFEH","1","1","119.09","0","119.09","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7452585","NAPKIN 2PLY INTR FOLD 6.3X8.26","1","1","22.32","0","22.32","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706813","FORK PLAS PP X-HVY BLK","1","1","16.03","0","16.03","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706914","KNIFE PLAS PP X-HVY BLK","1","1","17.66","0","17.66","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7064580","CUP PLAS TRANS HIPS 12 OZ","1","1","42.93","0","42.93","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","2","2","37.6","0","75.2","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354142","TRAY PAPER FOOD 2LB LOGO NTG","1","1","24.6","0","24.6","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","1","1","46.07","0","46.07","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7293283","PAN FOIL STM TBL FULL DP 3-3/8","0","0","50.01","0","0","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8936379","SODA PEPSI COLA","1","1","34.01","0","34.01","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0635005","SODA COLA PEPSI ZERO SUGAR","1","1","34.01","0","34.01","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5167711","SODA ORANGE CRSH","1","1","34.01","0","34.01","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8492330","WATER PURIFIED BTL PET LSE DW","1","1","20.56","0","20.56","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2102479","SKEWER BAMBOO 10IN","1","0","7.82","0","7.82","N","0.0","S"
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5254743","SPICE PAPRIKA GROUND","1","0","45.84","0","45.84","N","0.0","S"
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","1","1","23.65","0","23.65","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108399","DRESSING VINAIGRETTE LOGO","1","1","75.49","0","75.49","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5934302","OIL OLIVE BLEND 80/20","1","1","78.45","0","78.45","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4062337","BEAN GARBANZO FCY NO SULFITE","1","1","31.8","0","31.8","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4372932","PEPPER BANANA MILD RING","1","1","40.85","0","40.85","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4693715","PASTE HERB HARISSA MOROCCAN","1","1","28.05","0","28.05","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","1","1","38.85","0","38.85","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","4","4","26.36","0","105.44","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","1","1","56.72","0","56.72","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7253487","CHEESE FETA RW","1","1","2.513","0","88.66","Y","35.28",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","1","1","95.98","0","95.98","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213703","SAUCE SPICY YOGURT LOGO","1","1","95.98","0","95.98","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","1","1","89.32","0","89.32","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","1","1","54.93","0","54.93","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7211838","PORK SLI GYRO CONE","1","1","87.99","0","87.99","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","5","5","87.04","0","435.2","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","6","6","92.86","0","557.16","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","1","1","49.53","0","49.53","N","0.0",
|
||||
"850122686","$2,636.43","$2,636.43","2026-04-20","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","2.59","0","2.59","N","0.0",
|
||||
"850125010","$0.00","-$119.09","2026-04-21","2026-04-21","Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455080","CHOCOLATE DUBAI PISTCHO KUNFEH","-1","-1","119.09","0","-119.09","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0191567","STRAW PLAS TRANS JMB WRPD 7.75","1","0","5.09","0","5.09","N","0.0","S"
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7790795","LID PLAS CLR F/1.5-2.5OZ PRTN","1","1","30.05","0","30.05","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","2","2","43.99","0","87.98","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","2","2","41.25","0","82.5","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354127","CUP PAPER COLD 22 OZ LOGO NTG","1","1","47.6","0","47.6","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7661388","LID PLAS FLAT F/12-22 OZ","1","1","25.41","0","25.41","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354120","CONTAINER PAPER 1/30 OZ NTG","1","1","30.6","0","30.6","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354119","CONTAINER PAPER 4/110OZ NTG","1","1","27.6","0","27.6","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","3","3","37.6","0","112.8","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","2","2","46.07","0","92.14","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7293283","PAN FOIL STM TBL FULL DP 3-3/8","1","1","50.01","4.88","50.01","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7465969","PAN FOIL STM TBL DEEPXH 2-9/16","1","1","44.75","4.36","44.75","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5056098","LINER TRASH 40X48 13 MC NAT","1","1","56.06","5.46","56.06","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1336403","SODA DR PPR REG","1","1","34.01","0","34.01","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8492330","WATER PURIFIED BTL PET LSE DW","1","1","20.56","0","20.56","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9911226","SODA CHERRY VISSINADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9910355","SODA LEMON LEMONADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9911229","WATER MINERAL CARNONATED GREEK","1","1","26.57","0","26.57","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7153421","SYRUP COLA PEPSI ZERO BIB","1","1","71.94","0","71.94","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7296407","GLOVE NITRILE BLK PEDRFREE LRG","1","1","39.41","3.85","39.41","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7301949","RICE MIX NICKS","1","1","31.07","0","31.07","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","5","5","23.65","0","118.25","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7360056","DIP GARLIC TOUM","2","2","29.4","0","58.8","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","3","3","38.85","0","116.55","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171302","DRESSING MARINADE SOUVLAKI","1","1","72.44","0","72.44","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","7","7","26.36","0","184.52","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","2","2","56.72","0","113.44","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","2","2","95.98","0","191.96","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","4","4","89.32","0","357.28","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","2","2","54.93","0","109.86","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7007041","BUN BRIOCHE HOMESTYLE 4.25","1","1","30.93","0","30.93","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455027","SPANAKOPITA SPINACH COOKED","1","1","69.37","0","69.37","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","356.64","Y","47.2",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7211838","PORK SLI GYRO CONE","1","1","87.99","0","87.99","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","7","7","87.04","0","609.28","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","8","8","92.86","0","742.88","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7274591","APTZR VEG FALAFEL PUCK HALAL","1","1","98.4","0","98.4","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7324922","PASTRY BEIGNET MN FLD CHOCCRML","1","1","53.82","0","53.82","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9477498","ALLOWANCE FOR DROP SIZE","1","1","-7.5","0","-7.5","N","0.0",
|
||||
"850130314","$4,393.39","$4,393.39","2026-04-23","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","4.27","0.01","4.27","N","0.0",
|
||||
"850137898","$104.49","$104.49","2026-04-25","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","1","1","49.53","0","49.53","N","0.0",
|
||||
"850137898","$104.49","$104.49","2026-04-25","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850137898","$104.49","$104.49","2026-04-25","2026-06-19","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","0.12","0","0.12","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7452585","NAPKIN 2PLY INTR FOLD 6.3X8.26","1","1","22.32","0","22.32","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706813","FORK PLAS PP X-HVY BLK","0","0","16.03","0","0","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","1","1","43.99","0","43.99","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","1","1","41.25","0","41.25","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354119","CONTAINER PAPER 4/110OZ NTG","1","1","27.6","0","27.6","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","2","2","37.6","0","75.2","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","2","2","46.07","0","92.14","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7293257","LID FOIL F/FULL STM TBL PAN","1","1","54.17","5.27","54.17","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6938211","LID FOIL F/ HALF STMTBL PAN","1","1","29.53","2.89","29.53","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8492330","WATER PURIFIED BTL PET LSE DW","1","1","20.56","0","20.56","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9911228","SODA ORANGE PORTOKALADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2102479","SKEWER BAMBOO 10IN","1","0","7.82","0","7.82","N","0.0","S"
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","2","2","23.65","0","47.3","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108399","DRESSING VINAIGRETTE LOGO","1","1","75.49","0","75.49","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","1","1","38.85","0","38.85","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171302","DRESSING MARINADE SOUVLAKI","1","1","72.44","0","72.44","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","5","5","26.36","0","131.8","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1376805","PAD SCOUR GRN 6X9IN ANTIMICRO","1","1","11.79","1.16","11.79","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7485170","BUTTER SOLID USDA AA UNSLTD","1","1","74.83","0","74.83","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","1","1","56.72","0","56.72","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7253487","CHEESE FETA RW","1","1","2.513","0","88.18","Y","35.09",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","2","2","95.98","0","191.96","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213703","SAUCE SPICY YOGURT LOGO","1","1","95.98","0","95.98","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","3","3","89.32","0","267.96","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","1","1","54.98","0","54.98","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455027","SPANAKOPITA SPINACH COOKED","1","1","69.37","0","69.37","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","396.69","Y","52.5",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7211838","PORK SLI GYRO CONE","1","1","87.99","0","87.99","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","6","6","87.04","0","522.24","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","6","6","92.86","0","557.16","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7274591","APTZR VEG FALAFEL PUCK HALAL","1","1","98.4","0","98.4","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2520551","FORK PLAS PP HVY BLK FULL LENG","1","1","22.59","0","22.59","N","0.0",
|
||||
"850139122","$3,461.13","$3,461.13","2026-04-27","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","2.94","0","2.94","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706813","FORK PLAS PP X-HVY BLK","1","1","16.03","0","16.03","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4613026","CUP PORTION PLAS CLR 1.50 OZ","1","1","26.15","0","26.15","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7373744","DESSERT CUP","1","1","72.22","0","72.22","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7373626","LID DOME DESSERT CUP","1","1","53.59","0","53.59","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","1","1","43.99","0","43.99","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","1","1","41.25","0","41.25","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354120","CONTAINER PAPER 1/30 OZ NTG","1","1","30.6","0","30.6","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354119","CONTAINER PAPER 4/110OZ NTG","1","1","27.6","0","27.6","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","3","3","37.6","0","112.8","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","2","2","46.07","0","92.14","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7365006","WRAP PAPER 14X14 LOGO VER2","1","1","91.04","0","91.04","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8936379","SODA PEPSI COLA","1","1","34.01","0","34.01","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0635005","SODA COLA PEPSI ZERO SUGAR","1","1","34.01","0","34.01","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1336403","SODA DR PPR REG","1","1","34.01","0","34.01","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9911226","SODA CHERRY VISSINADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7459969","SYRUP DR PPR DIET BIB","1","1","62.48","0","62.48","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7296407","GLOVE NITRILE BLK PEDRFREE LRG","1","1","39.41","3.85","39.41","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7301949","RICE MIX NICKS","1","1","31.07","0","31.07","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","2","2","54.84","0","109.68","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","5","5","23.65","0","118.25","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108399","DRESSING VINAIGRETTE LOGO","1","1","75.49","0","75.49","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4062337","BEAN GARBANZO FCY NO SULFITE","1","1","31.8","0","31.8","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7360056","DIP GARLIC TOUM","2","2","29.4","0","58.8","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","1","1","38.85","0","38.85","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4136768","KETCHUP PACKET FCY","1","1","34","0","34","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171302","DRESSING MARINADE SOUVLAKI","1","1","72.44","0","72.44","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","7","7","26.36","0","184.52","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","2","2","56.72","0","113.44","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7253487","CHEESE FETA RW","1","1","2.513","0","97.86","Y","38.94",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","2","2","95.98","0","191.96","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","3","3","89.32","0","267.96","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","2","2","54.98","0","109.96","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455027","SPANAKOPITA SPINACH COOKED","1","1","69.37","0","69.37","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","378.56","Y","50.1",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","6","6","87.04","0","522.24","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","6","6","92.86","0","557.16","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","2","2","49.53","0","99.06","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7324922","PASTRY BEIGNET MN FLD CHOCCRML","1","1","53.82","0","53.82","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","0","0","57.66","0","0","N","0.0",
|
||||
"850146532","$4,054.54","$4,054.54","2026-04-30","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","3.94","0","3.94","N","0.0",
|
||||
"850157242","$434.43","$434.43","2026-05-02","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","2","2","92.86","0","185.72","N","0.0",
|
||||
"850157242","$434.43","$434.43","2026-05-02","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","1","1","87.04","0","87.04","N","0.0",
|
||||
"850157242","$434.43","$434.43","2026-05-02","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8492330","WATER PURIFIED BTL PET LSE DW","1","1","20.56","0","20.56","N","0.0",
|
||||
"850157242","$434.43","$434.43","2026-05-02","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","1","1","95.98","0","95.98","N","0.0",
|
||||
"850157242","$434.43","$434.43","2026-05-02","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","1","1","43.58","0","43.58","N","0.0",
|
||||
"850157242","$434.43","$434.43","2026-05-02","2026-06-26","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","0.35","0","0.35","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5001858","CHICKEN CVP THIGH B/S HALAL JM","4","4","99.56","0","398.24","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7321835","BOX CATERING 21X13X4.25 LOGO","1","1","68.25","0","68.25","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7452585","NAPKIN 2PLY INTR FOLD 6.3X8.26","1","1","22.32","0","22.32","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0191567","STRAW PLAS TRANS JMB WRPD 7.75","1","0","5.09","0","5.09","N","0.0","S"
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706946","SPOON PLAS TEA PP X-HVY BLK","1","1","18.01","0","18.01","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706813","FORK PLAS PP X-HVY BLK","1","1","16.03","0","16.03","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","1","1","43.58","0","43.58","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","1","1","40.99","0","40.99","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","2","2","37.6","0","75.2","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354120","CONTAINER PAPER 1/30 OZ NTG","1","1","30.6","0","30.6","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","2","2","46.28","0","92.56","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5167711","SODA ORANGE CRSH","1","1","34.01","0","34.01","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9911228","SODA ORANGE PORTOKALADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","2","2","54.84","0","109.68","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","5","5","23.65","0","118.25","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171301","DRESSING SALAD PRASINI","1","1","74.46","0","74.46","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108399","DRESSING VINAIGRETTE LOGO","1","1","75.93","0","75.93","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5934302","OIL OLIVE BLEND 80/20","1","1","82.96","0","82.96","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9910846","OLIVE KALAMATA PTD BRNE 22 LB","1","1","79.42","0","79.42","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4693715","PASTE HERB HARISSA MOROCCAN","1","1","28.05","0","28.05","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","1","1","38.85","0","38.85","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4361432","HONEY PURE CLOVER GR A TSC JUG","1","1","118.35","0","118.35","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171302","DRESSING MARINADE SOUVLAKI","1","1","73.28","0","73.28","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","7","7","26.36","0","184.52","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","2","2","56.72","0","113.44","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","1","1","95.98","0","95.98","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213703","SAUCE SPICY YOGURT LOGO","1","1","95.98","0","95.98","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","2","2","54.93","0","109.86","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8142366","BEEF GRND CHUCK FINE 80/20FRSH","1","1","5.14","0","314.05","Y","61.1",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","337.75","Y","44.7",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7211838","PORK SLI GYRO CONE","1","1","87.99","0","87.99","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","5","5","87.02","0","435.1","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","6","6","92.86","0","557.16","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","1","1","49.53","0","49.53","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7274591","APTZR VEG FALAFEL PUCK HALAL","1","1","98.4","0","98.4","N","0.0",
|
||||
"850158779","$4,144.09","$4,144.09","2026-05-04","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","3.49","0","3.49","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0191567","STRAW PLAS TRANS JMB WRPD 7.75","1","0","5.09","0","5.09","N","0.0","S"
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7790795","LID PLAS CLR F/1.5-2.5OZ PRTN","1","1","30.05","0","30.05","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","1","1","43.58","0","43.58","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","1","1","40.99","0","40.99","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354119","CONTAINER PAPER 4/110OZ NTG","1","1","27.6","0","27.6","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","2","2","37.6","0","75.2","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","1","1","46.28","0","46.28","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5723808","WRAP DELI WHT 12X12 GRS RESIST","1","0","24.46","0","24.46","N","0.0","S"
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8936379","SODA PEPSI COLA","1","1","34.01","0","34.01","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1336403","SODA DR PPR REG","1","1","34.01","0","34.01","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1428869","TEA ICED UNSWEET PURELEAF","1","1","21.84","0","21.84","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8492330","WATER PURIFIED BTL PET LSE DW","1","1","20.56","0","20.56","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7206974","JUICE CONC STRAWB DRAGON","1","1","172.37","0","172.37","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7153421","SYRUP COLA PEPSI ZERO BIB","1","1","71.94","0","71.94","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7234905","SYRUP LEMON LIME BIB","1","1","115.95","0","115.95","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2102479","SKEWER BAMBOO 10IN","1","0","7.82","0","7.82","N","0.0","S"
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7296407","GLOVE NITRILE BLK PEDRFREE LRG","1","1","39.41","3.85","39.41","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7301949","RICE MIX NICKS","1","1","32.33","0","32.33","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","4","4","23.65","0","94.6","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108399","DRESSING VINAIGRETTE LOGO","1","1","75.93","0","75.93","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4062337","BEAN GARBANZO FCY NO SULFITE","1","1","31.8","0","31.8","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4372932","PEPPER BANANA MILD RING","1","1","40.88","0","40.88","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7360056","DIP GARLIC TOUM","2","2","29.78","0","59.56","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","3","3","38.85","0","116.55","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171302","DRESSING MARINADE SOUVLAKI","1","1","73.28","0","73.28","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108544","SAUCE MUSTARD","1","1","81.66","0","81.66","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","5","5","26.36","0","131.8","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","2","2","56.72","0","113.44","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7253487","CHEESE FETA RW","1","1","2.513","0","90.12","Y","35.86",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","2","2","95.98","0","191.96","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","2","2","89.32","0","178.64","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","2","2","54.93","0","109.86","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7007041","BUN BRIOCHE HOMESTYLE 4.25","1","1","30.93","0","30.93","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455027","SPANAKOPITA SPINACH COOKED","1","1","69.37","0","69.37","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","377.8","Y","50.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","6","6","87.02","0","522.12","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","7","7","92.86","0","650.02","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","1","1","49.53","0","49.53","N","0.0",
|
||||
"850166198","$3,999.80","$3,999.80","2026-05-07","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","3.57","0","3.57","N","0.0",
|
||||
"850173770","$432.27","$432.27","2026-05-09","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","1","1","46.28","0","46.28","N","0.0",
|
||||
"850173770","$432.27","$432.27","2026-05-09","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","1","1","87.02","0","87.02","N","0.0",
|
||||
"850173770","$432.27","$432.27","2026-05-09","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","1","1","92.86","0","92.86","N","0.0",
|
||||
"850173770","$432.27","$432.27","2026-05-09","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850173770","$432.27","$432.27","2026-05-09","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","1","1","95.98","0","95.98","N","0.0",
|
||||
"850173770","$432.27","$432.27","2026-05-09","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","1","1","54.93","0","54.93","N","0.0",
|
||||
"850173770","$432.27","$432.27","2026-05-09","2026-07-03","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","0.36","0","0.36","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7452585","NAPKIN 2PLY INTR FOLD 6.3X8.26","1","1","22.32","0","22.32","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706813","FORK PLAS PP X-HVY BLK","1","1","16.03","0","16.03","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","1","1","43.58","0","43.58","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","1","1","40.99","0","40.99","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","2","2","37.6","0","75.2","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354120","CONTAINER PAPER 1/30 OZ NTG","1","1","30.6","0","30.6","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","1","1","46.28","0","46.28","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2451417","SAUCE CHILI HOT SRIRACHA","1","1","41.26","0","41.26","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0635005","SODA COLA PEPSI ZERO SUGAR","1","1","34.01","0","34.01","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9910355","SODA LEMON LEMONADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8847824","SYRUP COLA PEPSI BIB","1","1","115.95","0","115.95","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","6","6","23.65","0","141.9","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4113049","VINEGAR DISTILLED WHITE 5%","1","1","16.48","0","16.48","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4136768","KETCHUP PACKET FCY","1","1","34","0","34","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171302","DRESSING MARINADE SOUVLAKI","1","1","73.28","0","73.28","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","6","6","26.36","0","158.16","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7485170","BUTTER SOLID USDA AA UNSLTD","1","1","74.83","0","74.83","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","1","1","56.72","0","56.72","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7253487","CHEESE FETA RW","1","1","2.525","0","91.41","Y","36.2",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","1","1","99.62","0","99.62","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213703","SAUCE SPICY YOGURT LOGO","1","1","99.62","0","99.62","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","1","1","89.32","0","89.32","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","2","2","54.93","0","109.86","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455027","SPANAKOPITA SPINACH COOKED","1","1","69.37","0","69.37","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","298.46","Y","39.5",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7211838","PORK SLI GYRO CONE","1","1","87.99","0","87.99","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","5","5","87.04","0","435.2","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","6","6","92.86","0","557.16","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","1","1","49.53","0","49.53","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7274591","APTZR VEG FALAFEL PUCK HALAL","1","1","98.4","0","98.4","N","0.0",
|
||||
"850175232","$3,182.09","$3,182.09","2026-05-11","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","2.99","0","2.99","N","0.0",
|
||||
"850180287","$655.69","$655.69","2026-05-13","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","1","1","49.53","0","49.53","N","0.0",
|
||||
"850180287","$655.69","$655.69","2026-05-13","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","1","1","43.58","0","43.58","N","0.0",
|
||||
"850180287","$655.69","$655.69","2026-05-13","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","1","1","40.99","0","40.99","N","0.0",
|
||||
"850180287","$655.69","$655.69","2026-05-13","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","2","2","87.04","0","174.08","N","0.0",
|
||||
"850180287","$655.69","$655.69","2026-05-13","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","1","1","92.86","0","92.86","N","0.0",
|
||||
"850180287","$655.69","$655.69","2026-05-13","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","1","1","89.32","0","89.32","N","0.0",
|
||||
"850180287","$655.69","$655.69","2026-05-13","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","1","1","99.62","0","99.62","N","0.0",
|
||||
"850180287","$655.69","$655.69","2026-05-13","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4613026","CUP PORTION PLAS CLR 1.50 OZ","1","1","22.19","0","22.19","N","0.0",
|
||||
"850180287","$655.69","$655.69","2026-05-13","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7064580","CUP PLAS TRANS HIPS 12 OZ","1","1","42.93","0","42.93","N","0.0",
|
||||
"850180287","$655.69","$655.69","2026-05-13","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","0.59","0","0.59","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706813","FORK PLAS PP X-HVY BLK","1","1","16.03","0","16.03","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","1","1","43.58","0","43.58","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","3","3","37.6","0","112.8","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354120","CONTAINER PAPER 1/30 OZ NTG","1","1","30.6","0","30.6","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354119","CONTAINER PAPER 4/110OZ NTG","1","1","27.6","0","27.6","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354142","TRAY PAPER FOOD 2LB LOGO NTG","1","1","24.6","0","24.6","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","2","2","46.28","0","92.56","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7365006","WRAP PAPER 14X14 LOGO VER2","1","1","91.04","0","91.04","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1336403","SODA DR PPR REG","1","1","34.01","0","34.01","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8492330","WATER PURIFIED BTL PET LSE DW","2","2","20.56","0","41.12","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9911229","WATER MINERAL CARNONATED GREEK","1","1","26.57","0","26.57","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7228477","DRINK ENERGY ORANGE SPRKLNG","1","1","24.48","0","24.48","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2102479","SKEWER BAMBOO 10IN","1","0","7.82","0","7.82","N","0.0","S"
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5793963","GRILL BRICK 3.5IN THICK","1","1","35.22","3.41","35.22","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6937767","FOIL ALMN ROLL HVY WGT 500 FT","1","1","39.46","3.87","39.46","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7435266","FILM PVC 18X2000 ROLL","1","1","21.82","2.13","21.82","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7301949","RICE MIX NICKS","1","1","32.33","0","32.33","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","2","2","54.84","0","109.68","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","4","4","23.65","0","94.6","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108399","DRESSING VINAIGRETTE LOGO","2","2","75.93","0","151.86","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5934302","OIL OLIVE BLEND 80/20","1","1","82.96","0","82.96","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4062337","BEAN GARBANZO FCY NO SULFITE","1","1","31.8","0","31.8","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7360056","DIP GARLIC TOUM","2","2","29.78","0","59.56","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","2","2","38.85","0","77.7","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7377725","PASTE TAHINI DRESSING","1","1","38.05","0","38.05","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","8","8","26.36","0","210.88","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","3","3","56.72","0","170.16","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7253487","CHEESE FETA RW","1","1","2.525","0","89.28","Y","35.36",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","1","1","99.62","0","99.62","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","3","3","89.32","0","267.96","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","2","2","54.93","0","109.86","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","314.33","Y","41.6",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7211838","PORK SLI GYRO CONE","1","1","87.99","0","87.99","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","5","5","87.04","0","435.2","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","6","6","92.86","0","557.16","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","1","1","49.53","0","49.53","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","0","0","55.66","0","0","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7324922","PASTRY BEIGNET MN FLD CHOCCRML","1","1","53.82","0","53.82","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7274591","APTZR VEG FALAFEL PUCK HALAL","1","1","98.4","0","98.4","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1498411","DOUGH PASTRY HNY PUFF","1","1","53.1","0","53.1","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9477498","ALLOWANCE FOR DROP SIZE","1","1","-7.01","0","-7.01","N","0.0",
|
||||
"850182453","$3,956.95","$3,956.95","2026-05-14","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","4","0.01","4","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354127","CUP PAPER COLD 22 OZ LOGO NTG","1","1","47.6","0","47.6","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7228764","DRINK ENERGY PEACH VIBE SPRKLG","1","1","24.48","0","24.48","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","1","1","46.28","0","46.28","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","2","2","87.04","0","174.08","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","1","1","92.86","0","92.86","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","1","1","99.62","0","99.62","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213703","SAUCE SPICY YOGURT LOGO","1","1","99.62","0","99.62","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","1","1","40.99","0","40.99","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","1","1","7.556","0","383.09","Y","50.7",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","1","1","89.32","0","89.32","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7661388","LID PLAS FLAT F/12-22 OZ","1","1","25.41","0","25.41","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850190221","$1,234.46","$1,234.46","2026-05-16","2026-07-10","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","0.83","0","0.83","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7452585","NAPKIN 2PLY INTR FOLD 6.3X8.26","1","1","22.32","0","22.32","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0191567","STRAW PLAS TRANS JMB WRPD 7.75","1","0","5.09","0","5.09","N","0.0","S"
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706813","FORK PLAS PP X-HVY BLK","1","1","16.03","0","16.03","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","1","1","43.58","0","43.58","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","1","1","40.99","0","40.99","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354119","CONTAINER PAPER 4/110OZ NTG","1","1","27.6","0","27.6","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354120","CONTAINER PAPER 1/30 OZ NTG","1","1","30.6","0","30.6","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","2","2","37.6","0","75.2","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","1","1","46.28","0","46.28","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8367823","SAUCE HOT BOTTLE","1","1","24.47","0","24.47","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5056098","LINER TRASH 40X48 13 MC NAT","1","1","58.01","5.66","58.01","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8936379","SODA PEPSI COLA","1","1","34.01","0","34.01","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6937767","FOIL ALMN ROLL HVY WGT 500 FT","1","1","39.46","3.85","39.46","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7301949","RICE MIX NICKS","1","1","32.33","0","32.33","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","3","3","23.65","0","70.95","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4693715","PASTE HERB HARISSA MOROCCAN","1","1","28.05","0","28.05","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","1","1","42.5","0","42.5","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171302","DRESSING MARINADE SOUVLAKI","1","1","73.28","0","73.28","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","4","4","26.36","0","105.44","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","1","1","56.72","0","56.72","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","2","2","99.62","0","199.24","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","2","2","89.32","0","178.64","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","2","2","54.88","0","109.76","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455027","SPANAKOPITA SPINACH COOKED","1","1","69.37","0","69.37","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","4","4","87.04","0","348.16","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","6","6","92.86","0","557.16","N","0.0",
|
||||
"850191803","$2,403.25","$2,403.25","2026-05-18","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","2.46","0","2.46","N","0.0",
|
||||
"850195877","$359.28","$359.28","2026-05-19","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9495920","SYRUP LEMONADE PNK BIB","1","1","115.95","0","115.95","N","0.0",
|
||||
"850195877","$359.28","$359.28","2026-05-19","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7324922","PASTRY BEIGNET MN FLD CHOCCRML","1","1","53.82","0","53.82","N","0.0",
|
||||
"850195877","$359.28","$359.28","2026-05-19","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1336403","SODA DR PPR REG","1","1","34.01","0","34.01","N","0.0",
|
||||
"850195877","$359.28","$359.28","2026-05-19","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5167711","SODA ORANGE CRSH","1","1","34.01","0","34.01","N","0.0",
|
||||
"850195877","$359.28","$359.28","2026-05-19","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455080","CHOCOLATE DUBAI PISTCHO KUNFEH","1","1","119.09","0","119.09","N","0.0",
|
||||
"850195880","$59.56","$59.56","2026-05-19","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7360056","DIP GARLIC TOUM","2","2","29.78","0","59.56","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7452585","NAPKIN 2PLY INTR FOLD 6.3X8.26","1","1","22.32","0","22.32","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1706946","SPOON PLAS TEA PP X-HVY BLK","1","1","18.01","0","18.01","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7790795","LID PLAS CLR F/1.5-2.5OZ PRTN","1","1","30.05","0","30.05","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408008","BOWL PLASTIC COATING 42 OZ","2","2","43.58","0","87.16","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7408215","LID CLEAR PET 42 OZ","1","1","40.99","0","40.99","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7354119","CONTAINER PAPER 4/110OZ NTG","1","1","27.6","0","27.6","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7250678","CONTAINER PAPER MLD FBR 9X6","3","3","37.6","0","112.8","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7417242","BAG PAPER 250 CT","2","2","46.28","0","92.56","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7321470","CANDY MILK CHOC SHELLS","1","1","133.28","0","133.28","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","1428869","TEA ICED UNSWEET PURELEAF","1","1","21.84","0","21.84","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8492330","WATER PURIFIED BTL PET LSE DW","1","1","20.56","0","20.56","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9911226","SODA CHERRY VISSINADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9911228","SODA ORANGE PORTOKALADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9910355","SODA LEMON LEMONADA GREEK","1","1","14.93","0","14.93","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7228761","DRINK ENERGY TROPICAL VIBE","1","1","24.48","0","24.48","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7206974","JUICE CONC STRAWB DRAGON","1","1","160.37","0","160.37","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7153421","SYRUP COLA PEPSI ZERO BIB","1","1","71.94","0","71.94","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","8847741","SYRUP MOUNTAIN DEW BIB","1","1","115.95","0","115.95","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7459969","SYRUP DR PPR DIET BIB","1","1","62.48","0","62.48","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2102479","SKEWER BAMBOO 10IN","1","0","7.82","0","7.82","N","0.0","S"
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7296407","GLOVE NITRILE BLK PEDRFREE LRG","1","1","39.41","3.85","39.41","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7053293","RICE BASMATI PABROIL SELA CS","1","1","54.84","0","54.84","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","2039220","POTATO KENNEBEC FRESH","5","5","23.65","0","118.25","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7108399","DRESSING VINAIGRETTE LOGO","1","1","75.93","0","75.93","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4062337","BEAN GARBANZO FCY NO SULFITE","1","1","31.8","0","31.8","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4372932","PEPPER BANANA MILD RING","1","1","40.88","0","40.88","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","4823761","OIL CORN","2","2","42.5","0","85","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7171302","DRESSING MARINADE SOUVLAKI","1","1","73.28","0","73.28","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","5223334","BREAD PITA GYRO PRE-OILED 7","8","8","26.36","0","210.88","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7278619","SPREAD HUMMUS TRADITIONAL","3","3","56.72","0","170.16","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7253487","CHEESE FETA RW","1","1","2.525","0","92.16","Y","36.5",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213639","SAUCE TZATZIKI","2","2","99.62","0","199.24","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7213703","SAUCE SPICY YOGURT LOGO","1","1","99.62","0","99.62","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7792187","CHICKEN CVP THIGH BNLS SKLS","3","3","89.32","0","267.96","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7302646","YOGURT FRZN NF NICK THE GREEK","3","3","54.88","0","164.64","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7455027","SPANAKOPITA SPINACH COOKED","2","2","69.37","0","138.74","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","0932867","BEEF SHLDR TERES MAJOR SEL","2","2","7.556","0","817.56","Y","108.2",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7211838","PORK SLI GYRO CONE","1","1","87.99","0","87.99","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7124188","GYRO CHICKEN SHAWARMA CONE","7","7","87.04","0","609.28","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9906087","MEAT GYRO BEEF CONE NTG","7","7","92.86","0","650.02","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7187055","BAKLAVA CLASSIC 2X24","2","2","49.53","0","99.06","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7274591","APTZR VEG FALAFEL PUCK HALAL","1","1","98.4","0","98.4","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","9477498","ALLOWANCE FOR DROP SIZE","1","1","-7.91","0","-7.91","N","0.0",
|
||||
"850199082","$5,324.76","$5,324.76","2026-05-21","2026-07-17","Not Paid","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","6592893","CHGS FOR FUEL SURCHARGE","1","1","4.52","0","4.52","N","0.0",
|
||||
"850200939","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","-1","-1","57.66","0","-57.66","N","0.0",
|
||||
"850200939","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","1","1","55.66","0","55.66","N","0.0",
|
||||
"850201144","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","-1","-1","57.66","0","-57.66","N","0.0",
|
||||
"850201144","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","1","1","55.66","0","55.66","N","0.0",
|
||||
"850200964","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","-1","-1","57.66","0","-57.66","N","0.0",
|
||||
"850200964","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","1","1","55.66","0","55.66","N","0.0",
|
||||
"850201060","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","-1","-1","57.66","0","-57.66","N","0.0",
|
||||
"850201060","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","1","1","55.66","0","55.66","N","0.0",
|
||||
"850200828","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","-1","-1","57.66","0","-57.66","N","0.0",
|
||||
"850200828","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","1","1","55.66","0","55.66","N","0.0",
|
||||
"850201089","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","-1","-1","57.66","0","-57.66","N","0.0",
|
||||
"850201089","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","1","1","55.66","0","55.66","N","0.0",
|
||||
"850201127","-$4.00","-$4.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","-2","-2","57.66","0","-115.32","N","0.0",
|
||||
"850201127","-$4.00","-$4.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","2","2","55.66","0","111.32","N","0.0",
|
||||
"850200909","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","-1","-1","57.66","0","-57.66","N","0.0",
|
||||
"850200909","-$2.00","-$2.00","2026-05-21","2026-05-21","Payment Processing","NICK THE GREEK CONCORD","175469","CKC CONCORD INC","175469","7212299","DESSERT MINI PLAIN BEIGNET","1","1","55.66","0","55.66","N","0.0",
|
||||
|
File diff suppressed because one or more lines are too long
5342
resources/public/js/htmx.js
Normal file
5342
resources/public/js/htmx.js
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1759,4 +1759,38 @@ Id,Sysco Category,Sysco Description,Integreat Account,Integreat Account Code,Nic
|
||||
1758,MEATS,PORK BELLY SKIN ON P12 COV,Beef/Pork Costs,51110,
|
||||
1759,MEATS,PORK SHANK BONE KUROBUTA PR12,Beef/Pork Costs,51110,
|
||||
1760,CANNED AND DRY,SEASONING ITALIAN WHL,Food Costs,50000,
|
||||
1761,PRODUCE,MUSHROOM PORTABELLA CAP 4-5,Produce Costs,51200,
|
||||
1761,PRODUCE,MUSHROOM PORTABELLA CAP 4-5,Produce Costs,51200,
|
||||
1762,PAPER & DISP,BAG PAPER 250 CT,Paper Costs,55000,
|
||||
1763,MEATS,BEEF SHLDR TERES MAJOR SEL,Beef/Pork Costs,51110,
|
||||
1764,PAPER & DISP,BOWL PLASTIC COATING 42 OZ,Paper Costs,55000,
|
||||
1765,PAPER & DISP,BOX CATERING 21X13X4.25 LOGO,Paper Costs,55000,
|
||||
1766,CANNED AND DRY,CANDY MILK CHOC SHELLS,Food Costs,50000,
|
||||
1767,CANNED AND DRY,CHOCOLATE DUBAI PISTCHO KUNFEH,Food Costs,50000,
|
||||
1768,PAPER & DISP,CONTAINER PAPER 1/30 OZ NTG,Paper Costs,55000,
|
||||
1769,PAPER & DISP,CONTAINER PAPER 4/110OZ NTG,Paper Costs,55000,
|
||||
1770,PAPER & DISP,CUP PAPER COLD 22 OZ LOGO NTG,Paper Costs,55000,
|
||||
1771,PAPER & DISP,CUP PORTION PLAS CLR 1.50 OZ,Paper Costs,55000,
|
||||
1772,CANNED AND DRY,DESSERT CUP,Food Costs,50000,
|
||||
1773,FROZEN,DESSERT MINI PLAIN BEIGNET,Food Costs,50000,
|
||||
1774,CANNED AND DRY,DIP GARLIC TOUM,Food Costs,50000,
|
||||
1775,CANNED AND DRY,DRINK ENERGY ORANGE SPRKLNG,Soft Beverage Costs,52000,
|
||||
1776,CANNED AND DRY,DRINK ENERGY PEACH VIBE SPRKLG,Soft Beverage Costs,52000,
|
||||
1777,CANNED AND DRY,DRINK ENERGY TROPICAL VIBE,Soft Beverage Costs,52000,
|
||||
1778,PAPER & DISP,FILM PVC 18X2000 ROLL,Paper Costs,55000,
|
||||
1779,CANNED AND DRY,JUICE CONC MANDARIN CARDAMOM,Food Costs,50000,
|
||||
1780,CANNED AND DRY,JUICE CONC STRAWB DRAGON,Food Costs,50000,
|
||||
1781,PAPER & DISP,LID CLEAR PET 42 OZ,Paper Costs,55000,
|
||||
1782,PAPER & DISP,LID DOME DESSERT CUP,Paper Costs,55000,
|
||||
1783,PAPER & DISP,NAPKIN 2PLY INTR FOLD 6.3X8.26,Paper Costs,55000,
|
||||
1784,CANNED AND DRY,PASTE HERB HARISSA MOROCCAN,Food Costs,50000,
|
||||
1785,CANNED AND DRY,PASTE TAHINI DRESSING,Food Costs,50000,
|
||||
1786,FROZEN,PASTRY BEIGNET MN FLD CHOCCRML,Food Costs,50000,
|
||||
1787,CANNED AND DRY,PEPPER BANANA MILD RING,Food Costs,50000,
|
||||
1788,CANNED AND DRY,RICE MIX NICKS,Food Costs,50000,
|
||||
1789,CANNED AND DRY,SODA CHERRY VISSINADA GREEK,Soft Beverage Costs,52000,
|
||||
1790,CANNED AND DRY,SODA COLA PEPSI ZERO SUGAR,Soft Beverage Costs,52000,
|
||||
1791,CANNED AND DRY,SODA PEPSI COLA,Soft Beverage Costs,52000,
|
||||
1792,FROZEN,SPANAKOPITA SPINACH COOKED,Food Costs,50000,
|
||||
1793,PAPER & DISP,SPOON PLAS TEA PP X-HVY BLK,Paper Costs,55000,
|
||||
1794,PAPER & DISP,WRAP PAPER 14X14 LOGO VER2,Paper Costs,55000,
|
||||
1795,DAIRY PRODUCTS,YOGURT FRZN NF NICK THE GREEK,Dairy Costs,51300,
|
||||
|
||||
|
17
skills-lock.json
Normal file
17
skills-lock.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": 1,
|
||||
"skills": {
|
||||
"agent-browser": {
|
||||
"source": "vercel-labs/agent-browser",
|
||||
"sourceType": "github",
|
||||
"skillPath": "skills/agent-browser/SKILL.md",
|
||||
"computedHash": "228f87d57035100d9dc6efcfc05aafd4b6e3962adacaa04b8217ab2fadb15dc8"
|
||||
},
|
||||
"frontend-design": {
|
||||
"source": "anthropics/skills",
|
||||
"sourceType": "github",
|
||||
"skillPath": "skills/frontend-design/SKILL.md",
|
||||
"computedHash": "063a0e6448123cd359ad0044cc46b0e490cc7964d45ef4bb9fd842bd2ffbca67"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
(ns amazonica.aws.textract
|
||||
(:require [amazonica.core :as amz])
|
||||
(:import [com.amazonaws.services.textract AmazonTextractClient ]))
|
||||
(:import [com.amazonaws.services.textract AmazonTextractClient]))
|
||||
|
||||
#_
|
||||
(import '[com.amazonaws.services.textract AmazonTextractClient ])
|
||||
#_(import '[com.amazonaws.services.textract.model S3Object ])
|
||||
#_(import '[com.amazonaws.services.textract.model StartExpenseAnalysisRequest ])
|
||||
#_(import '[com.amazonaws.services.textract.model GetExpenseAnalysisRequest ])
|
||||
#_(import '[com.amazonaws.services.textract AmazonTextractClient])
|
||||
#_(import '[com.amazonaws.services.textract.model S3Object])
|
||||
#_(import '[com.amazonaws.services.textract.model StartExpenseAnalysisRequest])
|
||||
#_(import '[com.amazonaws.services.textract.model GetExpenseAnalysisRequest])
|
||||
|
||||
#_(import '[com.amazonaws.services.textract.model DocumentLocation])
|
||||
(amz/set-client AmazonTextractClient *ns*)
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
[(str "container:" (:DockerId container-data))
|
||||
(str "ip:" (-> container-data :Networks first :IPv4Addresses first))])
|
||||
|
||||
|
||||
(mount/defstate container-tags
|
||||
:start (get-container-tags)
|
||||
:stop nil)
|
||||
|
||||
@@ -5,14 +5,11 @@
|
||||
(path [cursor])
|
||||
(state [cursor]))
|
||||
|
||||
|
||||
(defprotocol ITransact
|
||||
(-transact! [cursor f]))
|
||||
|
||||
|
||||
(declare to-cursor cursor?)
|
||||
|
||||
|
||||
(deftype ValCursor [value state path]
|
||||
IDeref
|
||||
(deref [_]
|
||||
@@ -23,9 +20,8 @@
|
||||
ITransact
|
||||
(-transact! [_ f]
|
||||
(get-in
|
||||
(swap! state (if (empty? path) f #(update-in % path f)))
|
||||
path)))
|
||||
|
||||
(swap! state (if (empty? path) f #(update-in % path f)))
|
||||
path)))
|
||||
|
||||
(deftype MapCursor [value state path]
|
||||
Counted
|
||||
@@ -53,14 +49,13 @@
|
||||
ITransact
|
||||
(-transact! [cursor f]
|
||||
(get-in
|
||||
(swap! state (if (empty? path) f #(update-in % path f)))
|
||||
path))
|
||||
(swap! state (if (empty? path) f #(update-in % path f)))
|
||||
path))
|
||||
Seqable
|
||||
(seq [this]
|
||||
(for [[k v] @this]
|
||||
[k (to-cursor v state (conj path k) nil)])))
|
||||
|
||||
|
||||
(deftype VecCursor [value state path]
|
||||
Counted
|
||||
(count [_]
|
||||
@@ -91,29 +86,25 @@
|
||||
ITransact
|
||||
(-transact! [cursor f]
|
||||
(get-in
|
||||
(swap! state (if (empty? path) f #(update-in % path f)))
|
||||
path))
|
||||
(swap! state (if (empty? path) f #(update-in % path f)))
|
||||
path))
|
||||
Seqable
|
||||
(seq [this]
|
||||
(for [[v i] (map vector @this (range))]
|
||||
(to-cursor v state (conj path i) nil))))
|
||||
|
||||
|
||||
(defn- to-cursor
|
||||
([v state path value]
|
||||
(cond
|
||||
(cursor? v) v
|
||||
(map? v) (MapCursor. value state path)
|
||||
(vector? v) (VecCursor. value state path)
|
||||
:else (ValCursor. value state path)
|
||||
)))
|
||||
|
||||
:else (ValCursor. value state path))))
|
||||
|
||||
(defn cursor? [c]
|
||||
"Returns true if c is a cursor."
|
||||
(satisfies? ICursor c))
|
||||
|
||||
|
||||
(defn cursor [v]
|
||||
"Creates cursor from supplied value v. If v is an ordinary
|
||||
data structure, it is wrapped into atom. If v is an atom,
|
||||
@@ -123,7 +114,6 @@
|
||||
(if (instance? Atom v) v (atom v))
|
||||
[] nil))
|
||||
|
||||
|
||||
(defn synthetic-cursor [v prefix]
|
||||
(let [internal-cursor (cursor v)]
|
||||
(reify ICursor
|
||||
@@ -132,14 +122,12 @@
|
||||
(state [this]
|
||||
(state internal-cursor)))))
|
||||
|
||||
|
||||
(defn transact! [cursor f]
|
||||
"Changes value beneath cursor by passing it to a single-argument
|
||||
function f. Old value will be passed as function argument. Function
|
||||
result will be the new value."
|
||||
(-transact! cursor f))
|
||||
|
||||
|
||||
(defn update! [cursor v]
|
||||
"Replaces value supplied by cursor with value v."
|
||||
(-transact! cursor (constantly v)))
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
[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]
|
||||
[iol-ion.tx.upsert-sales-summary-ledger]
|
||||
[com.github.ivarref.gen-fn :refer [gen-fn! datomic-fn]]
|
||||
[auto-ap.utils :refer [default-pagination-size by]]
|
||||
[clojure.edn :as edn]
|
||||
@@ -27,8 +27,8 @@
|
||||
(def uri (:datomic-url env))
|
||||
|
||||
#_(mount/defstate client
|
||||
:start (dc/client (:client-config env))
|
||||
:stop nil)
|
||||
:start (dc/client (:client-config env))
|
||||
:stop nil)
|
||||
|
||||
(mount/defstate conn
|
||||
:start (dc/connect uri)
|
||||
@@ -38,21 +38,20 @@
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
#_(defn create-database []
|
||||
(d/create-database uri))
|
||||
(d/create-database uri))
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
#_(defn drop-database []
|
||||
(d/delete-database uri))
|
||||
(d/delete-database uri))
|
||||
|
||||
(defn remove-nils [m]
|
||||
(let [result (reduce-kv
|
||||
(fn [m k v]
|
||||
(if (not (nil? v))
|
||||
(assoc m k v)
|
||||
m
|
||||
))
|
||||
{}
|
||||
m)]
|
||||
(fn [m k v]
|
||||
(if (not (nil? v))
|
||||
(assoc m k v)
|
||||
m))
|
||||
{}
|
||||
m)]
|
||||
(if (seq result)
|
||||
result
|
||||
nil)))
|
||||
@@ -80,7 +79,7 @@
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/doc "A vendor's email address"}
|
||||
|
||||
|
||||
{:db/ident :vendor/phone
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one
|
||||
@@ -102,14 +101,13 @@
|
||||
:db/valueType :db.type/ref
|
||||
:db/isComponent true
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/doc "The vendor's secondary contact"}
|
||||
:db/doc "The vendor's secondary contact"}
|
||||
{:db/ident :vendor/address
|
||||
:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/isComponent true
|
||||
:db.install/_attribute :db.part/db
|
||||
:db/doc "The vendor's address"}
|
||||
])
|
||||
:db/doc "The vendor's address"}])
|
||||
|
||||
(def client-schema
|
||||
[{:db/ident :client/original-id
|
||||
@@ -151,8 +149,7 @@
|
||||
:db/doc "Bank accounts for the client"}])
|
||||
|
||||
(def address-schema
|
||||
[
|
||||
{:db/ident :address/street1
|
||||
[{:db/ident :address/street1
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/doc "123 main st"}
|
||||
@@ -174,8 +171,7 @@
|
||||
:db/doc "95014"}])
|
||||
|
||||
(def contact-schema
|
||||
[
|
||||
{:db/ident :contact/name
|
||||
[{:db/ident :contact/name
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/doc "John Smith"}
|
||||
@@ -188,8 +184,6 @@
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/doc "hello@example.com"}])
|
||||
|
||||
|
||||
|
||||
(def bank-account-schema
|
||||
[{:db/ident :bank-account/external-id
|
||||
:db/valueType :db.type/long
|
||||
@@ -296,7 +290,6 @@
|
||||
:db/cardinality :db.cardinality/many
|
||||
:db/isComponent true
|
||||
:db/doc "The expense account categories for this invoice"}
|
||||
|
||||
|
||||
{:db/ident :invoice-status/paid}
|
||||
{:db/ident :invoice-status/unpaid}
|
||||
@@ -312,17 +305,15 @@
|
||||
:db/valueType :db.type/long
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/doc "The code for the expense account"}
|
||||
{:db/ident :invoice-expense-account/location
|
||||
{:db/ident :invoice-expense-account/location
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/doc "Location for this expense account"}
|
||||
:db/doc "Location for this expense account"}
|
||||
{:db/ident :invoice-expense-account/amount
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/doc "The amount that this contributes to"}])
|
||||
|
||||
|
||||
|
||||
(def payment-schema
|
||||
[{:db/ident :payment/original-id
|
||||
:db/valueType :db.type/long
|
||||
@@ -373,9 +364,8 @@
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/doc "raw data used to generate check pdf"}
|
||||
|
||||
|
||||
;; relations
|
||||
;; relations
|
||||
{:db/ident :payment/vendor
|
||||
:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one
|
||||
@@ -400,8 +390,7 @@
|
||||
|
||||
{:db/ident :payment-type/cash}
|
||||
{:db/ident :payment-type/check}
|
||||
{:db/ident :payment-type/debit}
|
||||
])
|
||||
{:db/ident :payment-type/debit}])
|
||||
|
||||
(def invoice-payment-schema
|
||||
[{:db/ident :invoice-payment/original-id
|
||||
@@ -414,7 +403,7 @@
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/doc "The amount that was paid to this invoice"}
|
||||
|
||||
|
||||
;; relations
|
||||
{:db/ident :invoice-payment/invoice
|
||||
:db/valueType :db.type/ref
|
||||
@@ -481,8 +470,7 @@
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/doc "The check number that was parsed from the description"}
|
||||
|
||||
|
||||
;; relations
|
||||
;; relations
|
||||
{:db/ident :transaction/vendor
|
||||
:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one
|
||||
@@ -498,8 +486,7 @@
|
||||
{:db/ident :transaction/payment
|
||||
:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/doc "The payment that this transaction matched to"}
|
||||
])
|
||||
:db/doc "The payment that this transaction matched to"}])
|
||||
|
||||
(def user-schema
|
||||
[{:db/ident :user/original-id
|
||||
@@ -531,12 +518,10 @@
|
||||
;;enums
|
||||
{:db/ident :user-role/admin}
|
||||
{:db/ident :user-role/user}
|
||||
{:db/ident :user-role/none}
|
||||
])
|
||||
{:db/ident :user-role/none}])
|
||||
|
||||
(def base-schema
|
||||
[ address-schema contact-schema vendor-schema client-schema bank-account-schema invoice-schema invoice-expense-account-schema payment-schema invoice-payment-schema transaction-schema user-schema])
|
||||
|
||||
[address-schema contact-schema vendor-schema client-schema bank-account-schema invoice-schema invoice-expense-account-schema payment-schema invoice-payment-schema transaction-schema user-schema])
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn migrate-vendors [_]
|
||||
@@ -590,19 +575,19 @@
|
||||
|
||||
(defn add-sorter-fields-2 [q sort-map args]
|
||||
(reduce
|
||||
(fn [q {:keys [sort-key]}]
|
||||
(merge-query q
|
||||
{:query {:find [(last (last (sort-map
|
||||
sort-key
|
||||
(println "Warning, trying to sort by unsupported field" sort-key))))]
|
||||
:where (sort-map
|
||||
sort-key
|
||||
(println "Warning, trying to sort by unsupported field" sort-key))}}))
|
||||
q
|
||||
(:sort args)))
|
||||
(fn [q {:keys [sort-key]}]
|
||||
(merge-query q
|
||||
{:query {:find [(last (last (sort-map
|
||||
sort-key
|
||||
(println "Warning, trying to sort by unsupported field" sort-key))))]
|
||||
:where (sort-map
|
||||
sort-key
|
||||
(println "Warning, trying to sort by unsupported field" sort-key))}}))
|
||||
q
|
||||
(:sort args)))
|
||||
|
||||
(defn apply-sort-3 [args results]
|
||||
|
||||
|
||||
(let [sort-bys (conj (into [] (:sort args))
|
||||
{:sort-key "default" :asc (if (contains? args :default-asc?)
|
||||
(:default-asc? args)
|
||||
@@ -611,7 +596,7 @@
|
||||
comparator (fn [xs ys]
|
||||
(reduce
|
||||
(fn [_ i]
|
||||
|
||||
|
||||
(let [comparison (if (:asc (nth sort-bys i))
|
||||
(compare (nth xs i) (nth ys i))
|
||||
(compare (nth ys i) (nth xs i)))]
|
||||
@@ -625,18 +610,18 @@
|
||||
|
||||
;; TODO replace COULD JUST BE SORT-3
|
||||
(defn apply-sort-4 [args results]
|
||||
|
||||
|
||||
(let [sort-bys (-> []
|
||||
(into (:sort args))
|
||||
(conj {:sort-key "default" :asc (if (contains? args :default-asc?)
|
||||
(:default-asc? args)
|
||||
true)})
|
||||
(:default-asc? args)
|
||||
true)})
|
||||
(conj {:sort-key "e" :asc true}))
|
||||
length (count sort-bys)
|
||||
comparator (fn [xs ys]
|
||||
(reduce
|
||||
(fn [_ i]
|
||||
|
||||
|
||||
(let [comparison (if (:asc (nth sort-bys i))
|
||||
(compare (nth xs i) (nth ys i))
|
||||
(compare (nth ys i) (nth xs i)))]
|
||||
@@ -657,7 +642,7 @@
|
||||
(defn apply-pagination [args results]
|
||||
{:ids (->> results
|
||||
(drop (or (:start args) 0))
|
||||
(take (or (:count args )
|
||||
(take (or (:count args)
|
||||
(:per-page args)
|
||||
default-pagination-size))
|
||||
(map last))
|
||||
@@ -669,8 +654,8 @@
|
||||
(reduce
|
||||
(fn [full-tx batch]
|
||||
(let [batch (conj (vec batch) {:db/id "datomic.tx"
|
||||
:audit/user (str (:user/role id) "-" (:user/name id))
|
||||
:audit/batch batch-id})
|
||||
:audit/user (str (:user/role id) "-" (:user/name id))
|
||||
:audit/batch batch-id})
|
||||
_ (mu/log ::transacting-batch
|
||||
:batch batch-id
|
||||
:count (count batch))
|
||||
@@ -687,18 +672,17 @@
|
||||
(partition-all 200 txes))))
|
||||
|
||||
(defn audit-transact [txes id]
|
||||
(try
|
||||
(try
|
||||
@(dc/transact-async conn (conj txes {:db/id "datomic.tx"
|
||||
:audit/user (str (:user/role id) "-" (:user/name id))}))
|
||||
:audit/user (str (:user/role id) "-" (:user/name id))}))
|
||||
(catch Exception e
|
||||
(mu/log ::transaction-error
|
||||
:exception e
|
||||
:level :error
|
||||
:tx txes)
|
||||
(throw e)
|
||||
)))
|
||||
(throw e))))
|
||||
|
||||
(defn pull-many [db read ids ]
|
||||
(defn pull-many [db read ids]
|
||||
(->> (dc/q '[:find (pull ?e r)
|
||||
:in $ [?e ...] r]
|
||||
db
|
||||
@@ -706,22 +690,22 @@
|
||||
read)
|
||||
(map first)))
|
||||
|
||||
(defn pull-many-by-id [db read ids ]
|
||||
(defn pull-many-by-id [db read ids]
|
||||
(into {}
|
||||
(map (fn [[e]]
|
||||
[(:db/id e) e]))
|
||||
(dc/q '[:find (pull ?e r)
|
||||
:in $ [?e ...] r]
|
||||
db
|
||||
ids
|
||||
read)))
|
||||
:in $ [?e ...] r]
|
||||
db
|
||||
ids
|
||||
read)))
|
||||
|
||||
(defn random-tempid []
|
||||
(str (UUID/randomUUID)))
|
||||
|
||||
(defn pull-id [db id]
|
||||
(if (sequential? id)
|
||||
(ffirst (dc/q '[:find ?i
|
||||
(ffirst (dc/q '[:find ?i
|
||||
:in $ [?a ?v]
|
||||
:where [?i ?a ?v]]
|
||||
db
|
||||
@@ -734,170 +718,163 @@
|
||||
(defn pull-ref [db k id]
|
||||
(:db/id (pull-attr db k id)))
|
||||
|
||||
#_(comment
|
||||
(dc/pull (dc/db conn) '[*] 175921860633685)
|
||||
|
||||
(upsert-entity (dc/db conn) {:db/id 175921860633685 :invoice/invoice-number nil :invoice/date #inst "2021-01-01" :invoice/expense-accounts [:reset-rels [{:db/id "new" :invoice-expense-account/amount 1}]]})
|
||||
|
||||
(upsert-entity (dc/db conn) {:invoice/client #:db{:id 79164837221949},
|
||||
:invoice/status #:db{:id 101155069755470, :ident :invoice-status/paid},
|
||||
:invoice/due #inst "2020-12-23T08:00:00.000-00:00",
|
||||
:invoice/invoice-number "12648",
|
||||
:invoice/import-status
|
||||
:import-status/imported,
|
||||
:invoice/vendor nil,
|
||||
:invoice/date #inst "2020-12-16T08:00:00.000-00:00",
|
||||
:entity/migration-key 17592234924273,
|
||||
:db/id 175921860633685,
|
||||
:invoice/outstanding-balance 0.0,
|
||||
:invoice/expense-accounts
|
||||
[{:entity/migration-key 17592234924274,
|
||||
:invoice-expense-account/location nil
|
||||
:invoice-expense-account/amount 360.0,
|
||||
:invoice-expense-account/account #:db{:id 92358976759248}}]})
|
||||
|
||||
#_(comment
|
||||
(dc/pull (dc/db conn) '[*] 175921860633685)
|
||||
|
||||
(upsert-entity (dc/db conn) {:db/id 175921860633685 :invoice/invoice-number nil :invoice/date #inst "2021-01-01" :invoice/expense-accounts [:reset-rels [{:db/id "new" :invoice-expense-account/amount 1}]]})
|
||||
|
||||
(upsert-entity (dc/db conn) {:invoice/client #:db{:id 79164837221949},
|
||||
:invoice/status #:db{:id 101155069755470, :ident :invoice-status/paid},
|
||||
:invoice/due #inst "2020-12-23T08:00:00.000-00:00",
|
||||
:invoice/invoice-number "12648",
|
||||
:invoice/import-status
|
||||
:import-status/imported,
|
||||
:invoice/vendor nil,
|
||||
:invoice/date #inst "2020-12-16T08:00:00.000-00:00",
|
||||
:entity/migration-key 17592234924273,
|
||||
:db/id 175921860633685,
|
||||
:invoice/outstanding-balance 0.0,
|
||||
:invoice/expense-accounts
|
||||
[{:entity/migration-key 17592234924274,
|
||||
:invoice-expense-account/location nil
|
||||
:invoice-expense-account/amount 360.0,
|
||||
:invoice-expense-account/account #:db{:id 92358976759248}}],})
|
||||
|
||||
|
||||
|
||||
#_(dc/pull (dc/db conn) auto-ap.datomic.clients 79164837221904)
|
||||
(upsert-entity (dc/db conn) {:client/name "20Twenty - WG Development LLC",
|
||||
:client/square-locations
|
||||
[{:db/id 83562883711605,
|
||||
:entity/migration-key 17592258901782,
|
||||
:square-location/square-id "L2579ATQ0X1ET",
|
||||
:square-location/name "20Twenty",
|
||||
:square-location/client-location "WG"}],
|
||||
:client/square-auth-token
|
||||
"EAAAEEr749Ea6AdPTdngsmUPwIM3ETbPwcx3QQl_NS0KWuIL-JNzAg4f3W9DGQhb",
|
||||
:client/bank-accounts
|
||||
[{:bank-account/sort-order 2,
|
||||
:bank-account/include-in-reports true,
|
||||
:bank-account/number "3467",
|
||||
:bank-account/code "20TY-WFCC3467",
|
||||
:bank-account/locations ["WG"],
|
||||
:entity/migration-key 17592245102834,
|
||||
:bank-account/current-balance 11160.289999999979,
|
||||
:bank-account/name "Wells Fargo CC - 3467",
|
||||
:db/id 83562883732805,
|
||||
:bank-account/start-date #inst "2021-12-01T08:00:00.000-00:00",
|
||||
:bank-account/visible true,
|
||||
:bank-account/type
|
||||
#:db{:id 101155069755504, :ident :bank-account-type/credit},
|
||||
:bank-account/intuit-bank-account #:db{:id 105553116286744},
|
||||
:bank-account/integration-status
|
||||
{:db/id 74766790691480,
|
||||
:entity/migration-key 17592267080690,
|
||||
:integration-status/last-updated #inst "2022-08-23T03:47:44.892-00:00",
|
||||
:integration-status/last-attempt #inst "2022-08-23T03:47:44.892-00:00",
|
||||
#_(dc/pull (dc/db conn) auto-ap.datomic.clients 79164837221904)
|
||||
(upsert-entity (dc/db conn) {:client/name "20Twenty - WG Development LLC",
|
||||
:client/square-locations
|
||||
[{:db/id 83562883711605,
|
||||
:entity/migration-key 17592258901782,
|
||||
:square-location/square-id "L2579ATQ0X1ET",
|
||||
:square-location/name "20Twenty",
|
||||
:square-location/client-location "WG"}],
|
||||
:client/square-auth-token
|
||||
"EAAAEEr749Ea6AdPTdngsmUPwIM3ETbPwcx3QQl_NS0KWuIL-JNzAg4f3W9DGQhb",
|
||||
:client/bank-accounts
|
||||
[{:bank-account/sort-order 2,
|
||||
:bank-account/include-in-reports true,
|
||||
:bank-account/number "3467",
|
||||
:bank-account/code "20TY-WFCC3467",
|
||||
:bank-account/locations ["WG"],
|
||||
:entity/migration-key 17592245102834,
|
||||
:bank-account/current-balance 11160.289999999979,
|
||||
:bank-account/name "Wells Fargo CC - 3467",
|
||||
:db/id 83562883732805,
|
||||
:bank-account/start-date #inst "2021-12-01T08:00:00.000-00:00",
|
||||
:bank-account/visible true,
|
||||
:bank-account/type
|
||||
#:db{:id 101155069755504, :ident :bank-account-type/credit},
|
||||
:bank-account/intuit-bank-account #:db{:id 105553116286744},
|
||||
:bank-account/integration-status
|
||||
{:db/id 74766790691480,
|
||||
:entity/migration-key 17592267080690,
|
||||
:integration-status/last-updated #inst "2022-08-23T03:47:44.892-00:00",
|
||||
:integration-status/last-attempt #inst "2022-08-23T03:47:44.892-00:00",
|
||||
:integration-status/state
|
||||
#:db{:id 101155069755529, :ident :integration-state/success}},
|
||||
:bank-account/bank-name "Wells Fargo"}
|
||||
{:bank-account/sort-order 0,
|
||||
:bank-account/include-in-reports true,
|
||||
:bank-account/numeric-code 11301,
|
||||
:bank-account/check-number 301,
|
||||
:bank-account/number "1734742859",
|
||||
:bank-account/code "20TY-WF2882",
|
||||
:bank-account/locations ["WG"],
|
||||
:bank-account/bank-code "11-4288/1210 4285",
|
||||
:entity/migration-key 17592241193004,
|
||||
:bank-account/current-balance -47342.54000000085,
|
||||
:bank-account/name "Wells Fargo Main - 2859",
|
||||
:db/id 83562883732846,
|
||||
:bank-account/start-date #inst "2021-12-01T08:00:00.000-00:00",
|
||||
:bank-account/visible true,
|
||||
:bank-account/type
|
||||
#:db{:id 101155069755468, :ident :bank-account-type/check},
|
||||
:bank-account/intuit-bank-account #:db{:id 105553116286745},
|
||||
:bank-account/routing "121042882",
|
||||
:bank-account/integration-status
|
||||
{:db/id 74766790691458,
|
||||
:entity/migration-key 17592267080255,
|
||||
:integration-status/last-updated #inst "2022-08-23T03:46:45.879-00:00",
|
||||
:integration-status/last-attempt #inst "2022-08-23T03:46:45.879-00:00",
|
||||
:integration-status/state
|
||||
#:db{:id 101155069755529, :ident :integration-state/success}},
|
||||
:bank-account/bank-name "Wells Fargo"}
|
||||
{:bank-account/sort-order 1,
|
||||
:bank-account/include-in-reports true,
|
||||
:bank-account/numeric-code 20101,
|
||||
:bank-account/yodlee-account-id 27345526,
|
||||
:bank-account/number "41006",
|
||||
:bank-account/code "20TY-Amex41006",
|
||||
:bank-account/locations ["WG"],
|
||||
:entity/migration-key 17592241193006,
|
||||
:bank-account/current-balance 9674.069999999963,
|
||||
:bank-account/name "Amex - 41006",
|
||||
:db/id 83562883732847,
|
||||
:bank-account/visible true,
|
||||
:bank-account/type
|
||||
#:db{:id 101155069755504, :ident :bank-account-type/credit},
|
||||
:bank-account/bank-name "American Express"}
|
||||
{:bank-account/sort-order 3,
|
||||
:bank-account/include-in-reports true,
|
||||
:bank-account/numeric-code 11101,
|
||||
:bank-account/code "20TY-0",
|
||||
:bank-account/locations ["WG"],
|
||||
:entity/migration-key 17592241193005,
|
||||
:bank-account/current-balance 0.0,
|
||||
:bank-account/name "CASH",
|
||||
:db/id 83562883732848,
|
||||
:bank-account/visible true,
|
||||
:bank-account/type
|
||||
#:db{:id 101155069755469, :ident :bank-account-type/cash}}],
|
||||
:entity/migration-key 17592241193003,
|
||||
:db/id 79164837221904,
|
||||
:client/address
|
||||
{:db/id 105553116285906,
|
||||
:entity/migration-key 17592250661126,
|
||||
:address/street1 "1389 Lincoln Ave",
|
||||
:address/city "San Jose",
|
||||
:address/state "CA",
|
||||
:address/zip "95125"},
|
||||
:client/code "NY",
|
||||
:client/locations ["WE" "NG"],
|
||||
:client/square-integration-status
|
||||
{:db/id 74766790691447,
|
||||
:entity/migration-key 17592267072653,
|
||||
:integration-status/last-updated #inst "2022-08-23T13:09:16.082-00:00",
|
||||
:integration-status/last-attempt #inst "2022-08-23T13:08:47.018-00:00",
|
||||
:integration-status/state
|
||||
#:db{:id 101155069755529, :ident :integration-state/success}},
|
||||
:bank-account/bank-name "Wells Fargo"}
|
||||
{:bank-account/sort-order 0,
|
||||
:bank-account/include-in-reports true,
|
||||
:bank-account/numeric-code 11301,
|
||||
:bank-account/check-number 301,
|
||||
:bank-account/number "1734742859",
|
||||
:bank-account/code "20TY-WF2882",
|
||||
:bank-account/locations ["WG"],
|
||||
:bank-account/bank-code "11-4288/1210 4285",
|
||||
:entity/migration-key 17592241193004,
|
||||
:bank-account/current-balance -47342.54000000085,
|
||||
:bank-account/name "Wells Fargo Main - 2859",
|
||||
:db/id 83562883732846,
|
||||
:bank-account/start-date #inst "2021-12-01T08:00:00.000-00:00",
|
||||
:bank-account/visible true,
|
||||
:bank-account/type
|
||||
#:db{:id 101155069755468, :ident :bank-account-type/check},
|
||||
:bank-account/intuit-bank-account #:db{:id 105553116286745},
|
||||
:bank-account/routing "121042882",
|
||||
:bank-account/integration-status
|
||||
{:db/id 74766790691458,
|
||||
:entity/migration-key 17592267080255,
|
||||
:integration-status/last-updated #inst "2022-08-23T03:46:45.879-00:00",
|
||||
:integration-status/last-attempt #inst "2022-08-23T03:46:45.879-00:00",
|
||||
:integration-status/state
|
||||
#:db{:id 101155069755529, :ident :integration-state/success}},
|
||||
:bank-account/bank-name "Wells Fargo"}
|
||||
{:bank-account/sort-order 1,
|
||||
:bank-account/include-in-reports true,
|
||||
:bank-account/numeric-code 20101,
|
||||
:bank-account/yodlee-account-id 27345526,
|
||||
:bank-account/number "41006",
|
||||
:bank-account/code "20TY-Amex41006",
|
||||
:bank-account/locations ["WG"],
|
||||
:entity/migration-key 17592241193006,
|
||||
:bank-account/current-balance 9674.069999999963,
|
||||
:bank-account/name "Amex - 41006",
|
||||
:db/id 83562883732847,
|
||||
:bank-account/visible true,
|
||||
:bank-account/type
|
||||
#:db{:id 101155069755504, :ident :bank-account-type/credit},
|
||||
:bank-account/bank-name "American Express"}
|
||||
{:bank-account/sort-order 3,
|
||||
:bank-account/include-in-reports true,
|
||||
:bank-account/numeric-code 11101,
|
||||
:bank-account/code "20TY-0",
|
||||
:bank-account/locations ["WG"],
|
||||
:entity/migration-key 17592241193005,
|
||||
:bank-account/current-balance 0.0,
|
||||
:bank-account/name "CASH",
|
||||
:db/id 83562883732848,
|
||||
:bank-account/visible true,
|
||||
:bank-account/type
|
||||
#:db{:id 101155069755469, :ident :bank-account-type/cash}}],
|
||||
:entity/migration-key 17592241193003,
|
||||
:db/id 79164837221904,
|
||||
:client/address
|
||||
{:db/id 105553116285906,
|
||||
:entity/migration-key 17592250661126,
|
||||
:address/street1 "1389 Lincoln Ave",
|
||||
:address/city "San Jose",
|
||||
:address/state "CA",
|
||||
:address/zip "95125"},
|
||||
:client/code "NY",
|
||||
:client/locations ["WE" "NG"],
|
||||
:client/square-integration-status
|
||||
{:db/id 74766790691447,
|
||||
:entity/migration-key 17592267072653,
|
||||
:integration-status/last-updated #inst "2022-08-23T13:09:16.082-00:00",
|
||||
:integration-status/last-attempt #inst "2022-08-23T13:08:47.018-00:00",
|
||||
:integration-status/state
|
||||
#:db{:id 101155069755529, :ident :integration-state/success}}})
|
||||
|
||||
)
|
||||
#:db{:id 101155069755529, :ident :integration-state/success}}}))
|
||||
|
||||
(defn install-functions []
|
||||
@(dc/transact conn
|
||||
(edn/read-string {:readers {'db/id id-literal
|
||||
'db/fn construct}} (slurp (io/resource "functions.edn")))))
|
||||
(edn/read-string {:readers {'db/id id-literal
|
||||
'db/fn construct}} (slurp (io/resource "functions.edn")))))
|
||||
(defn all-schema []
|
||||
(edn/read-string (slurp (io/resource "schema.edn"))))
|
||||
|
||||
(defn transact-schema [conn]
|
||||
@(dc/transact conn
|
||||
(edn/read-string (slurp (io/resource "schema.edn"))))
|
||||
(edn/read-string (slurp (io/resource "schema.edn"))))
|
||||
|
||||
;; this is temporary for any new stuff that needs to be asserted for cloud migration.
|
||||
@(dc/transact conn
|
||||
(edn/read-string (slurp (io/resource "cloud-migration-schema.edn")))))
|
||||
(edn/read-string (slurp (io/resource "cloud-migration-schema.edn")))))
|
||||
|
||||
(defn backoff [n]
|
||||
(let [base-timeout 500
|
||||
(let [base-timeout 500
|
||||
max-timeout 300000 ; 5 minutes
|
||||
max-retries 10
|
||||
backoff-time (* base-timeout (Math/pow 2 (min n max-retries)))]
|
||||
(min (+ backoff-time (rand-int base-timeout)) max-timeout)))
|
||||
|
||||
(defn transact-with-backoff
|
||||
([tx ] (transact-with-backoff tx 0))
|
||||
([tx] (transact-with-backoff tx 0))
|
||||
([tx attempt]
|
||||
(try
|
||||
(try
|
||||
@(dc/transact conn tx)
|
||||
(catch Exception e
|
||||
(if (< attempt 10)
|
||||
(do
|
||||
(do
|
||||
(Thread/sleep (backoff attempt))
|
||||
(mu/log ::transact-failed
|
||||
:exception e
|
||||
@@ -923,7 +900,6 @@
|
||||
(into #{}
|
||||
(map :db/id (:user/clients id [])))))
|
||||
|
||||
|
||||
(defn query2 [query]
|
||||
(apply dc/q (:query query) (:args query)))
|
||||
|
||||
@@ -933,14 +909,14 @@
|
||||
(defn observable-query [query]
|
||||
(mu/with-context {:query (pr-str (:query query))
|
||||
:args (pr-str (:args query))}
|
||||
(mu/trace ::query
|
||||
[]
|
||||
(let [query-results (dc/query {:query (:query query)
|
||||
:args (:args query)
|
||||
:query-stats true
|
||||
:io-context ::hello})]
|
||||
(alog/info ::query-stats
|
||||
:io-stats (pr-str (:io-stats query-results))
|
||||
:query-stats (pr-str (:query-stats query-results)))
|
||||
(:ret query-results)))))
|
||||
(mu/trace ::query
|
||||
[]
|
||||
(let [query-results (dc/query {:query (:query query)
|
||||
:args (:args query)
|
||||
:query-stats true
|
||||
:io-context ::hello})]
|
||||
(alog/info ::query-stats
|
||||
:io-stats (pr-str (:io-stats query-results))
|
||||
:query-stats (pr-str (:query-stats query-results)))
|
||||
(:ret query-results)))))
|
||||
|
||||
|
||||
@@ -12,18 +12,18 @@
|
||||
[datomic.api :as dc]))
|
||||
|
||||
(defn <-datomic [a]
|
||||
(-> a
|
||||
(-> a
|
||||
(update :account/applicability :db/ident)
|
||||
(update :account/invoice-allowance :db/ident)
|
||||
(update :account/vendor-allowance :db/ident)))
|
||||
|
||||
(def default-read ['* {:account/type [:db/ident :db/id]
|
||||
:account/applicability [:db/ident :db/id]
|
||||
:account/applicability [:db/ident :db/id]
|
||||
:account/invoice-allowance [:db/ident :db/id]
|
||||
:account/vendor-allowance [:db/ident :db/id]
|
||||
:account/client-overrides [:db/id
|
||||
:account-client-override/name
|
||||
{:account-client-override/client [:db/id :client/name]}]}])
|
||||
:account/client-overrides [:db/id
|
||||
:account-client-override/name
|
||||
{:account-client-override/client [:db/id :client/name]}]}])
|
||||
|
||||
(defn clientize [a client]
|
||||
(if-let [override-name (->> a
|
||||
@@ -52,44 +52,44 @@
|
||||
(map <-datomic)))))
|
||||
|
||||
(defn get-for-vendor [vendor-id client-id]
|
||||
(if client-id
|
||||
(if client-id
|
||||
(->>
|
||||
(dc/q '[:find (pull ?e r)
|
||||
:in $ ?v ?c r
|
||||
:where (or-join [?v ?c ?e]
|
||||
(and [?v :vendor/account-overrides ?ao]
|
||||
[?ao :vendor-account-override/client ?c]
|
||||
[?ao :vendor-account-override/account ?e])
|
||||
(and [?v :vendor/account-overrides ?ao]
|
||||
(not [?ao :vendor-account-override/client ?c])
|
||||
[?v :vendor/default-account ?e])
|
||||
(and (not [?v :vendor/account-overrides])
|
||||
[?v :vendor/default-account ?e]))]
|
||||
(dc/q '[:find (pull ?e r)
|
||||
:in $ ?v ?c r
|
||||
:where (or-join [?v ?c ?e]
|
||||
(and [?v :vendor/account-overrides ?ao]
|
||||
[?ao :vendor-account-override/client ?c]
|
||||
[?ao :vendor-account-override/account ?e])
|
||||
(and [?v :vendor/account-overrides ?ao]
|
||||
(not [?ao :vendor-account-override/client ?c])
|
||||
[?v :vendor/default-account ?e])
|
||||
(and (not [?v :vendor/account-overrides])
|
||||
[?v :vendor/default-account ?e]))]
|
||||
|
||||
(dc/db conn )
|
||||
(dc/db conn)
|
||||
vendor-id
|
||||
client-id
|
||||
default-read)
|
||||
(map first)
|
||||
(map <-datomic)
|
||||
first)
|
||||
(map first)
|
||||
(map <-datomic)
|
||||
first)
|
||||
(<-datomic (dc/q '[:find (pull ?e r)
|
||||
:in $ ?v r
|
||||
:where [?v :vendor/default-account ?e]]
|
||||
|
||||
(dc/db conn )
|
||||
(dc/db conn)
|
||||
vendor-id
|
||||
default-read))))
|
||||
|
||||
(defn get-account-by-numeric-code-and-sets [numeric-code _]
|
||||
(->>
|
||||
(dc/q {:find ['(pull ?e [* {:account/type [:db/ident :db/id]}])]
|
||||
:in ['$ '?numeric-code]
|
||||
:where ['[?e :account/numeric-code ?numeric-code]]}
|
||||
(dc/db conn) numeric-code)
|
||||
(map first)
|
||||
(map <-datomic)
|
||||
(first)))
|
||||
(dc/q {:find ['(pull ?e [* {:account/type [:db/ident :db/id]}])]
|
||||
:in ['$ '?numeric-code]
|
||||
:where ['[?e :account/numeric-code ?numeric-code]]}
|
||||
(dc/db conn) numeric-code)
|
||||
(map first)
|
||||
(map <-datomic)
|
||||
(first)))
|
||||
|
||||
(defn raw-graphql-ids [db args]
|
||||
(let [query (cond-> {:query {:find []
|
||||
@@ -111,23 +111,21 @@
|
||||
:args [(re-pattern (str "(?i)" (:name-like args)))]})
|
||||
|
||||
true
|
||||
(merge-query {:query {:find ['?sort-default '?e ]
|
||||
(merge-query {:query {:find ['?sort-default '?e]
|
||||
:where ['[?e :account/name]
|
||||
'[?e :account/numeric-code ?sort-default]]}}))]
|
||||
|
||||
|
||||
(cond->> (query2 query)
|
||||
true (apply-sort-3 args)
|
||||
true (apply-pagination args))))
|
||||
|
||||
|
||||
(defn graphql-results [ids db _]
|
||||
(let [results (->> (pull-many db default-read ids)
|
||||
(group-by :db/id))
|
||||
accounts (->> ids
|
||||
(map results)
|
||||
(map first)
|
||||
(map <-datomic))]
|
||||
(map results)
|
||||
(map first)
|
||||
(map <-datomic))]
|
||||
accounts))
|
||||
|
||||
(defn get-graphql [args]
|
||||
|
||||
@@ -13,15 +13,12 @@
|
||||
(def default-read '[* {:client/_bank-accounts [:db/id]}])
|
||||
|
||||
(defn <-datomic [x]
|
||||
(->> x
|
||||
(map #(update % :bank-account/type :db/ident))
|
||||
))
|
||||
|
||||
|
||||
(->> x
|
||||
(map #(update % :bank-account/type :db/ident))))
|
||||
|
||||
(defn get-by-id [id]
|
||||
(->> [(dc/pull (dc/db conn ) default-read id)]
|
||||
(<-datomic)
|
||||
(->> [(dc/pull (dc/db conn) default-read id)]
|
||||
(<-datomic)
|
||||
(first)))
|
||||
|
||||
|
||||
|
||||
@@ -16,22 +16,22 @@
|
||||
|
||||
(defn <-datomic [result]
|
||||
(-> result
|
||||
(update :payment/date c/from-date)
|
||||
(update :payment/status :db/ident)
|
||||
(update :payment/type :db/ident)
|
||||
(update :transaction/_payment (fn [transactions]
|
||||
(mapv (fn [transaction]
|
||||
(update transaction :transaction/date c/from-date))
|
||||
transactions)))
|
||||
(rename-keys {:invoice-payment/_payment :payment/invoices})))
|
||||
(update :payment/date c/from-date)
|
||||
(update :payment/status :db/ident)
|
||||
(update :payment/type :db/ident)
|
||||
(update :transaction/_payment (fn [transactions]
|
||||
(mapv (fn [transaction]
|
||||
(update transaction :transaction/date c/from-date))
|
||||
transactions)))
|
||||
(rename-keys {:invoice-payment/_payment :payment/invoices})))
|
||||
|
||||
(def default-read '[*
|
||||
{:invoice-payment/_payment [* {:invoice-payment/invoice [*]}]}
|
||||
{:payment/client [:client/name :db/id :client/code]}
|
||||
{:payment/bank-account [*]}
|
||||
{:payment/vendor [:vendor/name {:vendor/default-account
|
||||
[:account/name :account/numeric-code :db/id]} :db/id {:vendor/primary-contact [*]} {:vendor/address [*]}]}
|
||||
{:payment/status [:db/ident]}
|
||||
{:invoice-payment/_payment [* {:invoice-payment/invoice [*]}]}
|
||||
{:payment/client [:client/name :db/id :client/code]}
|
||||
{:payment/bank-account [*]}
|
||||
{:payment/vendor [:vendor/name {:vendor/default-account
|
||||
[:account/name :account/numeric-code :db/id]} :db/id {:vendor/primary-contact [*]} {:vendor/address [*]}]}
|
||||
{:payment/status [:db/ident]}
|
||||
{:payment/type [:db/ident]}
|
||||
{:transaction/_payment [:db/id :transaction/date]}])
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
(:sort args) (add-sorter-fields {"client" ['[?e :payment/client ?c]
|
||||
'[?c :client/name ?sort-client]]
|
||||
"vendor" ['[?e :payment/vendor ?v]
|
||||
'[?v :vendor/name ?sort-vendor]]
|
||||
'[?v :vendor/name ?sort-vendor]]
|
||||
"bank-account" ['[?e :payment/bank-account ?ba]
|
||||
'[?ba :bank-account/name ?sort-bank-account]]
|
||||
"check-number" ['[(get-else $ ?e :payment/check-number 0) ?sort-check-number]]
|
||||
@@ -73,7 +73,7 @@
|
||||
:where []}
|
||||
:args [(:exact-match-id args)]})
|
||||
|
||||
(:vendor-id args)
|
||||
(:vendor-id args)
|
||||
(merge-query {:query {:in ['?vendor-id]
|
||||
:where ['[?e :payment/vendor ?vendor-id]]}
|
||||
:args [(:vendor-id args)]})
|
||||
@@ -100,13 +100,13 @@
|
||||
:where ['[?e :payment/bank-account ?bank-account-id]]}
|
||||
:args [(:bank-account-id args)]})
|
||||
|
||||
(:amount-gte args)
|
||||
(:amount-gte args)
|
||||
(merge-query {:query {:in ['?amount-gte]
|
||||
:where ['[?e :payment/amount ?a]
|
||||
'[(>= ?a ?amount-gte)]]}
|
||||
:args [(:amount-gte args)]})
|
||||
|
||||
(:amount-lte args)
|
||||
(:amount-lte args)
|
||||
(merge-query {:query {:in ['?amount-lte]
|
||||
:where ['[?e :payment/amount ?a]
|
||||
'[(<= ?a ?amount-lte)]]}
|
||||
@@ -118,7 +118,6 @@
|
||||
'[(iol-ion.query/dollars= ?transaction-amount ?amount)]]}
|
||||
:args [(:amount args)]})
|
||||
|
||||
|
||||
(:status args)
|
||||
(merge-query {:query {:in ['?status]
|
||||
:where ['[?e :payment/status ?status]]}
|
||||
@@ -137,7 +136,6 @@
|
||||
true
|
||||
(merge-query {:query {:find ['?sort-default '?e]}})))]
|
||||
|
||||
|
||||
(cond->> (observable-query query)
|
||||
true (apply-sort-3 (assoc args :default-asc? false))
|
||||
true (apply-pagination args)))))
|
||||
@@ -146,9 +144,9 @@
|
||||
(let [results (->> (pull-many db default-read ids)
|
||||
(group-by :db/id))
|
||||
payments (->> ids
|
||||
(map results)
|
||||
(map first)
|
||||
(mapv <-datomic))]
|
||||
(map results)
|
||||
(map first)
|
||||
(mapv <-datomic))]
|
||||
payments))
|
||||
|
||||
(defn get-graphql [args]
|
||||
@@ -169,7 +167,7 @@
|
||||
[]))
|
||||
|
||||
(defn get-by-id [id]
|
||||
(->>
|
||||
(->>
|
||||
(dc/pull (dc/db conn) default-read id)
|
||||
(<-datomic)))
|
||||
|
||||
|
||||
@@ -122,8 +122,6 @@
|
||||
Long/parseLong
|
||||
(#(hash-map :db/id %)))))
|
||||
|
||||
|
||||
|
||||
(defn exact-match [identifier]
|
||||
(when (and identifier (not-empty identifier))
|
||||
(some-> (solr/query solr/impl "clients"
|
||||
@@ -170,7 +168,6 @@
|
||||
matching-ids)
|
||||
(set (map :db/id (:clients args))))
|
||||
|
||||
|
||||
query (cond-> {:query {:find []
|
||||
:in ['$]
|
||||
:where []}
|
||||
@@ -179,7 +176,6 @@
|
||||
(merge-query {:query {:in ['[?e ...]]}
|
||||
:args [(set valid-ids)]})
|
||||
|
||||
|
||||
(:sort args) (add-sorter-fields {"name" ['[?e :client/name ?sort-name]]}
|
||||
args)
|
||||
|
||||
@@ -195,7 +191,6 @@
|
||||
(map cleanse))]
|
||||
results))
|
||||
|
||||
|
||||
(defn get-graphql-page [args]
|
||||
(let [db (dc/db conn)
|
||||
{ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)]
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"date" ['[?e :expected-deposit/date ?sort-date]]
|
||||
"total" ['[?e :expected-deposit/total ?sort-total]]
|
||||
"fee" ['[?e :expected-deposit/fee ?sort-fee]]}
|
||||
args)
|
||||
args)
|
||||
|
||||
true
|
||||
(merge-query {:query {:in ['[?xx ...]]
|
||||
@@ -58,13 +58,13 @@
|
||||
'[?client-id :client/code ?client-code]]}
|
||||
:args [(:client-code args)]})
|
||||
|
||||
(:total-gte args)
|
||||
(:total-gte args)
|
||||
(merge-query {:query {:in ['?total-gte]
|
||||
:where ['[?e :expected-deposit/total ?a]
|
||||
'[(>= ?a ?total-gte)]]}
|
||||
:args [(:total-gte args)]})
|
||||
|
||||
(:total-lte args)
|
||||
(:total-lte args)
|
||||
(merge-query {:query {:in ['?total-lte]
|
||||
:where ['[?e :expected-deposit/total ?a]
|
||||
'[(<= ?a ?total-lte)]]}
|
||||
@@ -76,7 +76,6 @@
|
||||
'[(iol-ion.query/dollars= ?expected-deposit-total ?total)]]}
|
||||
:args [(:total args)]})
|
||||
|
||||
|
||||
(:start (:date-range args))
|
||||
(merge-query {:query {:in '[?start-date]
|
||||
:where ['[?e :expected-deposit/date ?date]
|
||||
@@ -92,7 +91,7 @@
|
||||
true
|
||||
(merge-query {:query {:find ['?sort-default '?e]
|
||||
:where ['[?e :expected-deposit/date ?sort-default]]}}))]
|
||||
|
||||
|
||||
(cond->> (query2 query)
|
||||
true (apply-sort-3 args)
|
||||
true (apply-pagination args))))
|
||||
@@ -101,26 +100,26 @@
|
||||
(let [results (->> (pull-many db default-read ids)
|
||||
(group-by :db/id))
|
||||
payments (->> ids
|
||||
(map results)
|
||||
(map first)
|
||||
(mapv <-datomic)
|
||||
(map (fn get-totals [ed]
|
||||
(assoc ed :totals
|
||||
(->> (dc/q '[:find ?d4 (count ?c) (sum ?a)
|
||||
:in $ ?ed
|
||||
:where [?ed :expected-deposit/charges ?c]
|
||||
[?c :charge/total ?a]
|
||||
[?o :sales-order/charges ?c]
|
||||
[?o :sales-order/date ?d]
|
||||
[(clj-time.coerce/from-date ?d) ?d2]
|
||||
[(auto-ap.time/localize ?d2) ?d3]
|
||||
[(clj-time.coerce/to-local-date ?d3) ?d4]]
|
||||
(dc/db conn)
|
||||
(:db/id ed))
|
||||
(map (fn [[date count amount]]
|
||||
{:date (c/to-date-time date)
|
||||
:count count
|
||||
:amount amount})))))))]
|
||||
(map results)
|
||||
(map first)
|
||||
(mapv <-datomic)
|
||||
(map (fn get-totals [ed]
|
||||
(assoc ed :totals
|
||||
(->> (dc/q '[:find ?d4 (count ?c) (sum ?a)
|
||||
:in $ ?ed
|
||||
:where [?ed :expected-deposit/charges ?c]
|
||||
[?c :charge/total ?a]
|
||||
[?o :sales-order/charges ?c]
|
||||
[?o :sales-order/date ?d]
|
||||
[(clj-time.coerce/from-date ?d) ?d2]
|
||||
[(auto-ap.time/localize ?d2) ?d3]
|
||||
[(clj-time.coerce/to-local-date ?d3) ?d4]]
|
||||
(dc/db conn)
|
||||
(:db/id ed))
|
||||
(map (fn [[date count amount]]
|
||||
{:date (c/to-date-time date)
|
||||
:count count
|
||||
:amount amount})))))))]
|
||||
payments))
|
||||
|
||||
(defn get-graphql [args]
|
||||
|
||||
@@ -34,16 +34,15 @@
|
||||
|
||||
(defn <-datomic [x]
|
||||
(-> x
|
||||
(update :invoice/date coerce/from-date)
|
||||
(update :invoice/due coerce/from-date)
|
||||
(update :invoice/scheduled-payment coerce/from-date)
|
||||
(update :invoice/status :db/ident)
|
||||
(update :invoice/expense-accounts (fn [eas]
|
||||
(map
|
||||
#(update % :invoice-expense-account/account d-accounts/clientize (:db/id (:invoice/client x)))
|
||||
eas)))
|
||||
(rename-keys {:invoice-payment/_invoice :invoice/payments})))
|
||||
|
||||
(update :invoice/date coerce/from-date)
|
||||
(update :invoice/due coerce/from-date)
|
||||
(update :invoice/scheduled-payment coerce/from-date)
|
||||
(update :invoice/status :db/ident)
|
||||
(update :invoice/expense-accounts (fn [eas]
|
||||
(map
|
||||
#(update % :invoice-expense-account/account d-accounts/clientize (:db/id (:invoice/client x)))
|
||||
eas)))
|
||||
(rename-keys {:invoice-payment/_invoice :invoice/payments})))
|
||||
|
||||
(defn raw-graphql-ids
|
||||
([args]
|
||||
@@ -63,37 +62,29 @@
|
||||
valid-clients]}
|
||||
(cond-> {:query {:find []
|
||||
:in '[$ [?clients ?start ?end]]
|
||||
:where '[
|
||||
[(iol-ion.query/scan-invoices $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]
|
||||
]}
|
||||
:where '[[(iol-ion.query/scan-invoices $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]]}
|
||||
:args [db
|
||||
[valid-clients
|
||||
(some-> (:start (:date-range args)) coerce/to-date)
|
||||
(some-> (:end (:date-range args)) coerce/to-date)]]}
|
||||
|
||||
|
||||
(:client-id args)
|
||||
(merge-query {:query {:in ['?client-id]
|
||||
:where ['[?e :invoice/client ?client-id]]}
|
||||
:args [ (:client-id args)]})
|
||||
:args [(:client-id args)]})
|
||||
|
||||
(:client-code args)
|
||||
(merge-query {:query {:in ['?client-code]
|
||||
:where ['[?e :invoice/client ?client-id]
|
||||
'[?client-id :client/code ?client-code]]}
|
||||
:args [ (:client-code args)]})
|
||||
:args [(:client-code args)]})
|
||||
|
||||
(:original-id args)
|
||||
(merge-query {:query {:in ['?original-id]
|
||||
:where [
|
||||
'[?e :invoice/client ?c]
|
||||
:where ['[?e :invoice/client ?c]
|
||||
'[?c :client/original-id ?original-id]]}
|
||||
:args [ (cond-> (:original-id args)
|
||||
(string? (:original-id args)) Long/parseLong )]})
|
||||
|
||||
|
||||
|
||||
|
||||
:args [(cond-> (:original-id args)
|
||||
(string? (:original-id args)) Long/parseLong)]})
|
||||
|
||||
(:start (:due-range args)) (merge-query {:query {:in '[?start-due]
|
||||
:where ['[?e :invoice/due ?due]
|
||||
@@ -104,34 +95,33 @@
|
||||
:where ['[?e :invoice/due ?due]
|
||||
'[(<= ?due ?end-due)]]}
|
||||
:args [(coerce/to-date (:end (:due-range args)))]})
|
||||
|
||||
|
||||
(:import-status args)
|
||||
(merge-query {:query {:in ['?import-status]
|
||||
:where ['[?e :invoice/import-status ?import-status]]}
|
||||
:args [ (keyword "import-status" (:import-status args))]})
|
||||
:args [(keyword "import-status" (:import-status args))]})
|
||||
(:status args)
|
||||
(merge-query {:query {:in ['?status]
|
||||
:where ['[?e :invoice/status ?status]]}
|
||||
:args [ (:status args)]})
|
||||
:args [(:status args)]})
|
||||
(:vendor-id args)
|
||||
(merge-query {:query {:in ['?vendor-id]
|
||||
:where ['[?e :invoice/vendor ?vendor-id]]}
|
||||
:args [ (:vendor-id args)]})
|
||||
:args [(:vendor-id args)]})
|
||||
|
||||
(:account-id args)
|
||||
(merge-query {:query {:in ['?account-id]
|
||||
:where ['[?e :invoice/expense-accounts ?iea ?]
|
||||
'[?iea :invoice-expense-account/account ?account-id]]}
|
||||
:args [ (:account-id args)]})
|
||||
:args [(:account-id args)]})
|
||||
|
||||
(:amount-gte args)
|
||||
(:amount-gte args)
|
||||
(merge-query {:query {:in ['?amount-gte]
|
||||
:where ['[?e :invoice/total ?total-filter]
|
||||
'[(>= ?total-filter ?amount-gte)]]}
|
||||
:args [(:amount-gte args)]})
|
||||
|
||||
(:amount-lte args)
|
||||
(:amount-lte args)
|
||||
(merge-query {:query {:in ['?amount-lte]
|
||||
:where ['[?e :invoice/total ?total-filter]
|
||||
'[(<= ?total-filter ?amount-lte)]]}
|
||||
@@ -151,7 +141,7 @@
|
||||
(:unresolved args)
|
||||
(merge-query {:query {:in []
|
||||
:where ['(or-join [?e]
|
||||
(not [?e :invoice/expense-accounts ])
|
||||
(not [?e :invoice/expense-accounts])
|
||||
(and [?e :invoice/expense-accounts ?ea]
|
||||
(not [?ea :invoice-expense-account/account])))]}
|
||||
:args []})
|
||||
@@ -165,7 +155,7 @@
|
||||
(:sort args) (add-sorter-fields {"client" ['[?e :invoice/client ?c]
|
||||
'[?c :client/name ?sort-client]]
|
||||
"vendor" ['[?e :invoice/vendor ?v]
|
||||
'[?v :vendor/name ?sort-vendor]]
|
||||
'[?v :vendor/name ?sort-vendor]]
|
||||
"description-original" ['[?e :transaction/description-original ?sort-description-original]]
|
||||
"location" ['[?e :invoice/expense-accounts ?iea]
|
||||
'[?iea :invoice-expense-account/location ?sort-location]]
|
||||
@@ -176,16 +166,15 @@
|
||||
"outstanding-balance" ['[?e :invoice/outstanding-balance ?sort-outstanding-balance]]}
|
||||
args)
|
||||
true
|
||||
(merge-query {:query {:find ['?sort-default '?e ]}}) ))]
|
||||
(merge-query {:query {:find ['?sort-default '?e]}})))]
|
||||
(->> (observable-query query)
|
||||
(apply-sort-3 args)
|
||||
(apply-pagination args)))))
|
||||
|
||||
(apply-sort-3 args)
|
||||
(apply-pagination args)))))
|
||||
|
||||
(defn graphql-results [ids db _]
|
||||
(let [results (->> (pull-many db default-read ids)
|
||||
(group-by :db/id))
|
||||
|
||||
|
||||
invoices (->> ids
|
||||
(map results)
|
||||
(map first)
|
||||
@@ -193,40 +182,38 @@
|
||||
invoices))
|
||||
|
||||
(defn sum-outstanding [ids]
|
||||
|
||||
(->>
|
||||
(dc/q {:find ['?id '?o]
|
||||
:in ['$ '[?id ...]]
|
||||
:where ['[?id :invoice/outstanding-balance ?o]]}
|
||||
(dc/db conn)
|
||||
ids)
|
||||
|
||||
(->>
|
||||
(dc/q {:find ['?id '?o]
|
||||
:in ['$ '[?id ...]]
|
||||
:where ['[?id :invoice/outstanding-balance ?o]]}
|
||||
(dc/db conn)
|
||||
ids)
|
||||
(map last)
|
||||
(reduce
|
||||
+
|
||||
0.0)))
|
||||
|
||||
(defn sum-total-amount [ids]
|
||||
|
||||
(->>
|
||||
(dc/q {:find ['?id '?o]
|
||||
:in ['$ '[?id ...]]
|
||||
:where ['[?id :invoice/total ?o]]
|
||||
}
|
||||
(dc/db conn)
|
||||
ids)
|
||||
(map last)
|
||||
(reduce
|
||||
+
|
||||
0.0)))
|
||||
|
||||
(->>
|
||||
(dc/q {:find ['?id '?o]
|
||||
:in ['$ '[?id ...]]
|
||||
:where ['[?id :invoice/total ?o]]}
|
||||
(dc/db conn)
|
||||
ids)
|
||||
(map last)
|
||||
(reduce
|
||||
+
|
||||
0.0)))
|
||||
|
||||
(defn get-graphql [args]
|
||||
|
||||
|
||||
(let [db (dc/db conn)
|
||||
{ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)
|
||||
outstanding (sum-outstanding ids-to-retrieve)
|
||||
total-amount (sum-total-amount ids-to-retrieve)]
|
||||
|
||||
|
||||
[(->> (graphql-results ids-to-retrieve db args))
|
||||
matching-count
|
||||
outstanding
|
||||
@@ -239,56 +226,51 @@
|
||||
|
||||
(defn get-multi [ids]
|
||||
(map <-datomic
|
||||
(pull-many (dc/db conn) default-read ids )))
|
||||
|
||||
|
||||
(pull-many (dc/db conn) default-read ids)))
|
||||
|
||||
(defn find-conflicting [{:keys [:invoice/invoice-number :invoice/vendor :invoice/client :db/id]}]
|
||||
|
||||
|
||||
(->> (dc/q
|
||||
{:find [(list 'pull '?e default-read)]
|
||||
:in ['$ '?invoice-number '?vendor '?client '?invoice-id]
|
||||
:where '[[?e :invoice/invoice-number ?invoice-number]
|
||||
[?e :invoice/vendor ?vendor]
|
||||
[?e :invoice/client ?client]
|
||||
(not [?e :invoice/status :invoice-status/voided])
|
||||
[(not= ?e ?invoice-id)]]}
|
||||
(dc/db conn) invoice-number vendor client (or id 0))
|
||||
{:find [(list 'pull '?e default-read)]
|
||||
:in ['$ '?invoice-number '?vendor '?client '?invoice-id]
|
||||
:where '[[?e :invoice/invoice-number ?invoice-number]
|
||||
[?e :invoice/vendor ?vendor]
|
||||
[?e :invoice/client ?client]
|
||||
(not [?e :invoice/status :invoice-status/voided])
|
||||
[(not= ?e ?invoice-id)]]}
|
||||
(dc/db conn) invoice-number vendor client (or id 0))
|
||||
(map first)
|
||||
(map <-datomic)))
|
||||
|
||||
|
||||
(defn get-existing-set []
|
||||
(let [vendored-results (set (dc/q {:find ['?vendor '?client '?invoice-number]
|
||||
:in ['$]
|
||||
:where '[[?e :invoice/invoice-number ?invoice-number]
|
||||
[?e :invoice/vendor ?vendor]
|
||||
[?e :invoice/client ?client]
|
||||
(not [?e :invoice/status :invoice-status/voided])
|
||||
]}
|
||||
(not [?e :invoice/status :invoice-status/voided])]}
|
||||
(dc/db conn)))
|
||||
vendorless-results (->> (dc/q {:find ['?client '?invoice-number]
|
||||
:in ['$]
|
||||
:where '[[?e :invoice/invoice-number ?invoice-number]
|
||||
(not [?e :invoice/vendor])
|
||||
[?e :invoice/client ?client]
|
||||
(not [?e :invoice/status :invoice-status/voided])
|
||||
]}
|
||||
(not [?e :invoice/status :invoice-status/voided])]}
|
||||
(dc/db conn))
|
||||
(mapv (fn [[client invoice-number]]
|
||||
[nil client invoice-number]) )
|
||||
[nil client invoice-number]))
|
||||
set)]
|
||||
(into vendored-results vendorless-results)))
|
||||
|
||||
|
||||
(defn filter-ids [ids]
|
||||
(if ids
|
||||
(->>
|
||||
(dc/q {:find ['?e]
|
||||
:in ['$ '[?e ...]]
|
||||
:where ['[?e :invoice/date]]}
|
||||
(dc/db conn) ids)
|
||||
(map first)
|
||||
vec)
|
||||
(if ids
|
||||
(->>
|
||||
(dc/q {:find ['?e]
|
||||
:in ['$ '[?e ...]]
|
||||
:where ['[?e :invoice/date]]}
|
||||
(dc/db conn) ids)
|
||||
(map first)
|
||||
vec)
|
||||
[]))
|
||||
|
||||
(defn code-invoice
|
||||
@@ -317,7 +299,7 @@
|
||||
client-id))))
|
||||
[schedule-payment-dom] (map first (dc/q '[:find ?dom
|
||||
:in $ ?v ?c
|
||||
:where [?v :vendor/schedule-payment-dom ?sp ]
|
||||
:where [?v :vendor/schedule-payment-dom ?sp]
|
||||
[?sp :vendor-schedule-payment-dom/client ?c]
|
||||
[?sp :vendor-schedule-payment-dom/dom ?dom]]
|
||||
db
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
(some-> (:start (:date-range args)) coerce/to-date)
|
||||
(some-> (:end (:date-range args)) coerce/to-date)]]}
|
||||
|
||||
(:only-external args)
|
||||
(merge-query {:query {:where ['(not [?e :journal-entry/original-entity ])]}})
|
||||
(:only-external args)
|
||||
(merge-query {:query {:where ['(not [?e :journal-entry/original-entity])]}})
|
||||
|
||||
(seq (:external-id-like args))
|
||||
(merge-query {:query {:in ['?external-id-like]
|
||||
@@ -48,12 +48,11 @@
|
||||
:where ['[?e :journal-entry/source ?source]]}
|
||||
:args [(:source args)]})
|
||||
|
||||
(:vendor-id args)
|
||||
(:vendor-id args)
|
||||
(merge-query {:query {:in ['?vendor-id]
|
||||
:where ['[?e :journal-entry/vendor ?vendor-id]]}
|
||||
:args [(:vendor-id args)]})
|
||||
|
||||
|
||||
(or (seq (:numeric-code args))
|
||||
(:bank-account-id args)
|
||||
(not-empty (:location args)))
|
||||
@@ -70,36 +69,35 @@
|
||||
:args [(vec (for [{:keys [from to]} (:numeric-code args)]
|
||||
[(or from 0) (or to 99999)]))]})
|
||||
|
||||
|
||||
(:amount-gte args)
|
||||
(:amount-gte args)
|
||||
(merge-query {:query {:in ['?amount-gte]
|
||||
:where ['[?e :journal-entry/amount ?a]
|
||||
'[(>= ?a ?amount-gte)]]}
|
||||
:args [(:amount-gte args)]})
|
||||
|
||||
(:amount-lte args)
|
||||
(:amount-lte args)
|
||||
(merge-query {:query {:in ['?amount-lte]
|
||||
:where ['[?e :journal-entry/amount ?a]
|
||||
'[(<= ?a ?amount-lte)]]}
|
||||
:args [(:amount-lte args)]})
|
||||
|
||||
(:bank-account-id args)
|
||||
(:bank-account-id args)
|
||||
(merge-query {:query {:in ['?a]
|
||||
:where ['[?li :journal-entry-line/account ?a]]}
|
||||
:args [(:bank-account-id args)]})
|
||||
|
||||
(:account-id args)
|
||||
(:account-id args)
|
||||
(merge-query {:query {:in ['?a2]
|
||||
:where ['[?e :journal-entry/line-items ?li2]
|
||||
'[?li2 :journal-entry-line/account ?a2]]}
|
||||
:args [(:account-id args)]})
|
||||
|
||||
(not-empty (:location args))
|
||||
(not-empty (:location args))
|
||||
(merge-query {:query {:in ['?location]
|
||||
:where ['[?li :journal-entry-line/location ?location]]}
|
||||
:args [(:location args)]})
|
||||
|
||||
(not-empty (:locations args))
|
||||
(not-empty (:locations args))
|
||||
(merge-query {:query {:in ['[?location ...]]
|
||||
:where ['[?li :journal-entry-line/location ?location]]}
|
||||
:args [(:locations args)]})
|
||||
@@ -118,7 +116,7 @@
|
||||
(merge-query {:query {:find ['?sort-default '?e]}})))]
|
||||
(->> (observable-query query)
|
||||
(apply-sort-4 (assoc args :default-asc? true))
|
||||
(apply-pagination args))))
|
||||
(apply-pagination args))))
|
||||
|
||||
(defn graphql-results [ids db _]
|
||||
(let [results (->> (pull-many db '[* {:journal-entry/client [:client/name :client/code :db/id]
|
||||
@@ -134,15 +132,15 @@
|
||||
(update je :journal-entry/line-items
|
||||
(fn [jels]
|
||||
(map
|
||||
#(update % :journal-entry-line/account d-accounts/clientize (:db/id (:journal-entry/client je)))
|
||||
jels)))))
|
||||
#(update % :journal-entry-line/account d-accounts/clientize (:db/id (:journal-entry/client je)))
|
||||
jels)))))
|
||||
(filter (fn [je]
|
||||
(every?
|
||||
(fn [jel]
|
||||
(let [include-in-reports (-> jel :journal-entry-line/account :bank-account/include-in-reports)]
|
||||
(or (nil? include-in-reports)
|
||||
(true? include-in-reports))))
|
||||
(:journal-entry/line-items je))))
|
||||
(fn [jel]
|
||||
(let [include-in-reports (-> jel :journal-entry-line/account :bank-account/include-in-reports)]
|
||||
(or (nil? include-in-reports)
|
||||
(true? include-in-reports))))
|
||||
(:journal-entry/line-items je))))
|
||||
(group-by :db/id))]
|
||||
(->> ids
|
||||
(map results)
|
||||
@@ -156,7 +154,7 @@
|
||||
matching-count]))
|
||||
|
||||
(defn filter-ids [ids]
|
||||
(if ids
|
||||
(if ids
|
||||
(->> (dc/q {:find ['?e]
|
||||
:in ['$ '[?e ...]]
|
||||
:where ['[?e :journal-entry/date]]}
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
(update :sales-order/charges (fn [cs]
|
||||
(map (fn [c]
|
||||
(-> c
|
||||
(update :charge/processor :db/ident)
|
||||
(set/rename-keys {:expected-deposit/_charges :expected-deposit})
|
||||
(update :expected-deposit first)))
|
||||
(update :charge/processor :db/ident)
|
||||
(set/rename-keys {:expected-deposit/_charges :expected-deposit})
|
||||
(update :expected-deposit first)))
|
||||
cs)))))
|
||||
|
||||
(def default-read '[:db/id
|
||||
@@ -43,8 +43,7 @@
|
||||
:sales-order/source,
|
||||
:sales-order/reference-link,
|
||||
{:sales-order/client [:client/name :db/id :client/code]
|
||||
:sales-order/charges [
|
||||
:charge/type-name,
|
||||
:sales-order/charges [:charge/type-name,
|
||||
:charge/total,
|
||||
:charge/tax,
|
||||
:charge/tip,
|
||||
@@ -63,7 +62,6 @@
|
||||
(set/intersection #{(:client-id args)}
|
||||
visible-clients)
|
||||
|
||||
|
||||
(:client-code args)
|
||||
(set/intersection #{(pull-id db [:client/code (:client-code args)])}
|
||||
visible-clients)
|
||||
@@ -79,7 +77,7 @@
|
||||
:where '[[(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]]}
|
||||
:args [db [selected-clients
|
||||
(some-> (:start (:date-range args)) c/to-date)
|
||||
(some-> (:end (:date-range args)) c/to-date )]]}
|
||||
(some-> (:end (:date-range args)) c/to-date)]]}
|
||||
|
||||
(:sort args) (add-sorter-fields-2 {"client" ['[?e :sales-order/client ?c]
|
||||
'[?c :client/name ?sort-client]]
|
||||
@@ -108,13 +106,13 @@
|
||||
'[?chg :charge/type-name ?type-name]]}
|
||||
:args [(:type-name args)]})
|
||||
|
||||
(:total-gte args)
|
||||
(:total-gte args)
|
||||
(merge-query {:query {:in ['?total-gte]
|
||||
:where ['[?e :sales-order/total ?a]
|
||||
'[(>= ?a ?total-gte)]]}
|
||||
:args [(:total-gte args)]})
|
||||
|
||||
(:total-lte args)
|
||||
(:total-lte args)
|
||||
(merge-query {:query {:in ['?total-lte]
|
||||
:where ['[?e :sales-order/total ?a]
|
||||
'[(<= ?a ?total-lte)]]}
|
||||
@@ -136,7 +134,7 @@
|
||||
|
||||
(defn graphql-results [ids db _]
|
||||
(let [results (->> (pull-many db default-read ids)
|
||||
(group-by :db/id))
|
||||
(group-by :db/id))
|
||||
payments (->> ids
|
||||
(map results)
|
||||
(map first)
|
||||
@@ -146,14 +144,14 @@
|
||||
(defn summarize-orders [ids]
|
||||
|
||||
(let [[total tax] (->>
|
||||
(dc/q {:find ['(sum ?t) '(sum ?tax)]
|
||||
:with ['?id]
|
||||
:in ['$ '[?id ...]]
|
||||
:where ['[?id :sales-order/total ?t]
|
||||
'[?id :sales-order/tax ?tax]]}
|
||||
(dc/db conn)
|
||||
ids)
|
||||
first)]
|
||||
(dc/q {:find ['(sum ?t) '(sum ?tax)]
|
||||
:with ['?id]
|
||||
:in ['$ '[?id ...]]
|
||||
:where ['[?id :sales-order/total ?t]
|
||||
'[?id :sales-order/tax ?tax]]}
|
||||
(dc/db conn)
|
||||
ids)
|
||||
first)]
|
||||
{:total total
|
||||
:tax tax}))
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"note" ['[?e :transaction-rule/note ?sort-note]]
|
||||
"amount-lte" ['[?e :transaction-rule/amount-lte ?sort-amount-lte]]
|
||||
"amount-gte" ['[?e :transaction-rule/amount-gte ?sort-amount-gte]]}
|
||||
args)
|
||||
args)
|
||||
|
||||
(seq (:clients args))
|
||||
(merge-query {:query {:in ['[?xx ...]]
|
||||
@@ -78,7 +78,6 @@
|
||||
(merge-query {:query {:find ['?e]
|
||||
:where ['[?e :transaction-rule/transaction-approval-status]]}}))]
|
||||
|
||||
|
||||
(cond->> (query2 query)
|
||||
true (apply-sort-3 args)
|
||||
true (apply-pagination args))))
|
||||
@@ -99,13 +98,13 @@
|
||||
matching-count]))
|
||||
|
||||
(defn get-by-id [id]
|
||||
(->>
|
||||
(->>
|
||||
(dc/pull (dc/db conn) default-read id)
|
||||
(<-datomic)))
|
||||
|
||||
(defn get-all []
|
||||
(mapv first
|
||||
(dc/q {:find [(list 'pull '?e default-read )]
|
||||
(dc/q {:find [(list 'pull '?e default-read)]
|
||||
:in ['$]
|
||||
:where ['[?e :transaction-rule/transaction-approval-status]]}
|
||||
(dc/db conn))))
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
(map first)
|
||||
set)))
|
||||
|
||||
|
||||
(defn raw-graphql-ids
|
||||
([args] (raw-graphql-ids (dc/db conn) args))
|
||||
([db args]
|
||||
@@ -87,7 +86,6 @@
|
||||
:where ['[?e :transaction/vendor ?vendor-id]]}
|
||||
:args [(:vendor-id args)]})
|
||||
|
||||
|
||||
(:amount-gte args)
|
||||
(merge-query {:query {:in ['?amount-gte]
|
||||
:where ['[?e :transaction/amount ?a]
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
[datomic.api :as dc]
|
||||
[datomic.api :as d]))
|
||||
|
||||
(defn find-or-insert! [{:keys [:user/provider :user/provider-id ] :as new-user}]
|
||||
(defn find-or-insert! [{:keys [:user/provider :user/provider-id] :as new-user}]
|
||||
(let [is-first-user? (not (seq (dc/q [:find '?e
|
||||
:in '$
|
||||
:where '[?e :user/provider]]
|
||||
(dc/db conn))))
|
||||
user-id (ffirst (dc/q '[:find ?e
|
||||
:in $ ?provider ?provider-id
|
||||
:where [?e :user/provider ?provider]
|
||||
[?e :user/provider-id ?provider-id]]
|
||||
:in $ ?provider ?provider-id
|
||||
:where [?e :user/provider ?provider]
|
||||
[?e :user/provider-id ?provider-id]]
|
||||
(dc/db conn) provider provider-id))
|
||||
result @(dc/transact conn [[:upsert-entity (cond-> (assoc new-user :db/id (or user-id "user")
|
||||
:user/last-login (java.util.Date.))
|
||||
|
||||
@@ -18,29 +18,29 @@
|
||||
(:vendor/legal-entity-tin-type a) (update :vendor/legal-entity-tin-type :db/ident)
|
||||
(:vendor/legal-entity-1099-type a) (update :vendor/legal-entity-1099-type :db/ident)
|
||||
true (assoc :usage (:vendor-usage/_vendor a))
|
||||
true (dissoc :vendor-usage/_vendor )))
|
||||
true (dissoc :vendor-usage/_vendor)))
|
||||
|
||||
(defn cleanse [id vendor]
|
||||
(let [clients (if-let [clients (limited-clients id)]
|
||||
(set (map :db/id clients))
|
||||
nil)]
|
||||
(if clients
|
||||
(-> vendor
|
||||
(-> vendor
|
||||
(update :vendor/account-overrides (fn [aos]
|
||||
(->> aos
|
||||
(filter #(clients (:db/id (:vendor-account-override/client %))))
|
||||
(map #(update % :vendor-account-override/account d-accounts/clientize (:db/id (:vendor-account-override/client %)))))))
|
||||
(update :vendor/terms-overrides (fn [to] (filter #(clients (:db/id (:vendor-terms-override/client %))) to)))
|
||||
(update :vendor/schedule-payment-dom (fn [to] (filter #(clients (:db/id (:vendor-schedule-payment-dom/client %))) to))))
|
||||
(-> vendor
|
||||
(-> vendor
|
||||
(update :vendor/account-overrides (fn [aos]
|
||||
(->> aos
|
||||
(map #(update % :vendor-account-override/account d-accounts/clientize (:db/id (:vendor-account-override/client %)))))))))))
|
||||
|
||||
(def default-read
|
||||
'[* {:vendor/account-overrides [* {:vendor-account-override/client [:client/name :db/id]
|
||||
:vendor-account-override/account [:account/name :account/numeric-code :db/id
|
||||
{:account/client-overrides [:account-client-override/client :account-client-override/name]}]}]
|
||||
:vendor-account-override/account [:account/name :account/numeric-code :db/id
|
||||
{:account/client-overrides [:account-client-override/client :account-client-override/name]}]}]
|
||||
:vendor/terms-overrides [* {:vendor-terms-override/client [:client/name :client/code :db/id]}]
|
||||
:vendor/schedule-payment-dom [* {:vendor-schedule-payment-dom/client [:client/name :client/code :db/id]}]
|
||||
:vendor/automatically-paid-when-due [:db/id :client/name]
|
||||
@@ -50,14 +50,13 @@
|
||||
:vendor/plaid-merchant [:db/id :plaid-merchant/name]
|
||||
:vendor-usage/_vendor [:vendor-usage/client :vendor-usage/count]}])
|
||||
|
||||
|
||||
(defn raw-graphql-ids [db args]
|
||||
(let [query (cond-> {:query {:find []
|
||||
:in ['$]
|
||||
:where []}
|
||||
:args [db]}
|
||||
(:sort args) (add-sorter-fields {"name" ['[?e :vendor/name ?sort-name]]}
|
||||
args)
|
||||
args)
|
||||
|
||||
(not (str/blank? (:name-like args)))
|
||||
(merge-query {:query {:in ['?name-like]
|
||||
@@ -70,25 +69,22 @@
|
||||
(merge-query {:query {:find ['?e]
|
||||
:where ['[?e :vendor/name]]}}))]
|
||||
|
||||
|
||||
(cond->> (query2 query)
|
||||
true (apply-sort-3 args)
|
||||
true (apply-pagination args))))
|
||||
|
||||
(defn trim-usage [v limited-clients]
|
||||
(->> (if limited-clients
|
||||
(update v :usage (fn [usages]
|
||||
(->> usages
|
||||
(filter (comp (set (map :db/id limited-clients)) :db/id :vendor-usage/client))
|
||||
(map (fn [u] {:client-id (:db/id (:vendor-usage/client u))
|
||||
:count (:vendor-usage/count u)})))))
|
||||
(update v :usage (fn [usages]
|
||||
(->> usages
|
||||
(filter (comp (set (map :db/id limited-clients)) :db/id :vendor-usage/client))
|
||||
(map (fn [u] {:client-id (:db/id (:vendor-usage/client u))
|
||||
:count (:vendor-usage/count u)})))))
|
||||
|
||||
(update v :usage (fn [usages]
|
||||
(->> usages
|
||||
(map (fn [u] {:client-id (:db/id (:vendor-usage/client u))
|
||||
:count (:vendor-usage/count u)}))))))
|
||||
|
||||
))
|
||||
(update v :usage (fn [usages]
|
||||
(->> usages
|
||||
(map (fn [u] {:client-id (:db/id (:vendor-usage/client u))
|
||||
:count (:vendor-usage/count u)}))))))))
|
||||
|
||||
(defn graphql-results [ids db args]
|
||||
(let [results (->> (pull-many db default-read ids)
|
||||
@@ -104,9 +100,7 @@
|
||||
(let [db (dc/db conn)
|
||||
{ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)]
|
||||
[(->> (graphql-results ids-to-retrieve db args))
|
||||
matching-count])
|
||||
|
||||
)
|
||||
matching-count]))
|
||||
|
||||
(defn get-graphql-by-id [args id]
|
||||
(->> (dc/q {:find [(list 'pull '?e default-read)]
|
||||
@@ -120,29 +114,28 @@
|
||||
first))
|
||||
|
||||
(defn get-by-id [id]
|
||||
|
||||
|
||||
(->> (dc/q '[:find (pull ?e [*
|
||||
{:vendor/default-account [:account/name :db/id :account/location]
|
||||
:vendor/legal-entity-tin-type [:db/ident :db/id]
|
||||
:vendor/legal-entity-1099-type [:db/ident :db/id]
|
||||
:vendor/plaid-merchant [:db/id :plaid-merchant/name]
|
||||
:vendor/account-overrides [* {:vendor-account-override/client [:client/name :db/id]
|
||||
:vendor-account-override/account [:account/name :account/numeric-code :db/id]}]
|
||||
:vendor/terms-overrides [* {:vendor-terms-override/client [:client/name :db/id]}]
|
||||
:vendor/schedule-payment-dom [* {:vendor-schedule-payment-dom/client [:client/name :db/id]}]
|
||||
:vendor/automatically-paid-when-due [:db/id :client/name]}])
|
||||
:in $ ?e
|
||||
:where [?e]]
|
||||
(dc/db conn)
|
||||
id)
|
||||
{:vendor/default-account [:account/name :db/id :account/location]
|
||||
:vendor/legal-entity-tin-type [:db/ident :db/id]
|
||||
:vendor/legal-entity-1099-type [:db/ident :db/id]
|
||||
:vendor/plaid-merchant [:db/id :plaid-merchant/name]
|
||||
:vendor/account-overrides [* {:vendor-account-override/client [:client/name :db/id]
|
||||
:vendor-account-override/account [:account/name :account/numeric-code :db/id]}]
|
||||
:vendor/terms-overrides [* {:vendor-terms-override/client [:client/name :db/id]}]
|
||||
:vendor/schedule-payment-dom [* {:vendor-schedule-payment-dom/client [:client/name :db/id]}]
|
||||
:vendor/automatically-paid-when-due [:db/id :client/name]}])
|
||||
:in $ ?e
|
||||
:where [?e]]
|
||||
(dc/db conn)
|
||||
id)
|
||||
(map first)
|
||||
(map <-datomic)
|
||||
(first)))
|
||||
|
||||
|
||||
(defn terms-for-client-id [vendor client-id]
|
||||
(or
|
||||
(->>
|
||||
(->>
|
||||
(filter
|
||||
(fn [to]
|
||||
(= (:db/id (:vendor-terms-override/client to))
|
||||
@@ -153,8 +146,8 @@
|
||||
(:vendor/terms vendor)))
|
||||
|
||||
(defn account-for-client-id [vendor client-id]
|
||||
(or
|
||||
(->>
|
||||
(or
|
||||
(->>
|
||||
(filter
|
||||
(fn [to]
|
||||
(= (:db/id (:vendor-account-override/client to))
|
||||
@@ -165,7 +158,7 @@
|
||||
(:vendor/default-account vendor)))
|
||||
|
||||
(defn automatically-paid-for-client-id? [vendor client-id]
|
||||
(->>
|
||||
(->>
|
||||
(:vendor/automatically-paid-when-due vendor)
|
||||
(filter
|
||||
(fn [client]
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
(defn get-merchants [_]
|
||||
;; TODO admin?
|
||||
(->>
|
||||
(dc/q {:find ['(pull ?e [:yodlee-merchant/name :yodlee-merchant/yodlee-id :db/id])]
|
||||
:in ['$]
|
||||
:where [['?e :yodlee-merchant/name]]}
|
||||
(dc/db conn))
|
||||
(mapv first)))
|
||||
(dc/q {:find ['(pull ?e [:yodlee-merchant/name :yodlee-merchant/yodlee-id :db/id])]
|
||||
:in ['$]
|
||||
:where [['?e :yodlee-merchant/name]]}
|
||||
(dc/db conn))
|
||||
(mapv first)))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns auto-ap.ezcater.core
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn random-tempid]]
|
||||
[auto-ap.datomic :refer [conn random-tempid]]
|
||||
[datomic.api :as dc]
|
||||
[clj-http.client :as client]
|
||||
[venia.core :as v]
|
||||
@@ -20,42 +20,41 @@
|
||||
:body (json/write-str {"query" (v/graphql-query q)})
|
||||
:as :json})
|
||||
:body
|
||||
:data
|
||||
))
|
||||
:data))
|
||||
|
||||
(defn get-caterers [integration]
|
||||
(:caterers (query integration {:venia/queries [{:query/data
|
||||
[:caterers [:name :uuid [:address [:name :street]]]]}]} )))
|
||||
[:caterers [:name :uuid [:address [:name :street]]]]}]})))
|
||||
|
||||
(defn get-subscriptions [integration]
|
||||
(->> (query integration {:venia/queries [{:query/data
|
||||
[:subscribers [:id [:subscriptions [:parentId :parentEntity :eventEntity :eventKey]] ]]}]} )
|
||||
[:subscribers [:id [:subscriptions [:parentId :parentEntity :eventEntity :eventKey]]]]}]})
|
||||
:subscribers
|
||||
first
|
||||
:subscriptions))
|
||||
|
||||
(defn get-integrations []
|
||||
(map first (dc/q '[:find (pull ?i [:ezcater-integration/api-key
|
||||
:ezcater-integration/subscriber-uuid
|
||||
:db/id
|
||||
:ezcater-integration/integration-status [:db/id]])
|
||||
:in $
|
||||
:where [?i :ezcater-integration/api-key]]
|
||||
(dc/db conn))))
|
||||
:ezcater-integration/subscriber-uuid
|
||||
:db/id
|
||||
:ezcater-integration/integration-status [:db/id]])
|
||||
:in $
|
||||
:where [?i :ezcater-integration/api-key]]
|
||||
(dc/db conn))))
|
||||
|
||||
(defn mark-integration-status [integration integration-status]
|
||||
@(dc/transact conn
|
||||
[{:db/id (:db/id integration)
|
||||
:ezcater-integration/integration-status (assoc integration-status
|
||||
:db/id (or (-> integration :ezcater-integration/integration-status :db/id)
|
||||
(random-tempid)))}]))
|
||||
[{:db/id (:db/id integration)
|
||||
:ezcater-integration/integration-status (assoc integration-status
|
||||
:db/id (or (-> integration :ezcater-integration/integration-status :db/id)
|
||||
(random-tempid)))}]))
|
||||
|
||||
(defn upsert-caterers
|
||||
([integration]
|
||||
@(dc/transact
|
||||
conn
|
||||
(for [caterer (get-caterers integration)]
|
||||
{:db/id (:db/id integration)
|
||||
{:db/id (:db/id integration)
|
||||
:ezcater-integration/caterers [{:ezcater-caterer/name (str (:name caterer) " (" (:street (:address caterer)) ")")
|
||||
:ezcater-caterer/search-terms (str (:name caterer) " " (:street (:address caterer)))
|
||||
:ezcater-caterer/uuid (:uuid caterer)}]}))))
|
||||
@@ -64,14 +63,14 @@
|
||||
([integration]
|
||||
(let [extant (get-subscriptions integration)
|
||||
to-ensure (set (map first (dc/q '[:find ?cu
|
||||
:in $
|
||||
:where [_ :client/ezcater-locations ?el]
|
||||
[?el :ezcater-location/caterer ?c]
|
||||
[?c :ezcater-caterer/uuid ?cu]]
|
||||
(dc/db conn))))
|
||||
:in $
|
||||
:where [_ :client/ezcater-locations ?el]
|
||||
[?el :ezcater-location/caterer ?c]
|
||||
[?c :ezcater-caterer/uuid ?cu]]
|
||||
(dc/db conn))))
|
||||
to-create (set/difference
|
||||
to-ensure
|
||||
(set (map :parentId extant)))]
|
||||
to-ensure
|
||||
(set (map :parentId extant)))]
|
||||
(doseq [parentId to-create]
|
||||
(query integration
|
||||
{:venia/operation {:operation/type :mutation
|
||||
@@ -94,7 +93,6 @@
|
||||
:eventKey 'cancelled}}
|
||||
[[:subscription [:parentId :parentEntity :eventEntity :eventKey]]]]]})))))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn upsert-ezcater
|
||||
([] (upsert-ezcater (get-integrations)))
|
||||
@@ -115,12 +113,11 @@
|
||||
|
||||
(defn get-caterer [caterer-uuid]
|
||||
(dc/pull (dc/db conn)
|
||||
'[:ezcater-caterer/name
|
||||
{:ezcater-integration/_caterers [:ezcater-integration/api-key]}
|
||||
{:ezcater-location/_caterer [:ezcater-location/location
|
||||
{:client/_ezcater-locations [:client/code]}]}]
|
||||
[:ezcater-caterer/uuid caterer-uuid]))
|
||||
|
||||
'[:ezcater-caterer/name
|
||||
{:ezcater-integration/_caterers [:ezcater-integration/api-key]}
|
||||
{:ezcater-location/_caterer [:ezcater-location/location
|
||||
{:client/_ezcater-locations [:client/code]}]}]
|
||||
[:ezcater-caterer/uuid caterer-uuid]))
|
||||
|
||||
(defn round-carry-cents [f]
|
||||
(with-precision 2 (double (.setScale (bigdec f) 2 java.math.RoundingMode/HALF_UP))))
|
||||
@@ -135,126 +132,118 @@
|
||||
0.15M
|
||||
:else
|
||||
0.07M)]
|
||||
(round-carry-cents
|
||||
(* commision%
|
||||
0.01M
|
||||
(+
|
||||
(-> order :totals :subTotal :subunits )
|
||||
(reduce +
|
||||
0
|
||||
(map (comp :subunits :cost) (:feesAndDiscounts (:catererCart order)))))))))
|
||||
|
||||
(defn ccp-fee [order]
|
||||
(round-carry-cents
|
||||
(* 0.000299M
|
||||
(+
|
||||
(-> order :totals :subTotal :subunits )
|
||||
(-> order :totals :salesTax :subunits )
|
||||
(round-carry-cents
|
||||
(* commision%
|
||||
0.01M
|
||||
(+
|
||||
(-> order :totals :subTotal :subunits)
|
||||
(reduce +
|
||||
0
|
||||
(map (comp :subunits :cost) (:feesAndDiscounts (:catererCart order))))))))
|
||||
(map (comp :subunits :cost) (:feesAndDiscounts (:catererCart order)))))))))
|
||||
|
||||
(defn ccp-fee [order]
|
||||
(round-carry-cents
|
||||
(* 0.000299M
|
||||
(+
|
||||
(-> order :totals :subTotal :subunits)
|
||||
(-> order :totals :salesTax :subunits)
|
||||
(reduce +
|
||||
0
|
||||
(map (comp :subunits :cost) (:feesAndDiscounts (:catererCart order))))))))
|
||||
|
||||
(defn order->sales-order [{{:keys [timestamp]} :event {:keys [orderItems]} :catererCart :keys [client-code client-location uuid] :as order}]
|
||||
(let [adjustment (round-carry-cents (- (+ (-> order :totals :subTotal :subunits (* 0.01))
|
||||
(-> order :totals :salesTax :subunits (* 0.01)))
|
||||
(-> order :catererCart :totals :catererTotalDue )
|
||||
(-> order :catererCart :totals :catererTotalDue)
|
||||
(commision order)
|
||||
(ccp-fee order)))
|
||||
service-charge (+ (commision order) (ccp-fee order))
|
||||
tax (-> order :totals :salesTax :subunits (* 0.01))
|
||||
tip (-> order :totals :tip :subunits (* 0.01))]
|
||||
#:sales-order
|
||||
{:date (atime/localize (coerce/to-date-time timestamp))
|
||||
:external-id (str "ezcater/order/" client-code "-" client-location "-" uuid)
|
||||
:client [:client/code client-code]
|
||||
:location client-location
|
||||
:reference-link (str (url/url "https://ezmanage.ezcater.com/orders/" uuid ))
|
||||
:line-items [#:order-line-item
|
||||
{:external-id (str "ezcater/order/" client-code "-" client-location "-" uuid "-" 0)
|
||||
:item-name "EZCater Catering"
|
||||
:category "EZCater Catering"
|
||||
:discount adjustment
|
||||
:tax tax
|
||||
:total (+ (-> order :totals :subTotal :subunits (* 0.01))
|
||||
tax
|
||||
tip)}]
|
||||
:charges [#:charge
|
||||
{:type-name "CARD"
|
||||
:date (atime/localize (coerce/to-date-time timestamp))
|
||||
:client [:client/code client-code]
|
||||
:location client-location
|
||||
:external-id (str "ezcater/charge/" uuid)
|
||||
:processor :ccp-processor/ezcater
|
||||
:total (+ (-> order :totals :subTotal :subunits (* 0.01))
|
||||
tax
|
||||
tip)
|
||||
:tip tip}]
|
||||
|
||||
:total (+ (-> order :totals :subTotal :subunits (* 0.01))
|
||||
tax
|
||||
tip)
|
||||
:discount adjustment
|
||||
:service-charge service-charge
|
||||
:tax tax
|
||||
:tip tip
|
||||
:returns 0.0
|
||||
:vendor :vendor/ccp-ezcater}))
|
||||
{:date (atime/localize (coerce/to-date-time timestamp))
|
||||
:external-id (str "ezcater/order/" client-code "-" client-location "-" uuid)
|
||||
:client [:client/code client-code]
|
||||
:location client-location
|
||||
:reference-link (str (url/url "https://ezmanage.ezcater.com/orders/" uuid))
|
||||
:line-items [#:order-line-item
|
||||
{:external-id (str "ezcater/order/" client-code "-" client-location "-" uuid "-" 0)
|
||||
:item-name "EZCater Catering"
|
||||
:category "EZCater Catering"
|
||||
:discount adjustment
|
||||
:tax tax
|
||||
:total (+ (-> order :totals :subTotal :subunits (* 0.01))
|
||||
tax
|
||||
tip)}]
|
||||
:charges [#:charge
|
||||
{:type-name "CARD"
|
||||
:date (atime/localize (coerce/to-date-time timestamp))
|
||||
:client [:client/code client-code]
|
||||
:location client-location
|
||||
:external-id (str "ezcater/charge/" uuid)
|
||||
:processor :ccp-processor/ezcater
|
||||
:total (+ (-> order :totals :subTotal :subunits (* 0.01))
|
||||
tax
|
||||
tip)
|
||||
:tip tip}]
|
||||
|
||||
:total (+ (-> order :totals :subTotal :subunits (* 0.01))
|
||||
tax
|
||||
tip)
|
||||
:discount adjustment
|
||||
:service-charge service-charge
|
||||
:tax tax
|
||||
:tip tip
|
||||
:returns 0.0
|
||||
:vendor :vendor/ccp-ezcater}))
|
||||
|
||||
(defn get-by-id [integration id]
|
||||
(query
|
||||
integration
|
||||
{:venia/queries [[:order {:id id}
|
||||
[:uuid
|
||||
:orderNumber
|
||||
:orderSourceType
|
||||
[:caterer
|
||||
[:name
|
||||
:uuid
|
||||
[:address [:street]]]]
|
||||
[:event
|
||||
[:timestamp
|
||||
:catererHandoffFoodTime
|
||||
:orderType]]
|
||||
[:catererCart [[:orderItems
|
||||
[:name
|
||||
:quantity
|
||||
:posItemId
|
||||
[:totalInSubunits
|
||||
[:currency
|
||||
:subunits]]]]
|
||||
[:totals
|
||||
[:catererTotalDue]]
|
||||
[:feesAndDiscounts
|
||||
{:type 'DELIVERY_FEE}
|
||||
[[:cost
|
||||
[:currency
|
||||
:subunits]]]]]]
|
||||
[:totals [[:customerTotalDue
|
||||
[
|
||||
:currency
|
||||
:subunits
|
||||
]]
|
||||
[:pointOfSaleIntegrationFee
|
||||
[
|
||||
:currency
|
||||
:subunits
|
||||
]]
|
||||
[:tip
|
||||
[:currency
|
||||
:subunits]]
|
||||
[:salesTax
|
||||
[
|
||||
:currency
|
||||
:subunits
|
||||
]]
|
||||
[:salesTaxRemittance
|
||||
[:currency
|
||||
:subunits
|
||||
]]
|
||||
[:subTotal
|
||||
[:currency
|
||||
:subunits]]]]]]]}))
|
||||
(query
|
||||
integration
|
||||
{:venia/queries [[:order {:id id}
|
||||
[:uuid
|
||||
:orderNumber
|
||||
:orderSourceType
|
||||
[:caterer
|
||||
[:name
|
||||
:uuid
|
||||
[:address [:street]]]]
|
||||
[:event
|
||||
[:timestamp
|
||||
:catererHandoffFoodTime
|
||||
:orderType]]
|
||||
[:catererCart [[:orderItems
|
||||
[:name
|
||||
:quantity
|
||||
:posItemId
|
||||
[:totalInSubunits
|
||||
[:currency
|
||||
:subunits]]]]
|
||||
[:totals
|
||||
[:catererTotalDue]]
|
||||
[:feesAndDiscounts
|
||||
{:type 'DELIVERY_FEE}
|
||||
[[:cost
|
||||
[:currency
|
||||
:subunits]]]]]]
|
||||
[:totals [[:customerTotalDue
|
||||
[:currency
|
||||
:subunits]]
|
||||
[:pointOfSaleIntegrationFee
|
||||
[:currency
|
||||
:subunits]]
|
||||
[:tip
|
||||
[:currency
|
||||
:subunits]]
|
||||
[:salesTax
|
||||
[:currency
|
||||
:subunits]]
|
||||
[:salesTaxRemittance
|
||||
[:currency
|
||||
:subunits]]
|
||||
[:subTotal
|
||||
[:currency
|
||||
:subunits]]]]]]]}))
|
||||
|
||||
(defn lookup-order [json]
|
||||
(let [caterer (get-caterer (get json "parent_id"))
|
||||
@@ -262,25 +251,25 @@
|
||||
client (-> caterer :ezcater-location/_caterer first :client/_ezcater-locations :client/code)
|
||||
location (-> caterer :ezcater-location/_caterer first :ezcater-location/location)]
|
||||
(if (and client location)
|
||||
(doto
|
||||
(-> (get-by-id integration (get json "entity_id"))
|
||||
(:order)
|
||||
(assoc :client-code client
|
||||
:client-location location))
|
||||
(doto
|
||||
(-> (get-by-id integration (get json "entity_id"))
|
||||
(:order)
|
||||
(assoc :client-code client
|
||||
:client-location location))
|
||||
(#(alog/info ::order-details :detail %)))
|
||||
(alog/warn ::caterer-no-longer-has-location :json json))))
|
||||
|
||||
(defn import-order [json]
|
||||
;; {"id" "bf3dcf5c-a68f-42d9-9084-049133e03d3d", "parent_type" "Caterer", "parent_id" "91541331-d7ae-4634-9e8b-ccbbcfb2ce70", "entity_type" "Order", "entity_id" "9ab05fee-a9c5-483b-a7f2-14debde4b7a8", "key" "accepted", "occurred_at" "2022-07-21T19:21:07.549Z"}
|
||||
(alog/info
|
||||
::try-import-order
|
||||
:json json)
|
||||
::try-import-order
|
||||
:json json)
|
||||
@(dc/transact conn (filter identity
|
||||
[(some-> json
|
||||
(lookup-order)
|
||||
(order->sales-order)
|
||||
(update :sales-order/date coerce/to-date)
|
||||
(update-in [:sales-order/charges 0 :charge/date] coerce/to-date))])))
|
||||
[(some-> json
|
||||
(lookup-order)
|
||||
(order->sales-order)
|
||||
(update :sales-order/date coerce/to-date)
|
||||
(update-in [:sales-order/charges 0 :charge/date] coerce/to-date))])))
|
||||
|
||||
(defn upsert-recent []
|
||||
(upsert-ezcater)
|
||||
@@ -289,17 +278,17 @@
|
||||
(filter #(= 7 (time/day-of-week %)))))
|
||||
(time/days 1)))
|
||||
orders-to-update (doall (for [[order uuid] (dc/q '[:find ?eid ?uuid
|
||||
:in $ ?start
|
||||
:where [?e :sales-order/vendor :vendor/ccp-ezcater]
|
||||
[?e :sales-order/date ?d]
|
||||
[(>= ?d ?start)]
|
||||
[?e :sales-order/external-id ?eid]
|
||||
[?e :sales-order/client ?c]
|
||||
[?c :client/ezcater-locations ?l]
|
||||
[?l :ezcater-location/caterer ?c2]
|
||||
[?c2 :ezcater-caterer/uuid ?uuid]]
|
||||
(dc/db conn)
|
||||
last-sunday)
|
||||
:in $ ?start
|
||||
:where [?e :sales-order/vendor :vendor/ccp-ezcater]
|
||||
[?e :sales-order/date ?d]
|
||||
[(>= ?d ?start)]
|
||||
[?e :sales-order/external-id ?eid]
|
||||
[?e :sales-order/client ?c]
|
||||
[?c :client/ezcater-locations ?l]
|
||||
[?l :ezcater-location/caterer ?c2]
|
||||
[?c2 :ezcater-caterer/uuid ?uuid]]
|
||||
(dc/db conn)
|
||||
last-sunday)
|
||||
:let [_ (alog/info ::considering
|
||||
:order order)
|
||||
id (last (str/split order #"/"))
|
||||
@@ -313,29 +302,29 @@
|
||||
"occurred_at" "2022-07-21T19:21:07.549Z"}
|
||||
ezcater-order (lookup-order lookup-map)
|
||||
extant-order (dc/pull (dc/db conn) '[:sales-order/total
|
||||
:sales-order/tax
|
||||
:sales-order/tip
|
||||
:sales-order/discount
|
||||
:sales-order/external-id
|
||||
{:sales-order/charges [:charge/tax
|
||||
:charge/tip
|
||||
:charge/total
|
||||
:charge/external-id]
|
||||
:sales-order/line-items [:order-line-item/external-id
|
||||
:order-line-item/total
|
||||
:order-line-item/tax
|
||||
:order-line-item/discount]}]
|
||||
[:sales-order/external-id order])
|
||||
:sales-order/tax
|
||||
:sales-order/tip
|
||||
:sales-order/discount
|
||||
:sales-order/external-id
|
||||
{:sales-order/charges [:charge/tax
|
||||
:charge/tip
|
||||
:charge/total
|
||||
:charge/external-id]
|
||||
:sales-order/line-items [:order-line-item/external-id
|
||||
:order-line-item/total
|
||||
:order-line-item/tax
|
||||
:order-line-item/discount]}]
|
||||
[:sales-order/external-id order])
|
||||
|
||||
updated-order (-> (order->sales-order ezcater-order)
|
||||
(select-keys
|
||||
#{:sales-order/total
|
||||
:sales-order/tax
|
||||
:sales-order/tip
|
||||
:sales-order/discount
|
||||
:sales-order/charges
|
||||
:sales-order/external-id
|
||||
:sales-order/line-items})
|
||||
#{:sales-order/total
|
||||
:sales-order/tax
|
||||
:sales-order/tip
|
||||
:sales-order/discount
|
||||
:sales-order/charges
|
||||
:sales-order/external-id
|
||||
:sales-order/line-items})
|
||||
(update :sales-order/line-items
|
||||
(fn [c]
|
||||
(map #(select-keys % #{:order-line-item/external-id
|
||||
|
||||
@@ -34,15 +34,14 @@
|
||||
(clojure.lang IPersistentMap)))
|
||||
|
||||
(def integreat-schema
|
||||
{
|
||||
:scalars {:id {:parse #(cond (number? %)
|
||||
{:scalars {:id {:parse #(cond (number? %)
|
||||
%
|
||||
|
||||
%
|
||||
(Long/parseLong %))
|
||||
|
||||
|
||||
:serialize #(.toString %)}
|
||||
:ident {:parse (fn [x] {:db/ident x})
|
||||
:ident {:parse (fn [x] {:db/ident x})
|
||||
:serialize #(or (:ident %) (:db/ident %) %)}
|
||||
:iso_date {:parse #(time/parse % time/iso-date)
|
||||
:serialize #(time/unparse % time/iso-date)}
|
||||
@@ -65,29 +64,28 @@
|
||||
:else
|
||||
%)
|
||||
:serialize #(cond (double? %)
|
||||
(str %)
|
||||
|
||||
(int? %)
|
||||
(str %)
|
||||
|
||||
:else
|
||||
%)}
|
||||
|
||||
:percentage {:parse #(cond (and (string? %)
|
||||
(not (str/blank? %)))
|
||||
(Double/parseDouble %)
|
||||
(str %)
|
||||
|
||||
(int? %)
|
||||
(double %)
|
||||
(str %)
|
||||
|
||||
:else
|
||||
%)
|
||||
%)}
|
||||
|
||||
:percentage {:parse #(cond (and (string? %)
|
||||
(not (str/blank? %)))
|
||||
(Double/parseDouble %)
|
||||
|
||||
(int? %)
|
||||
(double %)
|
||||
|
||||
:else
|
||||
%)
|
||||
:serialize #(if (double? %)
|
||||
(str %)
|
||||
%)}}
|
||||
:objects
|
||||
{
|
||||
:message
|
||||
{:message
|
||||
{:fields {:message {:type 'String}}}
|
||||
|
||||
:search_result
|
||||
@@ -128,8 +126,7 @@
|
||||
:email {:type 'String}
|
||||
:phone {:type 'String}}}
|
||||
|
||||
|
||||
:address
|
||||
:address
|
||||
{:fields {:id {:type :id}
|
||||
:street1 {:type 'String}
|
||||
:street2 {:type 'String}
|
||||
@@ -184,7 +181,6 @@
|
||||
:legal_entity_tin_type {:type :tin_type}
|
||||
:legal_entity_1099_type {:type :type_1099}}}
|
||||
|
||||
|
||||
:reminder
|
||||
{:fields {:id {:type 'Int}
|
||||
:email {:type 'String}
|
||||
@@ -193,13 +189,13 @@
|
||||
:scheduled {:type 'String}
|
||||
:sent {:type 'String}
|
||||
:vendor {:type :vendor}}}
|
||||
|
||||
|
||||
:yodlee_merchant {:fields {:id {:type :id}
|
||||
:yodlee_id {:type 'String}
|
||||
:name {:type 'String}}}
|
||||
|
||||
:plaid_merchant {:fields {:id {:type :id}
|
||||
:name {:type 'String}}}
|
||||
:name {:type 'String}}}
|
||||
|
||||
:intuit_bank_account {:fields {:id {:type :id}
|
||||
:external_id {:type 'String}
|
||||
@@ -222,8 +218,6 @@
|
||||
:accounts {:type '(list :percentage_account)}
|
||||
:transaction_approval_status {:type :transaction_approval_status}}}
|
||||
|
||||
|
||||
|
||||
:user
|
||||
{:fields {:id {:type :id}
|
||||
:name {:type 'String}
|
||||
@@ -264,18 +258,12 @@
|
||||
:start {:type 'Int}
|
||||
:end {:type 'Int}}}
|
||||
|
||||
|
||||
|
||||
:transaction_rule_page {:fields {:transaction_rules {:type '(list :transaction_rule)}
|
||||
:transaction_rule_page {:fields {:transaction_rules {:type '(list :transaction_rule)}
|
||||
:count {:type 'Int}
|
||||
:total {:type 'Int}
|
||||
:start {:type 'Int}
|
||||
:end {:type 'Int}}}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
:vendor_page {:fields {:vendors {:type '(list :vendor)}
|
||||
:count {:type 'Int}
|
||||
:total {:type 'Int}
|
||||
@@ -283,10 +271,10 @@
|
||||
:end {:type 'Int}}}
|
||||
|
||||
:account_page {:fields {:accounts {:type '(list :account)}
|
||||
:count {:type 'Int}
|
||||
:total {:type 'Int}
|
||||
:start {:type 'Int}
|
||||
:end {:type 'Int}}}
|
||||
:count {:type 'Int}
|
||||
:total {:type 'Int}
|
||||
:start {:type 'Int}
|
||||
:end {:type 'Int}}}
|
||||
|
||||
:reminder_page {:fields {:reminders {:type '(list :reminder)}
|
||||
:count {:type 'Int}
|
||||
@@ -303,7 +291,7 @@
|
||||
:paid {:type 'String}
|
||||
:unpaid {:type 'String}}}
|
||||
|
||||
:upcoming_transaction {:fields {:amount {:type :money}
|
||||
:upcoming_transaction {:fields {:amount {:type :money}
|
||||
:identifier {:type 'String}
|
||||
:date {:type :iso_date}}}
|
||||
|
||||
@@ -320,14 +308,13 @@
|
||||
:potential_transaction_rule_matches {:type '(list :transaction_rule)
|
||||
:args {:transaction_id {:type :id}}
|
||||
:resolve :get-transaction-rule-matches}
|
||||
|
||||
|
||||
:test_transaction_rule {:type '(list :transaction)
|
||||
:args {:transaction_rule {:type :edit_transaction_rule}}
|
||||
:resolve :test-transaction-rule}
|
||||
:args {:transaction_rule {:type :edit_transaction_rule}}
|
||||
:resolve :test-transaction-rule}
|
||||
|
||||
:run_transaction_rule {:type '(list :transaction)
|
||||
:args {:transaction_rule_id {:type :id}}
|
||||
:args {:transaction_rule_id {:type :id}}
|
||||
:resolve :run-transaction-rule}
|
||||
|
||||
:invoice_stats {:type '(list :invoice_stat)
|
||||
@@ -337,7 +324,6 @@
|
||||
:cash_flow {:type :cash_flow_result
|
||||
:args {:client_id {:type :id}}
|
||||
:resolve :get-cash-flow}
|
||||
|
||||
|
||||
:all_accounts {:type '(list :account)
|
||||
:args {}
|
||||
@@ -351,11 +337,7 @@
|
||||
:allowance {:type :account_allowance}
|
||||
:client_id {:type :id}
|
||||
:vendor_id {:type :id}}
|
||||
:resolve :search-account}
|
||||
|
||||
|
||||
|
||||
|
||||
:resolve :search-account}
|
||||
|
||||
:yodlee_merchants {:type '(list :yodlee_merchant)
|
||||
:args {}
|
||||
@@ -376,16 +358,13 @@
|
||||
:description {:type 'String}}
|
||||
:resolve :get-transaction-rule-page}
|
||||
|
||||
|
||||
|
||||
|
||||
:vendor {:type :vendor_page
|
||||
:args {:name_like {:type 'String}
|
||||
:start {:type 'Int}
|
||||
:per_page {:type 'Int}
|
||||
:sort {:type '(list :sort_item)}}
|
||||
:resolve :get-vendor}
|
||||
|
||||
|
||||
:vendor_by_id {:type :vendor
|
||||
:args {:id {:type :id}}
|
||||
:resolve :vendor-by-id}
|
||||
@@ -395,8 +374,7 @@
|
||||
:resolve :account-for-vendor}}
|
||||
|
||||
:input-objects
|
||||
{
|
||||
:sort_item
|
||||
{:sort_item
|
||||
{:fields {:sort_key {:type 'String}
|
||||
:sort_name {:type 'String}
|
||||
:asc {:type 'Boolean}}}
|
||||
@@ -495,8 +473,7 @@
|
||||
:name {:type 'String}
|
||||
:client_overrides {:type '(list :edit_account_client_override)}}}}
|
||||
|
||||
:enums {
|
||||
:processor {:values [{:enum-value :na}
|
||||
:enums {:processor {:values [{:enum-value :na}
|
||||
{:enum-value :doordash}
|
||||
{:enum-value :koala}
|
||||
{:enum-value :ezcater}
|
||||
@@ -533,9 +510,7 @@
|
||||
{:enum-value :equity}
|
||||
{:enum-value :revenue}]}}
|
||||
:mutations
|
||||
{
|
||||
|
||||
:delete_transaction_rule
|
||||
{:delete_transaction_rule
|
||||
{:type :id
|
||||
:args {:transaction_rule_id {:type :id}}
|
||||
:resolve :mutation/delete-transaction-rule}
|
||||
@@ -553,9 +528,7 @@
|
||||
:upsert_transaction_rule
|
||||
{:type :transaction_rule
|
||||
:args {:transaction_rule {:type :edit_transaction_rule}}
|
||||
:resolve :mutation/upsert-transaction-rule}
|
||||
}})
|
||||
|
||||
:resolve :mutation/upsert-transaction-rule}}})
|
||||
|
||||
(defn snake->kebab [s]
|
||||
(str/replace s #"_" "-"))
|
||||
@@ -571,65 +544,64 @@
|
||||
|
||||
(defn ->graphql [m]
|
||||
(walk/postwalk
|
||||
(fn [node]
|
||||
(cond
|
||||
(fn [node]
|
||||
(cond
|
||||
|
||||
(keyword? node)
|
||||
(snake node)
|
||||
(keyword? node)
|
||||
(snake node)
|
||||
|
||||
:else
|
||||
node))
|
||||
m))
|
||||
:else
|
||||
node))
|
||||
m))
|
||||
|
||||
|
||||
(defn get-expense-account-stats [_ {:keys [client_id] } _]
|
||||
(defn get-expense-account-stats [_ {:keys [client_id]} _]
|
||||
(let [query (cond-> {:query {:find ['?account '?account-name '(sum ?amount)]
|
||||
:in ['$]
|
||||
:where []}
|
||||
:args [(dc/db conn) client_id]}
|
||||
client_id (merge-query {:query {:in ['?c]}
|
||||
|
||||
:args [client_id]})
|
||||
(not client_id) (merge-query {:query {:where ['[?c :client/name]]}})
|
||||
:in ['$]
|
||||
:where []}
|
||||
:args [(dc/db conn) client_id]}
|
||||
client_id (merge-query {:query {:in ['?c]}
|
||||
|
||||
true (merge-query {:query {:where ['[?i :invoice/client ?c]
|
||||
'[?i :invoice/expense-accounts ?expense-account]
|
||||
'[?expense-account :invoice-expense-account/account ?account]
|
||||
'[?account :account/name ?account-name]
|
||||
'[?expense-account :invoice-expense-account/amount ?amount]]}}))
|
||||
:args [client_id]})
|
||||
(not client_id) (merge-query {:query {:where ['[?c :client/name]]}})
|
||||
|
||||
true (merge-query {:query {:where ['[?i :invoice/client ?c]
|
||||
'[?i :invoice/expense-accounts ?expense-account]
|
||||
'[?expense-account :invoice-expense-account/account ?account]
|
||||
'[?account :account/name ?account-name]
|
||||
'[?expense-account :invoice-expense-account/amount ?amount]]}}))
|
||||
result (query2 query)]
|
||||
(for [[account-id account-name total] result]
|
||||
{:account {:id account-id :name account-name} :total total})))
|
||||
|
||||
(defn get-invoice-stats [_ {:keys [client_id] } _]
|
||||
(defn get-invoice-stats [_ {:keys [client_id]} _]
|
||||
(let [query (cond-> {:query {:find ['?name '(sum ?outstanding-balance) '(sum ?total)]
|
||||
:in ['$]
|
||||
:where []}
|
||||
:args [(dc/db conn) client_id]}
|
||||
client_id (merge-query {:query {:in ['?c]}
|
||||
:args [client_id]})
|
||||
(not client_id) (merge-query {:query {:where ['[?c :client/name]]}})
|
||||
:in ['$]
|
||||
:where []}
|
||||
:args [(dc/db conn) client_id]}
|
||||
client_id (merge-query {:query {:in ['?c]}
|
||||
:args [client_id]})
|
||||
(not client_id) (merge-query {:query {:where ['[?c :client/name]]}})
|
||||
|
||||
true (merge-query {:query {:where ['[?i :invoice/client ?c]
|
||||
'[?i :invoice/outstanding-balance ?outstanding-balance]
|
||||
'[?i :invoice/total ?total]
|
||||
'[?i :invoice/due ?date]
|
||||
'[(.toInstant ^java.util.Date ?date) ?d2]
|
||||
'[(.between java.time.temporal.ChronoUnit/DAYS (java.time.Instant/now) ?d2 ) ?d3]
|
||||
'(or-join [?d3 ?name]
|
||||
(and [(<= ?d3 0)]
|
||||
[(ground :due) ?name])
|
||||
(and [(<= ?d3 30)]
|
||||
[(ground :due-30) ?name])
|
||||
(and [(<= ?d3 60)]
|
||||
[(ground :due-30) ?name])
|
||||
(and [(> ?d3 60)]
|
||||
[(ground :due-later) ?name]))]}}))
|
||||
true (merge-query {:query {:where ['[?i :invoice/client ?c]
|
||||
'[?i :invoice/outstanding-balance ?outstanding-balance]
|
||||
'[?i :invoice/total ?total]
|
||||
'[?i :invoice/due ?date]
|
||||
'[(.toInstant ^java.util.Date ?date) ?d2]
|
||||
'[(.between java.time.temporal.ChronoUnit/DAYS (java.time.Instant/now) ?d2) ?d3]
|
||||
'(or-join [?d3 ?name]
|
||||
(and [(<= ?d3 0)]
|
||||
[(ground :due) ?name])
|
||||
(and [(<= ?d3 30)]
|
||||
[(ground :due-30) ?name])
|
||||
(and [(<= ?d3 60)]
|
||||
[(ground :due-30) ?name])
|
||||
(and [(> ?d3 60)]
|
||||
[(ground :due-later) ?name]))]}}))
|
||||
result (->> (query2 query)
|
||||
(group-by first))]
|
||||
|
||||
|
||||
(for [[id name] [[:due "Due"] [:due-30 "0-30 days"] [:due-60 "31-60 days"] [:due-later ">60 days"]]
|
||||
:let [[[_ outstanding-balance total] ] (id result nil)
|
||||
:let [[[_ outstanding-balance total]] (id result nil)
|
||||
outstanding-balance (or outstanding-balance 0)
|
||||
total (or total 0)]]
|
||||
{:name name :unpaid outstanding-balance :paid (if (= :due id)
|
||||
@@ -637,7 +609,7 @@
|
||||
(- total outstanding-balance))})))
|
||||
|
||||
(defn has-fulfilled? [id date recent-fulfillments]
|
||||
|
||||
|
||||
(seq (transduce
|
||||
(filter (fn [[potential-id potential-date]]
|
||||
(let [date (coerce/to-date-time date)
|
||||
@@ -652,7 +624,7 @@
|
||||
|
||||
(defn get-cash-flow [_ {:keys [client_id]} _]
|
||||
(when client_id
|
||||
(let [{:client/keys [week-a-credits week-a-debits week-b-credits week-b-debits forecasted-transactions ]} (dc/pull (dc/db conn) '[*] client_id)
|
||||
(let [{:client/keys [week-a-credits week-a-debits week-b-credits week-b-debits forecasted-transactions]} (dc/pull (dc/db conn) '[*] client_id)
|
||||
total-cash (reduce
|
||||
(fn [total [credit debit]]
|
||||
(- (+ total credit)
|
||||
@@ -685,9 +657,9 @@
|
||||
:where ['[?p :payment/client ?client]
|
||||
'[?p :payment/status :payment-status/pending]
|
||||
'[?p :payment/amount ?amount]
|
||||
'(or
|
||||
[?p :payment/type :payment-type/debit]
|
||||
[?p :payment/type :payment-type/check])]}
|
||||
'(or
|
||||
[?p :payment/type :payment-type/debit]
|
||||
[?p :payment/type :payment-type/check])]}
|
||||
(dc/db conn) client_id (coerce/to-date (t/plus (time/local-now) (t/days 180))))))
|
||||
recent-fulfillments (dc/q {:find '[?f ?d]
|
||||
:in '[$ ?client ?min-date]
|
||||
@@ -710,7 +682,7 @@
|
||||
:date (coerce/to-date-time next)})
|
||||
is-week-a? (fn [d]
|
||||
(= 0 (mod (t/in-weeks (t/interval first-week-a d)) 2)))]
|
||||
|
||||
|
||||
{:beginning_balance total-cash
|
||||
:outstanding_payments outstanding-checks
|
||||
:invoices_due_soon (mapv (fn [[due outstanding invoice-number vendor-id vendor-name]]
|
||||
@@ -735,31 +707,29 @@
|
||||
:date (coerce/to-date-time date)})
|
||||
(take (* 7 4) (time/day-of-week-seq 1)))
|
||||
(filter #(< (:amount %) 0) forecasted-transactions))})))
|
||||
|
||||
|
||||
(def schema
|
||||
(-> integreat-schema
|
||||
(attach-tracing-resolvers
|
||||
{
|
||||
:get-all-accounts gq-accounts/get-all-graphql
|
||||
:get-transaction-rule-page gq-transaction-rules/get-transaction-rule-page
|
||||
:get-transaction-rule-matches gq-transaction-rules/get-transaction-rule-matches
|
||||
:get-expense-account-stats get-expense-account-stats
|
||||
:get-invoice-stats get-invoice-stats
|
||||
:get-cash-flow get-cash-flow
|
||||
:get-yodlee-merchants ym/get-yodlee-merchants
|
||||
:get-intuit-bank-accounts gq-intuit-bank-accounts/get-intuit-bank-accounts
|
||||
:vendor-by-id gq-vendors/get-by-id
|
||||
:account-for-vendor gq-accounts/default-for-vendor
|
||||
:mutation/delete-transaction-rule gq-transaction-rules/delete-transaction-rule
|
||||
:mutation/upsert-transaction-rule gq-transaction-rules/upsert-transaction-rule
|
||||
:test-transaction-rule gq-transaction-rules/test-transaction-rule
|
||||
:run-transaction-rule gq-transaction-rules/run-transaction-rule
|
||||
:mutation/upsert-vendor gq-vendors/upsert-vendor
|
||||
:mutation/merge-vendors gq-vendors/merge-vendors
|
||||
:get-vendor gq-vendors/get-graphql
|
||||
:search-vendor gq-vendors/search
|
||||
:search-account gq-accounts/search})
|
||||
{:get-all-accounts gq-accounts/get-all-graphql
|
||||
:get-transaction-rule-page gq-transaction-rules/get-transaction-rule-page
|
||||
:get-transaction-rule-matches gq-transaction-rules/get-transaction-rule-matches
|
||||
:get-expense-account-stats get-expense-account-stats
|
||||
:get-invoice-stats get-invoice-stats
|
||||
:get-cash-flow get-cash-flow
|
||||
:get-yodlee-merchants ym/get-yodlee-merchants
|
||||
:get-intuit-bank-accounts gq-intuit-bank-accounts/get-intuit-bank-accounts
|
||||
:vendor-by-id gq-vendors/get-by-id
|
||||
:account-for-vendor gq-accounts/default-for-vendor
|
||||
:mutation/delete-transaction-rule gq-transaction-rules/delete-transaction-rule
|
||||
:mutation/upsert-transaction-rule gq-transaction-rules/upsert-transaction-rule
|
||||
:test-transaction-rule gq-transaction-rules/test-transaction-rule
|
||||
:run-transaction-rule gq-transaction-rules/run-transaction-rule
|
||||
:mutation/upsert-vendor gq-vendors/upsert-vendor
|
||||
:mutation/merge-vendors gq-vendors/merge-vendors
|
||||
:get-vendor gq-vendors/get-graphql
|
||||
:search-vendor gq-vendors/search
|
||||
:search-account gq-accounts/search})
|
||||
gq-checks/attach
|
||||
gq-ledger/attach
|
||||
gq-plaid/attach
|
||||
@@ -772,30 +742,28 @@
|
||||
gq-sales-orders/attach
|
||||
schema/compile))
|
||||
|
||||
|
||||
|
||||
(defn simplify
|
||||
"Converts all ordered maps nested within the map into standard hash maps, and
|
||||
sequences into vectors, which makes for easier constants in the tests, and eliminates ordering problems."
|
||||
[m]
|
||||
(walk/postwalk
|
||||
(fn [node]
|
||||
(cond
|
||||
(instance? IPersistentMap node)
|
||||
(into {} node)
|
||||
(fn [node]
|
||||
(cond
|
||||
(instance? IPersistentMap node)
|
||||
(into {} node)
|
||||
|
||||
(seq? node)
|
||||
(vec node)
|
||||
(seq? node)
|
||||
(vec node)
|
||||
|
||||
(keyword? node)
|
||||
(kebab node)
|
||||
(keyword? node)
|
||||
(kebab node)
|
||||
|
||||
:else
|
||||
node))
|
||||
m))
|
||||
:else
|
||||
node))
|
||||
m))
|
||||
|
||||
(defn query-name [q]
|
||||
(try
|
||||
(try
|
||||
(str/join "__" (map name (:operations (p/operations (p/parse-query schema q)))))
|
||||
(catch Exception _
|
||||
"unknown query")))
|
||||
@@ -805,32 +773,32 @@
|
||||
(query id q nil))
|
||||
([id q v]
|
||||
(statsd/increment "query.graphql.count" {:tags #{(str "query:" (query-name q))}})
|
||||
(statsd/time! [(str "query.graphql.time" ) {:tags #{(str "query:" (query-name q))}}]
|
||||
(mu/with-context {:query-name (query-name q) :user id :query q}
|
||||
(mu/trace ::executing-query
|
||||
[]
|
||||
(try
|
||||
(let [[result time] (time-it (simplify (execute schema q (dissoc v
|
||||
:clients) {:id id
|
||||
:clients (:clients v)
|
||||
:log-context (or (mu/local-context) {})})))]
|
||||
|
||||
(when (seq (:errors result))
|
||||
(throw (ex-info "GraphQL error" {:result result})))
|
||||
result)
|
||||
(statsd/time! [(str "query.graphql.time") {:tags #{(str "query:" (query-name q))}}]
|
||||
(mu/with-context {:query-name (query-name q) :user id :query q}
|
||||
(mu/trace ::executing-query
|
||||
[]
|
||||
(try
|
||||
(let [[result time] (time-it (simplify (execute schema q (dissoc v
|
||||
:clients) {:id id
|
||||
:clients (:clients v)
|
||||
:log-context (or (mu/local-context) {})})))]
|
||||
|
||||
(catch Exception e
|
||||
(if-let [v (or (:validation-error (ex-data e))
|
||||
(:validation-error (ex-data (.getCause e))))]
|
||||
|
||||
(do
|
||||
(alog/warn ::query-validation
|
||||
:exception e)
|
||||
(throw e)
|
||||
#_{:errors [{:message v}]})
|
||||
(do
|
||||
(alog/error ::query-error
|
||||
:exception e)
|
||||
(when (seq (:errors result))
|
||||
(throw (ex-info "GraphQL error" {:result result})))
|
||||
result)
|
||||
|
||||
(throw e))))))))))
|
||||
(catch Exception e
|
||||
(if-let [v (or (:validation-error (ex-data e))
|
||||
(:validation-error (ex-data (.getCause e))))]
|
||||
|
||||
(do
|
||||
(alog/warn ::query-validation
|
||||
:exception e)
|
||||
(throw e)
|
||||
#_{:errors [{:message v}]})
|
||||
(do
|
||||
(alog/error ::query-error
|
||||
:exception e)
|
||||
|
||||
(throw e))))))))))
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
[iol-ion.tx :refer [random-tempid]]
|
||||
[com.brunobonacci.mulog :as mu]))
|
||||
|
||||
|
||||
(defn get-all-graphql [context args _]
|
||||
(assert-admin (:id context))
|
||||
(let [args (assoc args :id (:id context))
|
||||
|
||||
@@ -97,7 +97,6 @@
|
||||
[:line {:line-width 0.15 :color [50 50 50]}]]
|
||||
[:cell {:colspan 3}]]
|
||||
|
||||
|
||||
[[:cell {:size 9 :leading 11.5} "\n\n\n\n\nMEMO"]
|
||||
[:cell {:colspan 5 :leading 11.5} (split-memo memo)
|
||||
[:line {:line-width 0.15 :color [50 50 50]}]]
|
||||
@@ -186,8 +185,6 @@
|
||||
:payment/pdf-data
|
||||
(edn/read-string)
|
||||
|
||||
|
||||
|
||||
make-check-pdf)]
|
||||
(s3/put-object :bucket-name (:data-bucket env)
|
||||
:key (:payment/s3-key check)
|
||||
@@ -277,7 +274,6 @@
|
||||
(conj payment)
|
||||
(into (invoice-payments invoices invoice-amounts)))))
|
||||
|
||||
|
||||
(defmethod invoices->entities :payment-type/debit [invoices vendor client bank-account type index invoice-amounts date]
|
||||
(when (<= (->> invoices
|
||||
(map (comp invoice-amounts :db/id))
|
||||
@@ -297,7 +293,6 @@
|
||||
(conj payment)
|
||||
(into (invoice-payments invoices invoice-amounts)))))
|
||||
|
||||
|
||||
(defmethod invoices->entities :payment-type/balance-credit [invoices invoice-amounts]
|
||||
(when (<= (->> invoices
|
||||
(map (comp invoice-amounts :db/id))
|
||||
@@ -488,7 +483,6 @@
|
||||
{:s3-url nil
|
||||
:invoices (d-invoices/get-multi (map :invoice_id (:invoice_payments args)))})))
|
||||
|
||||
|
||||
(defn void-payment [context {id :payment_id} _]
|
||||
(let [check (d-checks/get-by-id id)]
|
||||
(assert (or (= :payment-status/pending (:payment/status check))
|
||||
@@ -549,7 +543,6 @@
|
||||
:invoice-status/unpaid)}]]))))))))
|
||||
id))
|
||||
|
||||
|
||||
(defn void-payments [context args _]
|
||||
(assert-admin (:id context))
|
||||
(let [args (assoc args :clients (:clients context))
|
||||
@@ -607,7 +600,6 @@
|
||||
0.001))
|
||||
invoices)
|
||||
|
||||
|
||||
total-to-pay (reduce + 0 (map :invoice/outstanding-balance invoices-to-be-paid))
|
||||
_ (when (<= total-to-pay 0.001)
|
||||
(assert-failure "You must select invoices that need to be paid."))
|
||||
@@ -637,8 +629,6 @@
|
||||
[total-to-pay []])))
|
||||
(into {}))
|
||||
|
||||
|
||||
|
||||
vendor-id (:db/id (:invoice/vendor (first invoices)))
|
||||
payment {:db/id (str vendor-id)
|
||||
:payment/amount total-to-pay
|
||||
@@ -751,7 +741,6 @@
|
||||
{:enum-value :pending}
|
||||
{:enum-value :cleared}]}})
|
||||
|
||||
|
||||
(def resolvers
|
||||
{:get-potential-payments get-potential-payments
|
||||
:get-payment-page get-payment-page
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
(defn get-admin-client [context {:keys [id]} _]
|
||||
(assert-admin (:id context))
|
||||
(->graphql
|
||||
(-> (d-clients/get-by-id id)
|
||||
(update :client/bank-accounts (fn [bas]
|
||||
(map #(set/rename-keys % {:bank-account/use-date-instead-of-post-date? :use-date-instead-of-post-date}) bas))))))
|
||||
(-> (d-clients/get-by-id id)
|
||||
(update :client/bank-accounts (fn [bas]
|
||||
(map #(set/rename-keys % {:bank-account/use-date-instead-of-post-date? :use-date-instead-of-post-date}) bas))))))
|
||||
|
||||
(defn get-client-page [context args _]
|
||||
(assert-admin (:id context))
|
||||
@@ -29,7 +29,7 @@
|
||||
[clients clients-count] (d-clients/get-graphql-page (assoc (<-graphql (:filters args))
|
||||
:clients (:clients context)))
|
||||
clients (->> clients
|
||||
|
||||
|
||||
(map (fn [c]
|
||||
(update c :client/bank-accounts (fn [bas]
|
||||
(map #(set/rename-keys % {:bank-account/use-date-instead-of-post-date? :use-date-instead-of-post-date}) bas)))))
|
||||
@@ -47,13 +47,6 @@
|
||||
bank-accounts))))))]
|
||||
(result->page clients clients-count :clients (:filters args))))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(def objects
|
||||
{:location_match
|
||||
{:fields {:location {:type 'String}
|
||||
@@ -102,15 +95,13 @@
|
||||
:yodlee_provider_accounts {:type '(list :yodlee_provider_account)}
|
||||
:plaid_items {:type '(list :plaid_item)}}}
|
||||
|
||||
:client_page
|
||||
:client_page
|
||||
{:fields {:clients {:type '(list :client)}
|
||||
:count {:type 'Int}
|
||||
:total {:type 'Int}
|
||||
:start {:type 'Int}
|
||||
:end {:type 'Int}}}
|
||||
|
||||
|
||||
|
||||
:bank_account
|
||||
{:fields {:id {:type :id}
|
||||
:integration_status {:type :integration_status}
|
||||
@@ -139,9 +130,7 @@
|
||||
:forecasted_transaction {:fields {:identifier {:type 'String}
|
||||
:id {:type :id}
|
||||
:day_of_month {:type 'Int}
|
||||
:amount {:type :money}}}
|
||||
|
||||
})
|
||||
:amount {:type :money}}}})
|
||||
|
||||
(def queries
|
||||
{:client {:type '(list :client)
|
||||
@@ -158,12 +147,12 @@
|
||||
{})
|
||||
|
||||
(def input-objects
|
||||
{ :client_filters
|
||||
{:client_filters
|
||||
{:fields {:code {:type 'String}
|
||||
:name_like {:type 'String}
|
||||
:start {:type 'Int}
|
||||
:per_page {:type 'Int}
|
||||
:sort {:type '(list :sort_item)}}} })
|
||||
:sort {:type '(list :sort_item)}}}})
|
||||
|
||||
(def enums
|
||||
{:bank_account_type {:values [{:enum-value :check}
|
||||
@@ -173,11 +162,10 @@
|
||||
(def resolvers
|
||||
{:get-client get-client
|
||||
:get-admin-client get-admin-client
|
||||
:get-client-page get-client-page })
|
||||
|
||||
:get-client-page get-client-page})
|
||||
|
||||
(defn attach [schema]
|
||||
(->
|
||||
(->
|
||||
(merge-with merge schema
|
||||
{:objects objects
|
||||
:queries queries
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
(defn get-all-expected-deposits [context args _]
|
||||
(assert-admin (:id context))
|
||||
(map
|
||||
(comp ->graphql status->graphql)
|
||||
(comp ->graphql status->graphql)
|
||||
(first (d-expected-deposit/get-graphql (assoc (<-graphql args) :count Integer/MAX_VALUE)))))
|
||||
|
||||
(defn get-expected-deposit-page [context args _]
|
||||
|
||||
@@ -17,15 +17,14 @@
|
||||
{:name name
|
||||
:id id}))
|
||||
|
||||
|
||||
(def objects
|
||||
{:ezcater_caterer {:fields {:name {:type 'String}
|
||||
:id {:type :id}}}})
|
||||
|
||||
(def queries
|
||||
{:search_ezcater_caterer {:type '(list :search_result)
|
||||
:args {:query {:type 'String}}
|
||||
:resolve :search-ezcater-caterer}})
|
||||
:args {:query {:type 'String}}
|
||||
:resolve :search-ezcater-caterer}})
|
||||
|
||||
(def enums
|
||||
{})
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
(merge-query {:query {:find ['?e]
|
||||
:where ['[?e :import-batch/date]]}}))]
|
||||
|
||||
|
||||
(cond->> (query2 query)
|
||||
true (apply-sort-3 args)
|
||||
true (apply-pagination args))))
|
||||
@@ -66,9 +65,8 @@
|
||||
(map #(update % :import-batch/date coerce/to-date-time)))
|
||||
matching-count :data args)))
|
||||
|
||||
|
||||
(defn attach [schema]
|
||||
(->
|
||||
(->
|
||||
(merge-with merge schema
|
||||
{:objects {:import_batch {:fields {:user_name {:type 'String}
|
||||
:id {:type :id}
|
||||
@@ -83,12 +81,10 @@
|
||||
:count {:type 'Int}
|
||||
:total {:type 'Int}
|
||||
:start {:type 'Int}
|
||||
:end {:type 'Int}}}
|
||||
|
||||
}
|
||||
:end {:type 'Int}}}}
|
||||
:queries {:import_batch_page {:type :import_batch_page
|
||||
:args {:filters {:type :import_batch_filters}}
|
||||
|
||||
|
||||
:resolve :get-import-batch-page}}
|
||||
:mutations {}
|
||||
:input-objects {:import_batch_filters {:fields {:start {:type 'Int}
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
(defn get-intuit-bank-accounts [context _ _]
|
||||
(assert-admin (:id context))
|
||||
(->graphql (map first (dc/q '[:find (pull ?e [*])
|
||||
:in $
|
||||
:where [?e :intuit-bank-account/external-id]]
|
||||
(dc/db conn)))))
|
||||
:in $
|
||||
:where [?e :intuit-bank-account/external-id]]
|
||||
(dc/db conn)))))
|
||||
|
||||
@@ -174,8 +174,6 @@
|
||||
(let [error (str "Expense account total (" expense-account-total ") does not equal invoice total (" total ")")]
|
||||
(throw (ex-info error {:validation-error error}))))))
|
||||
|
||||
|
||||
|
||||
(defn add-invoice [context {{:keys [expense_accounts client_id vendor_id] :as in} :invoice} _]
|
||||
(assert-no-conflicting in)
|
||||
(assert-can-see-client (:id context) client_id)
|
||||
@@ -193,8 +191,6 @@
|
||||
(when-not ((set (map :db/id (:client/bank-accounts (d-clients/get-by-id client-id)))) bank-account-id)
|
||||
(throw (ex-info (str "Bank account does not belong to client") {:validation-error "Bank account does not belong to client."}))))
|
||||
|
||||
|
||||
|
||||
(defn add-and-print-invoice [context {{:keys [total client_id vendor_id] :as in} :invoice bank-account-id :bank_account_id type :type} _]
|
||||
(mu/trace ::validating-invoice [:invoice in]
|
||||
(do
|
||||
@@ -261,7 +257,6 @@
|
||||
|
||||
(-> (d-invoices/get-by-id id) (->graphql (:id context)))))
|
||||
|
||||
|
||||
(defn get-ids-matching-filters [args]
|
||||
(let [ids (some-> args
|
||||
:filters
|
||||
@@ -448,8 +443,6 @@
|
||||
[])]
|
||||
accounts)))
|
||||
|
||||
|
||||
|
||||
(defn bulk-change-invoices [context args _]
|
||||
(assert-admin (:id context))
|
||||
(when-not (:client_id args)
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
_ (when (:client_id (:filters args))
|
||||
(assert-can-see-client (:id context) (:client_id (:filters args))))
|
||||
clients (or (and (:client_id (:filters args))
|
||||
[{:db/id (:client_id (:filters args))}])
|
||||
(:clients context))
|
||||
|
||||
[{:db/id (:client_id (:filters args))}])
|
||||
(:clients context))
|
||||
|
||||
[journal-entries journal-entries-count] (l/get-graphql (assoc (<-graphql (:filters args))
|
||||
:clients clients))
|
||||
|
||||
@@ -55,12 +55,10 @@
|
||||
(let [args (assoc args :id (:id context))
|
||||
[journal-entries journal-entries-count] (l/get-graphql (assoc (<-graphql (:filters args))
|
||||
:per-page Integer/MAX_VALUE
|
||||
:clients (:clients context)))
|
||||
:clients (:clients context)))]
|
||||
|
||||
|
||||
]
|
||||
{:csv_content_b64 (Base64/encodeBase64String
|
||||
(.getBytes
|
||||
(.getBytes
|
||||
(with-open [w (java.io.StringWriter.)]
|
||||
(csv/write-csv w
|
||||
(into [["Client" "Vendor" "Date" "Journal Entry" "Journal Entry Line" "Account Code" "Account Name" "Account Type" "Debit" "Credit" "Net"]]
|
||||
@@ -83,22 +81,19 @@
|
||||
(-> li :journal-entry-line/account :bank-account/numeric-code))
|
||||
(or (-> li :journal-entry-line/account :account/name)
|
||||
(-> li :journal-entry-line/account :bank-account/name))
|
||||
(some-> account-type name )
|
||||
(some-> account-type name)
|
||||
(-> li :journal-entry-line/debit)
|
||||
(-> li :journal-entry-line/credit)
|
||||
(if (#{:account-type/asset
|
||||
:account-type/dividend
|
||||
:account-type/expense} account-type)
|
||||
(- (or (-> li :journal-entry-line/debit) 0.0) (or (-> li :journal-entry-line/credit) 0.0))
|
||||
(- (or (-> li :journal-entry-line/credit) 0.0) (or (-> li :journal-entry-line/debit) 0.0)))
|
||||
|
||||
]))
|
||||
(:journal-entry/line-items j))
|
||||
))))
|
||||
(- (or (-> li :journal-entry-line/credit) 0.0) (or (-> li :journal-entry-line/debit) 0.0)))]))
|
||||
|
||||
(:journal-entry/line-items j))))))
|
||||
:quote? (constantly true))
|
||||
(.toString w))))}))
|
||||
|
||||
|
||||
(defn roll-up-until
|
||||
([lookup-account all-ledger-entries end-date]
|
||||
(roll-up-until lookup-account all-ledger-entries end-date nil))
|
||||
@@ -107,57 +102,56 @@
|
||||
(filter (fn [[d]]
|
||||
(if start-date
|
||||
(and
|
||||
(>= (compare d start-date) 0)
|
||||
(<= (compare d end-date) 0))
|
||||
(>= (compare d start-date) 0)
|
||||
(<= (compare d end-date) 0))
|
||||
(<= (compare d end-date) 0))))
|
||||
(reduce
|
||||
(fn [acc [_ _ account location debit credit]]
|
||||
(-> acc
|
||||
(update-in [[location account] :debit] (fnil + 0.0) debit)
|
||||
(update-in [[location account] :credit] (fnil + 0.0) credit)
|
||||
(update-in [[location account] :count] (fnil + 0) 1))
|
||||
)
|
||||
{})
|
||||
(fn [acc [_ _ account location debit credit]]
|
||||
(-> acc
|
||||
(update-in [[location account] :debit] (fnil + 0.0) debit)
|
||||
(update-in [[location account] :credit] (fnil + 0.0) credit)
|
||||
(update-in [[location account] :count] (fnil + 0) 1)))
|
||||
{})
|
||||
(reduce-kv
|
||||
(fn [acc [location account-id] {:keys [debit credit count]}]
|
||||
(let [account (lookup-account account-id)
|
||||
account-type (:account_type account)]
|
||||
|
||||
(conj acc (merge {:id (str account-id "-" location)
|
||||
:location (or location "")
|
||||
:count count
|
||||
:debits debit
|
||||
:credits credit
|
||||
:amount (if account-type (if (#{:account-type/asset
|
||||
:account-type/dividend
|
||||
:account-type/expense} account-type)
|
||||
(- debit credit)
|
||||
(- credit debit))
|
||||
0.0)}
|
||||
account))))
|
||||
[]))))
|
||||
(fn [acc [location account-id] {:keys [debit credit count]}]
|
||||
(let [account (lookup-account account-id)
|
||||
account-type (:account_type account)]
|
||||
|
||||
(conj acc (merge {:id (str account-id "-" location)
|
||||
:location (or location "")
|
||||
:count count
|
||||
:debits debit
|
||||
:credits credit
|
||||
:amount (if account-type (if (#{:account-type/asset
|
||||
:account-type/dividend
|
||||
:account-type/expense} account-type)
|
||||
(- debit credit)
|
||||
(- credit debit))
|
||||
0.0)}
|
||||
account))))
|
||||
[]))))
|
||||
|
||||
(defn full-ledger-for-client [client-id]
|
||||
(->> (dc/q
|
||||
{:find ['?d '?jel '?account '?location '?debit '?credit]
|
||||
:in ['$ '?client-id]
|
||||
:where '[[?e :journal-entry/client ?client-id]
|
||||
[?e :journal-entry/date ?d]
|
||||
[?e :journal-entry/line-items ?jel]
|
||||
(or-join [?e]
|
||||
(and [?e :journal-entry/original-entity ?i]
|
||||
(or-join [?e ?i]
|
||||
(and
|
||||
[?i :transaction/bank-account ?b]
|
||||
(or [?b :bank-account/include-in-reports true]
|
||||
(not [?b :bank-account/include-in-reports])))
|
||||
(not [?i :transaction/bank-account])))
|
||||
(not [?e :journal-entry/original-entity ]))
|
||||
[(get-else $ ?jel :journal-entry-line/account :account/unknown) ?account]
|
||||
[(get-else $ ?jel :journal-entry-line/debit 0.0) ?debit ]
|
||||
[(get-else $ ?jel :journal-entry-line/credit 0.0) ?credit]
|
||||
[(get-else $ ?jel :journal-entry-line/location "") ?location]]}
|
||||
(dc/db conn) client-id)
|
||||
(->> (dc/q
|
||||
{:find ['?d '?jel '?account '?location '?debit '?credit]
|
||||
:in ['$ '?client-id]
|
||||
:where '[[?e :journal-entry/client ?client-id]
|
||||
[?e :journal-entry/date ?d]
|
||||
[?e :journal-entry/line-items ?jel]
|
||||
(or-join [?e]
|
||||
(and [?e :journal-entry/original-entity ?i]
|
||||
(or-join [?e ?i]
|
||||
(and
|
||||
[?i :transaction/bank-account ?b]
|
||||
(or [?b :bank-account/include-in-reports true]
|
||||
(not [?b :bank-account/include-in-reports])))
|
||||
(not [?i :transaction/bank-account])))
|
||||
(not [?e :journal-entry/original-entity]))
|
||||
[(get-else $ ?jel :journal-entry-line/account :account/unknown) ?account]
|
||||
[(get-else $ ?jel :journal-entry-line/debit 0.0) ?debit]
|
||||
[(get-else $ ?jel :journal-entry-line/credit 0.0) ?credit]
|
||||
[(get-else $ ?jel :journal-entry-line/location "") ?location]]}
|
||||
(dc/db conn) client-id)
|
||||
(sort-by first)))
|
||||
|
||||
(defn get-balance-sheet [context args _]
|
||||
@@ -180,30 +174,29 @@
|
||||
[client-id (build-account-lookup client-id)]))
|
||||
(into {}))]
|
||||
(alog/info ::balance-sheet :params args)
|
||||
|
||||
|
||||
(cond-> {:balance-sheet-accounts (mapcat
|
||||
#(roll-up-until (lookup-account %) (all-ledger-entries %) end-date )
|
||||
client-ids)
|
||||
}
|
||||
#(roll-up-until (lookup-account %) (all-ledger-entries %) end-date)
|
||||
client-ids)}
|
||||
(:include_comparison args) (assoc :comparable-balance-sheet-accounts (mapcat
|
||||
#(roll-up-until (lookup-account %) (all-ledger-entries %) comparable-date )
|
||||
client-ids))
|
||||
#(roll-up-until (lookup-account %) (all-ledger-entries %) comparable-date)
|
||||
client-ids))
|
||||
true ->graphql)))
|
||||
|
||||
(defn get-profit-and-loss-raw [client-ids periods]
|
||||
(let [ all-ledger-entries (->> client-ids
|
||||
(map (fn [client-id]
|
||||
[client-id (full-ledger-for-client client-id)]))
|
||||
(into {}))
|
||||
(let [all-ledger-entries (->> client-ids
|
||||
(map (fn [client-id]
|
||||
[client-id (full-ledger-for-client client-id)]))
|
||||
(into {}))
|
||||
lookup-account (->> client-ids
|
||||
(map (fn [client-id]
|
||||
[client-id (build-account-lookup client-id)]))
|
||||
(into {}))]
|
||||
(->graphql {:periods
|
||||
(->graphql {:periods
|
||||
(->> periods
|
||||
(mapv (fn [{:keys [start end]}]
|
||||
{:accounts (mapcat
|
||||
#(roll-up-until (lookup-account %) (all-ledger-entries %) (coerce/to-date end) (coerce/to-date start) )
|
||||
#(roll-up-until (lookup-account %) (all-ledger-entries %) (coerce/to-date end) (coerce/to-date start))
|
||||
client-ids)})))})))
|
||||
|
||||
(defn get-profit-and-loss [context args _]
|
||||
@@ -216,12 +209,9 @@
|
||||
(assert-can-see-client (:id context) client-id))
|
||||
_ (when (and (:include_deltas args)
|
||||
(:column_per_location args))
|
||||
(throw (ex-info "Please select one of 'Include deltas' or 'Column per location'" {:validation-error "Please select one of 'Include deltas' or 'Column per location'"}))) ]
|
||||
(throw (ex-info "Please select one of 'Include deltas' or 'Column per location'" {:validation-error "Please select one of 'Include deltas' or 'Column per location'"})))]
|
||||
(get-profit-and-loss-raw client-ids (:periods args))))
|
||||
|
||||
|
||||
|
||||
|
||||
;; profit and loss based off of index
|
||||
#_(defn get-profit-and-loss [context args _]
|
||||
(let [client-id (:client_id args)
|
||||
@@ -239,17 +229,17 @@
|
||||
:in $ [?c ...]
|
||||
:where
|
||||
(or-join [?c ?a ?l]
|
||||
(and
|
||||
[?a :account/numeric-code]
|
||||
(not [?a :account/location])
|
||||
[?c :client/locations ?l])
|
||||
(and
|
||||
[?a :account/numeric-code]
|
||||
[?a :account/location ?l]
|
||||
[?c :client/locations ?l])
|
||||
[?a :account/numeric-code]
|
||||
(not [?a :account/location])
|
||||
[?c :client/locations ?l])
|
||||
(and
|
||||
[?c :client/bank-accounts ?a]
|
||||
[(ground "A") ?l]))]
|
||||
[?a :account/numeric-code]
|
||||
[?a :account/location ?l]
|
||||
[?c :client/locations ?l])
|
||||
(and
|
||||
[?c :client/bank-accounts ?a]
|
||||
[(ground "A") ?l]))]
|
||||
(dc/db conn)
|
||||
client-ids)
|
||||
lookup-account (->> client-ids
|
||||
@@ -257,49 +247,48 @@
|
||||
[client-id (build-account-lookup client-id)]))
|
||||
(into {}))]
|
||||
(->graphql
|
||||
{:periods
|
||||
(->> (:periods args)
|
||||
(mapv (fn [{:keys [start end]}]
|
||||
(let [start (coerce/to-date start)
|
||||
end (coerce/to-date end)]
|
||||
{:accounts (mapcat
|
||||
(fn [[c a l]]
|
||||
(let [start-point (->> (dc/index-pull db
|
||||
{:index :avet
|
||||
:selector [:db/id :journal-entry-line/running-balance :journal-entry-line/client+account+location+date]
|
||||
:start [:journal-entry-line/client+account+location+date [c a l start]]
|
||||
:reverse true
|
||||
:limit 1})
|
||||
(take-while (fn [result]
|
||||
(= [c a l]
|
||||
(take 3 (:journal-entry-line/client+account+location+date result)))))
|
||||
(drop-while (fn [{[_ _ _ date] :journal-entry-line/client+account+location+date}]
|
||||
(>= (compare date start) 0)))
|
||||
first)
|
||||
end-point (->> (dc/index-pull db
|
||||
{:index :avet
|
||||
:selector [:db/id :journal-entry-line/running-balance :journal-entry-line/client+account+location+date]
|
||||
:start [:journal-entry-line/client+account+location+date [c a l end]]
|
||||
:reverse true
|
||||
:limit 1})
|
||||
(take-while (fn [result]
|
||||
(= [c a l]
|
||||
(take 3 (:journal-entry-line/client+account+location+date result)))))
|
||||
(take 1)
|
||||
(drop-while (fn [{[_ _ _ date] :journal-entry-line/client+account+location+date}]
|
||||
(>= (compare date end) 0)))
|
||||
first)]
|
||||
(when end-point
|
||||
[(merge {:id (str a "-" l)
|
||||
:location (or l "")
|
||||
:count 0
|
||||
:debits 0
|
||||
:credits 0
|
||||
:amount (- (or (:journal-entry-line/running-balance end-point) 0.0)
|
||||
(or (:journal-entry-line/running-balance start-point) 0.0))
|
||||
}
|
||||
((lookup-account c) a))])))
|
||||
all-used-account-locations)}))))})))
|
||||
{:periods
|
||||
(->> (:periods args)
|
||||
(mapv (fn [{:keys [start end]}]
|
||||
(let [start (coerce/to-date start)
|
||||
end (coerce/to-date end)]
|
||||
{:accounts (mapcat
|
||||
(fn [[c a l]]
|
||||
(let [start-point (->> (dc/index-pull db
|
||||
{:index :avet
|
||||
:selector [:db/id :journal-entry-line/running-balance :journal-entry-line/client+account+location+date]
|
||||
:start [:journal-entry-line/client+account+location+date [c a l start]]
|
||||
:reverse true
|
||||
:limit 1})
|
||||
(take-while (fn [result]
|
||||
(= [c a l]
|
||||
(take 3 (:journal-entry-line/client+account+location+date result)))))
|
||||
(drop-while (fn [{[_ _ _ date] :journal-entry-line/client+account+location+date}]
|
||||
(>= (compare date start) 0)))
|
||||
first)
|
||||
end-point (->> (dc/index-pull db
|
||||
{:index :avet
|
||||
:selector [:db/id :journal-entry-line/running-balance :journal-entry-line/client+account+location+date]
|
||||
:start [:journal-entry-line/client+account+location+date [c a l end]]
|
||||
:reverse true
|
||||
:limit 1})
|
||||
(take-while (fn [result]
|
||||
(= [c a l]
|
||||
(take 3 (:journal-entry-line/client+account+location+date result)))))
|
||||
(take 1)
|
||||
(drop-while (fn [{[_ _ _ date] :journal-entry-line/client+account+location+date}]
|
||||
(>= (compare date end) 0)))
|
||||
first)]
|
||||
(when end-point
|
||||
[(merge {:id (str a "-" l)
|
||||
:location (or l "")
|
||||
:count 0
|
||||
:debits 0
|
||||
:credits 0
|
||||
:amount (- (or (:journal-entry-line/running-balance end-point) 0.0)
|
||||
(or (:journal-entry-line/running-balance start-point) 0.0))}
|
||||
((lookup-account c) a))])))
|
||||
all-used-account-locations)}))))})))
|
||||
|
||||
(defn profit-and-loss-pdf [context args value]
|
||||
(let [data (get-profit-and-loss context args value)
|
||||
@@ -320,10 +309,9 @@
|
||||
|
||||
(->graphql result)))
|
||||
|
||||
|
||||
(defn assoc-error [f]
|
||||
(fn [entry]
|
||||
(try
|
||||
(try
|
||||
(f entry)
|
||||
(catch Exception e
|
||||
(assoc entry :error (.getMessage e)
|
||||
@@ -333,13 +321,13 @@
|
||||
(defn all-ids-not-locked [all-ids]
|
||||
(->> all-ids
|
||||
(dc/q '[:find ?t
|
||||
:in $ [?t ...]
|
||||
:where
|
||||
[?t :journal-entry/client ?c]
|
||||
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
|
||||
[?t :journal-entry/date ?d]
|
||||
[(>= ?d ?lu)]]
|
||||
(dc/db conn))
|
||||
:in $ [?t ...]
|
||||
:where
|
||||
[?t :journal-entry/client ?c]
|
||||
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
|
||||
[?t :journal-entry/date ?d]
|
||||
[(>= ?d ?lu)]]
|
||||
(dc/db conn))
|
||||
(map first)))
|
||||
|
||||
(defn delete-external-ledger [context args _]
|
||||
@@ -353,8 +341,8 @@
|
||||
(#(l/raw-graphql-ids (dc/db conn) %))
|
||||
:ids)
|
||||
_ (alog/info ::trying-to-delete
|
||||
:count (count ids)
|
||||
:sample (take 3 ids))
|
||||
:count (count ids)
|
||||
:sample (take 3 ids))
|
||||
specific-ids (l/filter-ids (:ids args))
|
||||
all-ids (all-ids-not-locked (into (set ids) specific-ids))]
|
||||
(if (> (count all-ids) 1000)
|
||||
@@ -364,7 +352,7 @@
|
||||
(audit-transact-batch
|
||||
(map (fn [i]
|
||||
[:db/retractEntity i])
|
||||
all-ids)
|
||||
all-ids)
|
||||
(:id context))
|
||||
{:message (str "Succesfully deleted " (count all-ids) " ledger entries.")}))))
|
||||
|
||||
@@ -372,15 +360,15 @@
|
||||
(assert-admin (:id context))
|
||||
(let [used-vendor-names (set (map :vendor_name (:entries args)))
|
||||
all-vendors (mu/trace ::get-all-vendors
|
||||
[]
|
||||
(->> (dc/q '[:find ?e
|
||||
:in $ [?name ...]
|
||||
:where [?e :vendor/name ?name]]
|
||||
(dc/db conn)
|
||||
used-vendor-names)
|
||||
(map first)
|
||||
(pull-many (dc/db conn) [:db/id :vendor/name])
|
||||
(by :vendor/name)))
|
||||
[]
|
||||
(->> (dc/q '[:find ?e
|
||||
:in $ [?name ...]
|
||||
:where [?e :vendor/name ?name]]
|
||||
(dc/db conn)
|
||||
used-vendor-names)
|
||||
(map first)
|
||||
(pull-many (dc/db conn) [:db/id :vendor/name])
|
||||
(by :vendor/name)))
|
||||
client-locked-lookup (mu/trace ::get-all-clients []
|
||||
(->> (dc/q '[:find ?code ?locked-until
|
||||
:in $
|
||||
@@ -389,18 +377,18 @@
|
||||
(dc/db conn))
|
||||
(into {})))
|
||||
all-client-bank-accounts (mu/trace ::get-all-client-bank-accounts
|
||||
[]
|
||||
(->> (dc/q '[:find ?code ?ba-code
|
||||
:in $
|
||||
:where [?c :client/code ?code]
|
||||
[?c :client/bank-accounts ?ba]
|
||||
[?ba :bank-account/code ?ba-code]]
|
||||
(dc/db conn))
|
||||
(reduce
|
||||
(fn [acc [code ba-code]]
|
||||
(update acc code (fnil conj #{}) ba-code))
|
||||
{})))
|
||||
|
||||
[]
|
||||
(->> (dc/q '[:find ?code ?ba-code
|
||||
:in $
|
||||
:where [?c :client/code ?code]
|
||||
[?c :client/bank-accounts ?ba]
|
||||
[?ba :bank-account/code ?ba-code]]
|
||||
(dc/db conn))
|
||||
(reduce
|
||||
(fn [acc [code ba-code]]
|
||||
(update acc code (fnil conj #{}) ba-code))
|
||||
{})))
|
||||
|
||||
all-client-locations (mu/trace ::get-all-client-locations
|
||||
[]
|
||||
(->> (dc/q '[:find ?code ?location
|
||||
@@ -409,160 +397,158 @@
|
||||
[?c :client/locations ?location]]
|
||||
(dc/db conn))
|
||||
(reduce
|
||||
(fn [acc [code ba-code]]
|
||||
(update acc code (fnil conj #{"HQ" "A"}) ba-code))
|
||||
{})))
|
||||
|
||||
(fn [acc [code ba-code]]
|
||||
(update acc code (fnil conj #{"HQ" "A"}) ba-code))
|
||||
{})))
|
||||
|
||||
new-hidden-vendors (reduce
|
||||
(fn [new-vendors {:keys [vendor_name]}]
|
||||
(if (or (all-vendors vendor_name)
|
||||
(new-vendors vendor_name))
|
||||
new-vendors
|
||||
(assoc new-vendors vendor_name
|
||||
{:vendor/name vendor_name
|
||||
:vendor/hidden true
|
||||
:db/id vendor_name})))
|
||||
{}
|
||||
(:entries args))
|
||||
(fn [new-vendors {:keys [vendor_name]}]
|
||||
(if (or (all-vendors vendor_name)
|
||||
(new-vendors vendor_name))
|
||||
new-vendors
|
||||
(assoc new-vendors vendor_name
|
||||
{:vendor/name vendor_name
|
||||
:vendor/hidden true
|
||||
:db/id vendor_name})))
|
||||
{}
|
||||
(:entries args))
|
||||
_ (mu/trace ::upsert-new-vendors
|
||||
[]
|
||||
(audit-transact-batch (vec (vals new-hidden-vendors)) (:id context)))
|
||||
[]
|
||||
(audit-transact-batch (vec (vals new-hidden-vendors)) (:id context)))
|
||||
all-vendors (->> (dc/q '[:find ?e
|
||||
:in $ [?name ...]
|
||||
:where [?e :vendor/name ?name]]
|
||||
(dc/db conn)
|
||||
used-vendor-names)
|
||||
:in $ [?name ...]
|
||||
:where [?e :vendor/name ?name]]
|
||||
(dc/db conn)
|
||||
used-vendor-names)
|
||||
(map first)
|
||||
(pull-many (dc/db conn) [:db/id :vendor/name])
|
||||
(by :vendor/name))
|
||||
all-accounts (mu/trace ::get-all-accounts []
|
||||
(transduce (map (comp str :account/numeric-code)) conj #{} (a/get-accounts)))
|
||||
transaction (mu/trace ::build-transaction
|
||||
[:count (count (:entries args))]
|
||||
(doall (map
|
||||
(assoc-error (fn [entry]
|
||||
(let [vendor (all-vendors (:vendor_name entry))]
|
||||
(when-not (client-locked-lookup (:client_code entry))
|
||||
(throw (ex-info (str "Client '" (:client_code entry )"' not found.") {:status :error}) ))
|
||||
(when-not vendor
|
||||
(throw (ex-info (str "Vendor '" (:vendor_name entry) "' not found.") {:status :error})))
|
||||
(when-not (re-find #"\d{1,2}/\d{1,2}/\d{4}" (:date entry))
|
||||
(throw (ex-info (str "Date must be MM/dd/yyyy") {:status :error})))
|
||||
(when-let [locked-until (client-locked-lookup (:client_code entry))]
|
||||
(when (and (not (t/after? (coerce/to-date-time (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry))))
|
||||
(coerce/to-date-time locked-until)))
|
||||
(not (t/equal? (coerce/to-date-time (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry))))
|
||||
(coerce/to-date-time locked-until))))
|
||||
(throw (ex-info (str "Client's data is locked until " locked-until) {:status :error}))))
|
||||
|
||||
(when-not (dollars= (reduce (fnil + 0.0 0.0) 0.0 (map :debit (:line_items entry)))
|
||||
(reduce (fnil + 0.0 0.0) 0.0 (map :credit (:line_items entry))))
|
||||
(throw (ex-info (str "Debits '"
|
||||
(reduce (fnil + 0.0 0.0) 0 (map :debit (:line_items entry)))
|
||||
"' and credits '"
|
||||
(reduce (fnil + 0.0 0.0) 0 (map :credit (:line_items entry)))
|
||||
"' do not add up.")
|
||||
{:status :error})))
|
||||
(when (dollars= (reduce (fnil + 0.0 0.0) 0.0 (map :debit (:line_items entry)))
|
||||
0.0)
|
||||
(throw (ex-info (str "Cannot have ledger entries that total $0.00")
|
||||
{:status :ignored})))
|
||||
(assoc entry
|
||||
:status :success
|
||||
:tx
|
||||
[:upsert-ledger
|
||||
(remove-nils
|
||||
{:journal-entry/source (:source entry)
|
||||
:journal-entry/client [:client/code (:client_code entry)]
|
||||
:journal-entry/date (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry)))
|
||||
:journal-entry/external-id (:external_id entry)
|
||||
:journal-entry/vendor (:db/id (all-vendors (:vendor_name entry)))
|
||||
:journal-entry/amount (:amount entry)
|
||||
:journal-entry/note (:note entry)
|
||||
:journal-entry/cleared-against (:cleared_against entry)
|
||||
[:count (count (:entries args))]
|
||||
(doall (map
|
||||
(assoc-error (fn [entry]
|
||||
(let [vendor (all-vendors (:vendor_name entry))]
|
||||
(when-not (client-locked-lookup (:client_code entry))
|
||||
(throw (ex-info (str "Client '" (:client_code entry) "' not found.") {:status :error})))
|
||||
(when-not vendor
|
||||
(throw (ex-info (str "Vendor '" (:vendor_name entry) "' not found.") {:status :error})))
|
||||
(when-not (re-find #"\d{1,2}/\d{1,2}/\d{4}" (:date entry))
|
||||
(throw (ex-info (str "Date must be MM/dd/yyyy") {:status :error})))
|
||||
(when-let [locked-until (client-locked-lookup (:client_code entry))]
|
||||
(when (and (not (t/after? (coerce/to-date-time (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry))))
|
||||
(coerce/to-date-time locked-until)))
|
||||
(not (t/equal? (coerce/to-date-time (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry))))
|
||||
(coerce/to-date-time locked-until))))
|
||||
(throw (ex-info (str "Client's data is locked until " locked-until) {:status :error}))))
|
||||
|
||||
:journal-entry/line-items
|
||||
(mapv (fn [ea]
|
||||
(let [debit (or (:debit ea) 0.0)
|
||||
credit (or (:credit ea) 0.0)]
|
||||
(when (and (not (get
|
||||
(get all-client-locations (:client_code entry))
|
||||
(:location ea)))
|
||||
(not= "A" (:location ea)))
|
||||
(throw (ex-info (str "Location '" (:location ea) "' not found.")
|
||||
{:status :error})))
|
||||
(when (and (<= debit 0.0)
|
||||
(<= credit 0.0))
|
||||
(throw (ex-info (str "Line item amount " (or debit credit) " must be greater than 0.")
|
||||
{:status :error})))
|
||||
(when (and (not (all-accounts (:account_identifier ea)))
|
||||
(not (get
|
||||
(get all-client-bank-accounts (:client_code entry))
|
||||
(:account_identifier ea))))
|
||||
(throw (ex-info (str "Account '" (:account_identifier ea) "' not found.")
|
||||
{:status :error})))
|
||||
(let [matching-account (when (re-matches #"^[0-9]+$" (:account_identifier ea))
|
||||
(a/get-account-by-numeric-code-and-sets (Integer/parseInt (:account_identifier ea)) ["default"]))]
|
||||
(when (and matching-account
|
||||
(:account/location matching-account)
|
||||
(not= (:account/location matching-account)
|
||||
(:location ea)))
|
||||
(throw (ex-info (str "Account '"
|
||||
(:account/numeric-code matching-account)
|
||||
"' requires location '"
|
||||
(:account/location matching-account)
|
||||
"' but got '"
|
||||
(:location ea)
|
||||
"'")
|
||||
{:status :error})))
|
||||
(when (and matching-account
|
||||
(not (:account/location matching-account))
|
||||
(= "A" (:location ea)))
|
||||
(throw (ex-info (str "Account '"
|
||||
(:account/numeric-code matching-account)
|
||||
"' cannot use location '"
|
||||
(:location ea)
|
||||
"'")
|
||||
{:status :error})))
|
||||
(remove-nils (cond-> {:db/id (random-tempid)
|
||||
:journal-entry-line/location (:location ea)
|
||||
:journal-entry-line/debit (when (> debit 0)
|
||||
debit)
|
||||
:journal-entry-line/credit (when (> credit 0)
|
||||
credit)}
|
||||
matching-account (assoc :journal-entry-line/account (:db/id matching-account))
|
||||
(not matching-account) (assoc :journal-entry-line/account [:bank-account/code (:account_identifier ea)]))))))
|
||||
(:line_items entry))
|
||||
|
||||
:journal-entry/cleared true})]))))
|
||||
(:entries args))))
|
||||
(when-not (dollars= (reduce (fnil + 0.0 0.0) 0.0 (map :debit (:line_items entry)))
|
||||
(reduce (fnil + 0.0 0.0) 0.0 (map :credit (:line_items entry))))
|
||||
(throw (ex-info (str "Debits '"
|
||||
(reduce (fnil + 0.0 0.0) 0 (map :debit (:line_items entry)))
|
||||
"' and credits '"
|
||||
(reduce (fnil + 0.0 0.0) 0 (map :credit (:line_items entry)))
|
||||
"' do not add up.")
|
||||
{:status :error})))
|
||||
(when (dollars= (reduce (fnil + 0.0 0.0) 0.0 (map :debit (:line_items entry)))
|
||||
0.0)
|
||||
(throw (ex-info (str "Cannot have ledger entries that total $0.00")
|
||||
{:status :ignored})))
|
||||
(assoc entry
|
||||
:status :success
|
||||
:tx
|
||||
[:upsert-ledger
|
||||
(remove-nils
|
||||
{:journal-entry/source (:source entry)
|
||||
:journal-entry/client [:client/code (:client_code entry)]
|
||||
:journal-entry/date (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry)))
|
||||
:journal-entry/external-id (:external_id entry)
|
||||
:journal-entry/vendor (:db/id (all-vendors (:vendor_name entry)))
|
||||
:journal-entry/amount (:amount entry)
|
||||
:journal-entry/note (:note entry)
|
||||
:journal-entry/cleared-against (:cleared_against entry)
|
||||
|
||||
:journal-entry/line-items
|
||||
(mapv (fn [ea]
|
||||
(let [debit (or (:debit ea) 0.0)
|
||||
credit (or (:credit ea) 0.0)]
|
||||
(when (and (not (get
|
||||
(get all-client-locations (:client_code entry))
|
||||
(:location ea)))
|
||||
(not= "A" (:location ea)))
|
||||
(throw (ex-info (str "Location '" (:location ea) "' not found.")
|
||||
{:status :error})))
|
||||
(when (and (<= debit 0.0)
|
||||
(<= credit 0.0))
|
||||
(throw (ex-info (str "Line item amount " (or debit credit) " must be greater than 0.")
|
||||
{:status :error})))
|
||||
(when (and (not (all-accounts (:account_identifier ea)))
|
||||
(not (get
|
||||
(get all-client-bank-accounts (:client_code entry))
|
||||
(:account_identifier ea))))
|
||||
(throw (ex-info (str "Account '" (:account_identifier ea) "' not found.")
|
||||
{:status :error})))
|
||||
(let [matching-account (when (re-matches #"^[0-9]+$" (:account_identifier ea))
|
||||
(a/get-account-by-numeric-code-and-sets (Integer/parseInt (:account_identifier ea)) ["default"]))]
|
||||
(when (and matching-account
|
||||
(:account/location matching-account)
|
||||
(not= (:account/location matching-account)
|
||||
(:location ea)))
|
||||
(throw (ex-info (str "Account '"
|
||||
(:account/numeric-code matching-account)
|
||||
"' requires location '"
|
||||
(:account/location matching-account)
|
||||
"' but got '"
|
||||
(:location ea)
|
||||
"'")
|
||||
{:status :error})))
|
||||
(when (and matching-account
|
||||
(not (:account/location matching-account))
|
||||
(= "A" (:location ea)))
|
||||
(throw (ex-info (str "Account '"
|
||||
(:account/numeric-code matching-account)
|
||||
"' cannot use location '"
|
||||
(:location ea)
|
||||
"'")
|
||||
{:status :error})))
|
||||
(remove-nils (cond-> {:db/id (random-tempid)
|
||||
:journal-entry-line/location (:location ea)
|
||||
:journal-entry-line/debit (when (> debit 0)
|
||||
debit)
|
||||
:journal-entry-line/credit (when (> credit 0)
|
||||
credit)}
|
||||
matching-account (assoc :journal-entry-line/account (:db/id matching-account))
|
||||
(not matching-account) (assoc :journal-entry-line/account [:bank-account/code (:account_identifier ea)]))))))
|
||||
(:line_items entry))
|
||||
|
||||
:journal-entry/cleared true})]))))
|
||||
(:entries args))))
|
||||
errors (filter #(= (:status %) :error) transaction)
|
||||
ignored (filter #(= (:status %) :ignored) transaction)
|
||||
success (filter #(= (:status %) :success) transaction)
|
||||
retraction (mapv (fn [x] [:db/retractEntity [:journal-entry/external-id (:external_id x)]])
|
||||
success)
|
||||
ignore-retraction (->> ignored
|
||||
(map :external_id )
|
||||
(map :external_id)
|
||||
(dc/q '[:find ?je
|
||||
:in $ [?ei ...]
|
||||
:where [?je :journal-entry/external-id ?ei]]
|
||||
(dc/db conn)
|
||||
)
|
||||
:in $ [?ei ...]
|
||||
:where [?je :journal-entry/external-id ?ei]]
|
||||
(dc/db conn))
|
||||
(map first)
|
||||
(map (fn [je] [:db/retractEntity je])))]
|
||||
(alog/info ::manual-import
|
||||
:errors (count errors)
|
||||
:sample (take 3 errors))
|
||||
|
||||
|
||||
(mu/trace ::retraction-tx
|
||||
[:count (count retraction)]
|
||||
(audit-transact-batch retraction (:id context)))
|
||||
[:count (count retraction)]
|
||||
(audit-transact-batch retraction (:id context)))
|
||||
(mu/trace ::ignore-retraction-tx
|
||||
[:count (count ignore-retraction)]
|
||||
(when (seq ignore-retraction)
|
||||
(audit-transact-batch ignore-retraction (:id context))))
|
||||
(let [invalidated
|
||||
[:count (count ignore-retraction)]
|
||||
(when (seq ignore-retraction)
|
||||
(audit-transact-batch ignore-retraction (:id context))))
|
||||
(let [invalidated
|
||||
(mu/trace ::success-tx
|
||||
[:count (count success)]
|
||||
(for [[_ n] (:tempids (audit-transact-batch (map :tx success) (:id context)))]
|
||||
@@ -573,7 +559,7 @@
|
||||
[:count (count invalidated)]
|
||||
(doseq [n invalidated]
|
||||
(solr/touch n)))))
|
||||
|
||||
|
||||
{:successful (map (fn [x] {:external_id (:external_id x)}) success)
|
||||
:ignored (map (fn [x]
|
||||
{:external_id (:external_id x)})
|
||||
@@ -582,7 +568,6 @@
|
||||
:errors (map (fn [x] {:external_id (:external_id x)
|
||||
:error (:error x)}) errors)}))
|
||||
|
||||
|
||||
(defn get-journal-detail-report [context input _]
|
||||
(let [category-totals (atom {})
|
||||
base-categories (into []
|
||||
@@ -597,20 +582,19 @@
|
||||
:clients [{:db/id client-id}])
|
||||
{:filters {:location location
|
||||
:date_range (:date_range input)
|
||||
:from_numeric_code (l-reports/min-numeric-code category )
|
||||
:to_numeric_code (l-reports/max-numeric-code category )
|
||||
:from_numeric_code (l-reports/min-numeric-code category)
|
||||
:to_numeric_code (l-reports/max-numeric-code category)
|
||||
:per_page Integer/MAX_VALUE}}
|
||||
nil)
|
||||
:journal_entries
|
||||
(mapcat (fn [je]
|
||||
(->> (je :line_items)
|
||||
(filter (fn [jel]
|
||||
(when-let [account (account-lookup (:id (:account jel)))]
|
||||
(and
|
||||
(l-reports/account-belongs-in-category? (:numeric_code account) category)
|
||||
(= location (:location jel)))))
|
||||
)
|
||||
(map (fn [jel ]
|
||||
(when-let [account (account-lookup (:id (:account jel)))]
|
||||
(and
|
||||
(l-reports/account-belongs-in-category? (:numeric_code account) category)
|
||||
(= location (:location jel))))))
|
||||
(map (fn [jel]
|
||||
{:date (:date je)
|
||||
:debit (:debit jel)
|
||||
:credit (:credit jel)
|
||||
@@ -621,18 +605,18 @@
|
||||
(into []))
|
||||
_ (swap! category-totals assoc-in [client-id location category]
|
||||
(- (or (reduce + 0.0 (map #(or (:credit %) 0.0) all-journal-entries)) 0.0)
|
||||
(or (reduce + 0.0 (map #(or (:debit %) 0.0) all-journal-entries)) 0.0)) )
|
||||
(or (reduce + 0.0 (map #(or (:debit %) 0.0) all-journal-entries)) 0.0)))
|
||||
journal-entries-by-account (group-by #(account-lookup (get-in % [:account :id])) all-journal-entries)]
|
||||
[account journal-entries] (conj (vec journal-entries-by-account) [nil all-journal-entries])
|
||||
:let [journal-entries (first (reduce
|
||||
(fn [[acc last-je] je]
|
||||
(let [next-je (assoc je :running_balance
|
||||
(- (+ (or (:running_balance last-je 0.0) 0.0)
|
||||
(or (:credit je 0.0) 0.0))
|
||||
(or (:debit je 0.0) 0.0)))]
|
||||
[(conj acc next-je) next-je]))
|
||||
[]
|
||||
(sort-by :date journal-entries)))]]
|
||||
(fn [[acc last-je] je]
|
||||
(let [next-je (assoc je :running_balance
|
||||
(- (+ (or (:running_balance last-je 0.0) 0.0)
|
||||
(or (:credit je 0.0) 0.0))
|
||||
(or (:debit je 0.0) 0.0)))]
|
||||
[(conj acc next-je) next-je]))
|
||||
[]
|
||||
(sort-by :date journal-entries)))]]
|
||||
{:category (->graphql category)
|
||||
:client_id client-id
|
||||
:location location
|
||||
@@ -641,7 +625,7 @@
|
||||
:journal_entries (when account journal-entries)
|
||||
:total (- (or (reduce + 0.0 (map #(or (:credit %) 0.0) journal-entries)) 0.0)
|
||||
(or (reduce + 0.0 (map #(or (:debit %) 0.0) journal-entries)) 0.0))}))
|
||||
result {:categories
|
||||
result {:categories
|
||||
(into base-categories
|
||||
(for [client-id (:client_ids input)
|
||||
:let [_ (assert-can-see-client (:id context) client-id)
|
||||
@@ -675,15 +659,12 @@
|
||||
line))}]
|
||||
result))
|
||||
|
||||
|
||||
|
||||
(defn journal-detail-report-pdf [context args value]
|
||||
(let [data (get-journal-detail-report context args value)
|
||||
result (print-journal-detail-report (:id context) args data)]
|
||||
|
||||
(->graphql result)))
|
||||
|
||||
|
||||
(def objects
|
||||
{:balance_sheet_account
|
||||
{:fields {:id {:type 'String}
|
||||
@@ -847,7 +828,7 @@
|
||||
(def input-objects
|
||||
{:numeric_code_range
|
||||
{:fields {:from {:type 'Int}
|
||||
:to {:type 'Int}}}
|
||||
:to {:type 'Int}}}
|
||||
:ledger_filters
|
||||
{:fields {:client_id {:type :id}
|
||||
:vendor_id {:type :id}
|
||||
@@ -874,25 +855,23 @@
|
||||
:credit {:type :money}}}
|
||||
:import_ledger_entry
|
||||
{:fields {:source {:type 'String}
|
||||
:external_id {:type 'String}
|
||||
:external_id {:type 'String}
|
||||
:client_code {:type 'String}
|
||||
:date {:type 'String}
|
||||
:vendor_name {:type 'String}
|
||||
:amount {:type :money}
|
||||
:note {:type 'String}
|
||||
:cleared_against {:type 'String}
|
||||
:line_items {:type '(list :import_ledger_line_item)}}}
|
||||
})
|
||||
:line_items {:type '(list :import_ledger_line_item)}}}})
|
||||
|
||||
(def enums
|
||||
{:ledger_category {:values [{:enum-value :sales}
|
||||
{:enum-value :cogs}
|
||||
{:enum-value :payroll}
|
||||
{:enum-value :cogs}
|
||||
{:enum-value :payroll}
|
||||
{:enum-value :controllable}
|
||||
{:enum-value :fixed_overhead}
|
||||
{:enum-value :ownership_controllable}]}})
|
||||
|
||||
|
||||
(def resolvers
|
||||
{:get-ledger-page get-ledger-page
|
||||
:get-balance-sheet get-balance-sheet
|
||||
|
||||
@@ -21,13 +21,11 @@
|
||||
:name (first name)}))
|
||||
[]))
|
||||
|
||||
|
||||
|
||||
(defn attach [schema]
|
||||
(->
|
||||
(->
|
||||
(merge-with merge schema
|
||||
{:objects {:plaid_link_result
|
||||
{:fields {:token {:type 'String}} }
|
||||
{:fields {:token {:type 'String}}}
|
||||
|
||||
:plaid_item
|
||||
{:fields {:external_id {:type 'String}
|
||||
@@ -50,7 +48,7 @@
|
||||
:name {:type 'String}
|
||||
:number {:type 'String}}}}
|
||||
:queries {:search_plaid_merchants {:type '(list :plaid_merchant)
|
||||
:args {:query {:type 'String}}
|
||||
:resolve :search-plaid-merchants}}})
|
||||
:args {:query {:type 'String}}
|
||||
:resolve :search-plaid-merchants}}})
|
||||
(attach-tracing-resolvers {:search-plaid-merchants search-merchants})))
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(ns auto-ap.graphql.sales-orders
|
||||
(:require [auto-ap.datomic.sales-orders :as d-sales-orders2]
|
||||
[auto-ap.graphql.utils :refer [->graphql <-graphql result->page assert-admin] ]
|
||||
[auto-ap.graphql.utils :refer [->graphql <-graphql result->page assert-admin]]
|
||||
[com.walmartlabs.lacinia.util :refer [attach-resolvers]]
|
||||
[auto-ap.graphql.utils :refer [attach-tracing-resolvers]]))
|
||||
|
||||
@@ -14,19 +14,18 @@
|
||||
(defn get-all-sales-orders [context args _]
|
||||
(assert-admin (:id context))
|
||||
(map
|
||||
->graphql
|
||||
(first (d-sales-orders2/get-graphql (assoc (<-graphql args) :count Integer/MAX_VALUE)))))
|
||||
|
||||
->graphql
|
||||
(first (d-sales-orders2/get-graphql (assoc (<-graphql args) :count Integer/MAX_VALUE)))))
|
||||
|
||||
(def objects
|
||||
{:sales_order_page
|
||||
{:fields {:sales_orders {:type '(list :sales_order)}
|
||||
:count {:type 'Int}
|
||||
:total {:type 'Int}
|
||||
:start {:type 'Int}
|
||||
:end {:type 'Int}
|
||||
:sales_order_total {:type :money}
|
||||
:sales_order_tax {:type :money}}}
|
||||
:count {:type 'Int}
|
||||
:total {:type 'Int}
|
||||
:start {:type 'Int}
|
||||
:end {:type 'Int}
|
||||
:sales_order_total {:type :money}
|
||||
:sales_order_tax {:type :money}}}
|
||||
|
||||
:sales_order
|
||||
{:fields {:id {:type :id}
|
||||
@@ -93,8 +92,7 @@
|
||||
|
||||
(def resolvers
|
||||
{:get-all-sales-orders get-all-sales-orders
|
||||
:get-sales-order-page get-sales-orders-page
|
||||
})
|
||||
:get-sales-order-page get-sales-orders-page})
|
||||
|
||||
(defn attach [schema]
|
||||
(->
|
||||
|
||||
@@ -29,9 +29,8 @@
|
||||
(defn get-transaction-rule-matches [context args _]
|
||||
(if (= "admin" (:user/role (:id context)))
|
||||
(let [transaction (update (d-transactions/get-by-id (:transaction_id args)) :transaction/date c/to-date)
|
||||
all-rules (tr/get-all-for-client (:db/id (:transaction/client transaction)))
|
||||
all-rules (tr/get-all-for-client (:db/id (:transaction/client transaction)))]
|
||||
|
||||
]
|
||||
(mu/log ::counted
|
||||
:count (count all-rules))
|
||||
(doto (map ->graphql (rm/get-matching-rules transaction all-rules)) (#(println (count %)))))
|
||||
@@ -43,7 +42,7 @@
|
||||
:account account_id
|
||||
:location location})
|
||||
|
||||
(defn delete-transaction-rule [context {:keys [transaction_rule_id ]} _]
|
||||
(defn delete-transaction-rule [context {:keys [transaction_rule_id]} _]
|
||||
(assert-admin (:id context))
|
||||
(let [existing-transaction-rule (tr/get-by-id transaction_rule_id)]
|
||||
(when-not (:transaction-rule/description existing-transaction-rule)
|
||||
@@ -59,62 +58,59 @@
|
||||
(. java.util.regex.Pattern (compile description java.util.regex.Pattern/CASE_INSENSITIVE))
|
||||
(catch Exception e
|
||||
(throw (ex-info (ex-message e) {:validation-error (ex-message e)}))))
|
||||
_ (when-not (dollars= 1.0 account-total)
|
||||
_ (when-not (dollars= 1.0 account-total)
|
||||
(let [error (str "Account total (" account-total ") does not reach 100%")]
|
||||
(throw (ex-info error {:validation-error error}))))
|
||||
_ (when (and (str/blank? description)
|
||||
(nil? yodlee_merchant_id))
|
||||
(nil? yodlee_merchant_id))
|
||||
(let [error (str "You must provide a description or a yodlee merchant")]
|
||||
(throw (ex-info error {:validation-error error}))))
|
||||
_ (doseq [a accounts
|
||||
:let [{:keys [:account/location :account/name]} (dc/pull (dc/db conn) [:account/location :account/name] (:account_id a))
|
||||
client (dc/pull (dc/db conn) [:client/locations] client_id)
|
||||
]]
|
||||
client (dc/pull (dc/db conn) [:client/locations] client_id)]]
|
||||
(when (and location (not= location (:location a)))
|
||||
(let [err (str "Account " name " uses location " (:location a) ", but is supposed to be " location)]
|
||||
(throw (ex-info err {:validation-error err}) )))
|
||||
|
||||
(throw (ex-info err {:validation-error err}))))
|
||||
|
||||
(when (and (not location)
|
||||
(not (get (into #{"Shared"} (:client/locations client))
|
||||
(:location a))))
|
||||
(let [err (str "Account " name " uses location " (:location a) ", but doesn't belong to the client.")]
|
||||
(throw (ex-info err {:validation-error err}) ))))
|
||||
(throw (ex-info err {:validation-error err})))))
|
||||
rule-id (if id
|
||||
id
|
||||
"transaction-rule")
|
||||
transaction [[:upsert-entity #:transaction-rule {:db/id (or rule-id (random-tempid))
|
||||
:description description
|
||||
:note note
|
||||
:client client_id
|
||||
:bank-account bank_account_id
|
||||
:yodlee-merchant yodlee_merchant_id
|
||||
:dom-lte dom_lte
|
||||
:dom-gte dom_gte
|
||||
:amount-lte amount_lte
|
||||
:amount-gte amount_gte
|
||||
:vendor vendor_id
|
||||
:transaction-approval-status
|
||||
(some->> transaction_approval_status
|
||||
name
|
||||
snake->kebab
|
||||
(keyword "transaction-approval-status"))
|
||||
:transaction-rule/accounts (map transaction-rule-account->entity accounts)}]]
|
||||
|
||||
:description description
|
||||
:note note
|
||||
:client client_id
|
||||
:bank-account bank_account_id
|
||||
:yodlee-merchant yodlee_merchant_id
|
||||
:dom-lte dom_lte
|
||||
:dom-gte dom_gte
|
||||
:amount-lte amount_lte
|
||||
:amount-gte amount_gte
|
||||
:vendor vendor_id
|
||||
:transaction-approval-status
|
||||
(some->> transaction_approval_status
|
||||
name
|
||||
snake->kebab
|
||||
(keyword "transaction-approval-status"))
|
||||
:transaction-rule/accounts (map transaction-rule-account->entity accounts)}]]
|
||||
|
||||
transaction-result (audit-transact transaction (:id context))]
|
||||
(-> (tr/get-by-id (or (-> transaction-result :tempids (get "transaction-rule"))
|
||||
id))
|
||||
id))
|
||||
((ident->enum-f :transaction-rule/transaction-approval-status))
|
||||
(->graphql))))
|
||||
|
||||
(defn -test-transaction-rule [id {:keys [:transaction-rule/description :transaction-rule/client :transaction-rule/bank-account :transaction-rule/amount-lte :transaction-rule/amount-gte :transaction-rule/dom-lte :transaction-rule/dom-gte :transaction-rule/yodlee-merchant]} include-coded? count]
|
||||
(let [query (cond-> {:query {:find ['(pull ?e [* {:transaction/client [:client/name]
|
||||
:transaction/bank-account [:bank-account/name]
|
||||
:transaction/payment [:db/id]}
|
||||
])]
|
||||
:in ['$ ]
|
||||
:transaction/payment [:db/id]}])]
|
||||
:in ['$]
|
||||
:where []}
|
||||
:args [(dc/db conn)]}
|
||||
:args [(dc/db conn)]}
|
||||
description
|
||||
(merge-query {:query {:in ['?descr]
|
||||
:where ['[(iol-ion.query/->pattern ?descr) ?description-regex]]}
|
||||
@@ -170,23 +166,22 @@
|
||||
:where ['[?e :transaction/client ?client-id]]}
|
||||
:args [(:db/id client)]})
|
||||
|
||||
|
||||
(not include-coded?)
|
||||
(merge-query {:query {:where ['[or [?e :transaction/approval-status :transaction-approval-status/unapproved]
|
||||
[(missing? $ ?e :transaction/approval-status)]]]}})
|
||||
|
||||
true
|
||||
(merge-query {:query {:where ['[?e :transaction/id]]}}))]
|
||||
(->>
|
||||
(query2 query)
|
||||
(transduce (comp
|
||||
(take (or count 15))
|
||||
(map first)
|
||||
(map #(dissoc % :transaction/id))
|
||||
(map (fn [x]
|
||||
(update x :transaction/date c/from-date)))
|
||||
(map ->graphql))
|
||||
conj []))))
|
||||
(->>
|
||||
(query2 query)
|
||||
(transduce (comp
|
||||
(take (or count 15))
|
||||
(map first)
|
||||
(map #(dissoc % :transaction/id))
|
||||
(map (fn [x]
|
||||
(update x :transaction/date c/from-date)))
|
||||
(map ->graphql))
|
||||
conj []))))
|
||||
|
||||
(defn test-transaction-rule [{:keys [id]} {{:keys [description client_id bank_account_id amount_lte amount_gte dom_lte dom_gte yodlee_merchant_id]} :transaction_rule} _]
|
||||
(assert-admin id)
|
||||
@@ -200,7 +195,6 @@
|
||||
:yodlee-merchant (when yodlee_merchant_id {:db/id yodlee_merchant_id})}
|
||||
true 15))
|
||||
|
||||
|
||||
(defn run-transaction-rule [{:keys [id]} {:keys [transaction_rule_id count]} _]
|
||||
(assert-admin id)
|
||||
(-test-transaction-rule id (tr/get-by-id transaction_rule_id) false count))
|
||||
|
||||
@@ -66,14 +66,14 @@
|
||||
|
||||
(defn get-ids-matching-filters [args]
|
||||
(alog/info ::getting-ids-matching-filters
|
||||
:args args)
|
||||
:args args)
|
||||
(let [ids (some-> (:filters args)
|
||||
(assoc :clients (:clients args))
|
||||
(assoc :id (:id args))
|
||||
(<-graphql)
|
||||
(update :approval-status enum->keyword "transaction-approval-status")
|
||||
(assoc :per-page Integer/MAX_VALUE)
|
||||
(d-transactions/raw-graphql-ids )
|
||||
(d-transactions/raw-graphql-ids)
|
||||
:ids)
|
||||
specific-ids (d-transactions/filter-ids (seq (:ids args)))]
|
||||
(if (seq (:ids args))
|
||||
@@ -83,13 +83,13 @@
|
||||
(defn all-ids-not-locked [all-ids]
|
||||
(->> all-ids
|
||||
(dc/q '[:find ?t
|
||||
:in $ [?t ...]
|
||||
:where
|
||||
[?t :transaction/client ?c]
|
||||
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
|
||||
[?t :transaction/date ?d]
|
||||
[(>= ?d ?lu)]]
|
||||
(dc/db conn))
|
||||
:in $ [?t ...]
|
||||
:where
|
||||
[?t :transaction/client ?c]
|
||||
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
|
||||
[?t :transaction/date ?d]
|
||||
[(>= ?d ?lu)]]
|
||||
(dc/db conn))
|
||||
(map first)))
|
||||
(defn bulk-change-status [context args _]
|
||||
(let [_ (assert-admin (:id context))
|
||||
@@ -98,47 +98,46 @@
|
||||
all-ids-not-locked)]
|
||||
|
||||
(alog/info ::bulk-change-status
|
||||
:count (count all-ids)
|
||||
:sample (take 3 all-ids)
|
||||
:status (:status args)
|
||||
)
|
||||
:count (count all-ids)
|
||||
:sample (take 3 all-ids)
|
||||
:status (:status args))
|
||||
(audit-transact-batch
|
||||
(->> all-ids
|
||||
(mapv (fn [t]
|
||||
[:upsert-transaction {:db/id t
|
||||
:transaction/approval-status (enum->keyword (:status args) "transaction-approval-status")}])))
|
||||
|
||||
|
||||
(:id context))
|
||||
{:message (str "Succesfully changed " (count all-ids) " transactions to be " (name (:status args) ) ".")}))
|
||||
{:message (str "Succesfully changed " (count all-ids) " transactions to be " (name (:status args)) ".")}))
|
||||
|
||||
;; TODO very similar to rule-matching
|
||||
(defn maybe-code-accounts [transaction account-rules valid-locations]
|
||||
(with-precision 2
|
||||
(let [accounts (vec (mapcat
|
||||
(fn [ar]
|
||||
(let [cents-to-distribute (int (Math/round (Math/abs (* (:percentage ar)
|
||||
(:transaction/amount transaction)
|
||||
100))))]
|
||||
(if (= "Shared" (:location ar))
|
||||
(->> valid-locations
|
||||
(map
|
||||
(fn [cents location]
|
||||
{:db/id (random-tempid)
|
||||
:transaction-account/account (:account_id ar)
|
||||
:transaction-account/amount (* 0.01 cents)
|
||||
:transaction-account/location location})
|
||||
(rm/spread-cents cents-to-distribute (count valid-locations))))
|
||||
[(cond-> {:db/id (random-tempid)
|
||||
:transaction-account/account (:account_id ar)
|
||||
:transaction-account/amount (* 0.01 cents-to-distribute)}
|
||||
(:location ar) (assoc :transaction-account/location (:location ar)))])))
|
||||
account-rules))
|
||||
(fn [ar]
|
||||
(let [cents-to-distribute (int (Math/round (Math/abs (* (:percentage ar)
|
||||
(:transaction/amount transaction)
|
||||
100))))]
|
||||
(if (= "Shared" (:location ar))
|
||||
(->> valid-locations
|
||||
(map
|
||||
(fn [cents location]
|
||||
{:db/id (random-tempid)
|
||||
:transaction-account/account (:account_id ar)
|
||||
:transaction-account/amount (* 0.01 cents)
|
||||
:transaction-account/location location})
|
||||
(rm/spread-cents cents-to-distribute (count valid-locations))))
|
||||
[(cond-> {:db/id (random-tempid)
|
||||
:transaction-account/account (:account_id ar)
|
||||
:transaction-account/amount (* 0.01 cents-to-distribute)}
|
||||
(:location ar) (assoc :transaction-account/location (:location ar)))])))
|
||||
account-rules))
|
||||
accounts (mapv
|
||||
(fn [a]
|
||||
(update a :transaction-account/amount
|
||||
#(with-precision 2
|
||||
(double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP)))))
|
||||
accounts)
|
||||
(fn [a]
|
||||
(update a :transaction-account/amount
|
||||
#(with-precision 2
|
||||
(double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP)))))
|
||||
accounts)
|
||||
leftover (with-precision 2 (.round (bigdec (- (Math/abs (:transaction/amount transaction))
|
||||
(Math/abs (reduce + 0.0 (map #(:transaction-account/amount %) accounts)))))
|
||||
*math-context*))
|
||||
@@ -152,13 +151,13 @@
|
||||
(when-not (seq (:clients context))
|
||||
(throw (ex-info "Client is required"
|
||||
{:validation-error "Client is required"})))
|
||||
(let [args (assoc args :clients (:clients context) :id (:id context))
|
||||
(let [args (assoc args :clients (:clients context) :id (:id context))
|
||||
client->locations (->> (:clients context)
|
||||
(map :db/id )
|
||||
(map :db/id)
|
||||
(dc/q
|
||||
'[:find (pull ?e [:db/id :client/locations])
|
||||
:in $ [?e ...]]
|
||||
(dc/db conn))
|
||||
'[:find (pull ?e [:db/id :client/locations])
|
||||
:in $ [?e ...]]
|
||||
(dc/db conn))
|
||||
(map (fn [[client]]
|
||||
[(:db/id client) (:client/locations client)]))
|
||||
(into {}))
|
||||
@@ -166,41 +165,40 @@
|
||||
transactions (pull-many (dc/db conn) [:db/id :transaction/amount {:transaction/client [:db/id]}] (vec all-ids))
|
||||
account-total (reduce + 0 (map (fn [x] (:percentage x)) (:accounts args)))]
|
||||
(alog/info ::bulk-coding-transactions
|
||||
:count (count transactions)
|
||||
:sample (take 3 transactions))
|
||||
:count (count transactions)
|
||||
:sample (take 3 transactions))
|
||||
(when
|
||||
(and
|
||||
(seq (:accounts args))
|
||||
(not (dollars= 1.0 account-total)))
|
||||
(let [error (str "Account total (" account-total ") does not reach 100%")]
|
||||
(throw (ex-info error {:validation-error error}))))
|
||||
(and
|
||||
(seq (:accounts args))
|
||||
(not (dollars= 1.0 account-total)))
|
||||
(let [error (str "Account total (" account-total ") does not reach 100%")]
|
||||
(throw (ex-info error {:validation-error error}))))
|
||||
(doseq [a (:accounts args)
|
||||
:let [{:keys [:account/location :account/name]} (dc/pull (dc/db conn)
|
||||
[:account/location :account/name]
|
||||
(:account_id a))]]
|
||||
(when (and location (not= location (:location a)))
|
||||
(let [err (str "Account " name " uses location " (:location a) ", but is supposed to be " location)]
|
||||
(throw (ex-info err {:validation-error err}) )))
|
||||
(throw (ex-info err {:validation-error err}))))
|
||||
(doseq [[_ locations] client->locations]
|
||||
(when (and (not location)
|
||||
(not (get (into #{"Shared"} locations)
|
||||
(:location a))))
|
||||
(let [err (str "Account " name " uses location " (:location a) ", but doesn't belong to the client.")]
|
||||
(throw (ex-info err {:validation-error err}) )))))
|
||||
(throw (ex-info err {:validation-error err}))))))
|
||||
(audit-transact-batch
|
||||
(map (fn [t]
|
||||
(let [locations (client->locations (-> t :transaction/client :db/id))]
|
||||
(doto
|
||||
[:upsert-transaction (cond-> t
|
||||
(:approval_status args) (assoc :transaction/approval-status (enum->keyword (:approval_status args) "transaction-approval-status"))
|
||||
(:vendor args) (assoc :transaction/vendor (:vendor args))
|
||||
(seq (:accounts args)) (assoc :transaction/accounts (maybe-code-accounts t (:accounts args) locations)))]
|
||||
clojure.pprint/pprint)))
|
||||
transactions)
|
||||
(:id context))
|
||||
(map (fn [t]
|
||||
(let [locations (client->locations (-> t :transaction/client :db/id))]
|
||||
(doto
|
||||
[:upsert-transaction (cond-> t
|
||||
(:approval_status args) (assoc :transaction/approval-status (enum->keyword (:approval_status args) "transaction-approval-status"))
|
||||
(:vendor args) (assoc :transaction/vendor (:vendor args))
|
||||
(seq (:accounts args)) (assoc :transaction/accounts (maybe-code-accounts t (:accounts args) locations)))]
|
||||
clojure.pprint/pprint)))
|
||||
transactions)
|
||||
(:id context))
|
||||
{:message (str "Successfully coded " (count all-ids) " transactions.")}))
|
||||
|
||||
|
||||
(defn delete-transactions [context args _]
|
||||
(let [_ (assert-admin (:id context))
|
||||
args (assoc args :clients (:clients context))
|
||||
@@ -208,24 +206,24 @@
|
||||
db (dc/db conn)]
|
||||
|
||||
(alog/info ::bulk-delete-transactions
|
||||
:count (count all-ids)
|
||||
:sample (take 3 all-ids))
|
||||
:count (count all-ids)
|
||||
:sample (take 3 all-ids))
|
||||
(audit-transact-batch
|
||||
(mapcat (fn [i]
|
||||
(let [transaction (dc/pull db [:transaction/payment
|
||||
:transaction/expected-deposit
|
||||
:db/id] i)
|
||||
payment-id (-> transaction :transaction/payment :db/id)
|
||||
expected-deposit-id (-> transaction :transaction/expected-deposit :db/id)]
|
||||
(cond->> [[:db/retractEntity [:journal-entry/original-entity i]]]
|
||||
payment-id (into [{:db/id payment-id
|
||||
:payment/status :payment-status/pending}
|
||||
[:db/retract (:db/id transaction) :transaction/payment payment-id]])
|
||||
expected-deposit-id (into [{:db/id expected-deposit-id
|
||||
:expected-deposit/status :expected-deposit-status/pending}
|
||||
[:db/retract (:db/id transaction) :transaction/expected-deposit expected-deposit-id]]))))
|
||||
all-ids)
|
||||
(:id context))
|
||||
(mapcat (fn [i]
|
||||
(let [transaction (dc/pull db [:transaction/payment
|
||||
:transaction/expected-deposit
|
||||
:db/id] i)
|
||||
payment-id (-> transaction :transaction/payment :db/id)
|
||||
expected-deposit-id (-> transaction :transaction/expected-deposit :db/id)]
|
||||
(cond->> [[:db/retractEntity [:journal-entry/original-entity i]]]
|
||||
payment-id (into [{:db/id payment-id
|
||||
:payment/status :payment-status/pending}
|
||||
[:db/retract (:db/id transaction) :transaction/payment payment-id]])
|
||||
expected-deposit-id (into [{:db/id expected-deposit-id
|
||||
:expected-deposit/status :expected-deposit-status/pending}
|
||||
[:db/retract (:db/id transaction) :transaction/expected-deposit expected-deposit-id]]))))
|
||||
all-ids)
|
||||
(:id context))
|
||||
(audit-transact-batch
|
||||
(mapcat (fn [i]
|
||||
(let [transaction-tx (if (:suppress args)
|
||||
@@ -242,21 +240,21 @@
|
||||
(assert-power-user (:id context))
|
||||
|
||||
(let [transaction (d-transactions/get-by-id (:transaction_id args))
|
||||
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
|
||||
_ (assert-can-see-client (:id context) (:transaction/client transaction))
|
||||
matches-set (i-transactions/match-transaction-to-unfulfilled-autopayments (:transaction/amount transaction)
|
||||
(:db/id (:transaction/client transaction)))]
|
||||
(->graphql (for [matches matches-set]
|
||||
(for [[_ invoice-id ] matches]
|
||||
(for [[_ invoice-id] matches]
|
||||
(d-invoices/get-by-id invoice-id))))))
|
||||
|
||||
(defn get-potential-unpaid-invoices-matches [context args _]
|
||||
(assert-power-user (:id context))
|
||||
(let [transaction (d-transactions/get-by-id (:transaction_id args))
|
||||
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
|
||||
_ (assert-can-see-client (:id context) (:transaction/client transaction))
|
||||
matches-set (i-transactions/match-transaction-to-unpaid-invoices (:transaction/amount transaction)
|
||||
(:db/id (:transaction/client transaction)))]
|
||||
(->graphql (for [matches matches-set]
|
||||
(for [[_ invoice-id ] matches]
|
||||
(for [[_ invoice-id] matches]
|
||||
(d-invoices/get-by-id invoice-id))))))
|
||||
|
||||
(defn unlink-transaction [context args _]
|
||||
@@ -264,20 +262,20 @@
|
||||
args (assoc args :id (:id context))
|
||||
transaction-id (:transaction_id args)
|
||||
transaction (dc/pull (dc/db conn)
|
||||
[:transaction/approval-status
|
||||
:transaction/status
|
||||
:transaction/date
|
||||
:transaction/location
|
||||
:transaction/vendor
|
||||
:transaction/accounts
|
||||
:transaction/client [:db/id]
|
||||
{:transaction/payment [:payment/date {:payment/status [:db/ident]} :db/id]} ]
|
||||
transaction-id)
|
||||
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
|
||||
[:transaction/approval-status
|
||||
:transaction/status
|
||||
:transaction/date
|
||||
:transaction/location
|
||||
:transaction/vendor
|
||||
:transaction/accounts
|
||||
:transaction/client [:db/id]
|
||||
{:transaction/payment [:payment/date {:payment/status [:db/ident]} :db/id]}]
|
||||
transaction-id)
|
||||
_ (assert-can-see-client (:id context) (:transaction/client transaction))
|
||||
_ (assert-not-locked (:db/id (:transaction/client transaction)) (:transaction/date transaction))
|
||||
_ (when (:transaction/payment transaction)
|
||||
(assert-not-locked (:db/id (:transaction/client transaction)) (-> transaction :transaction/payment :payment/date)))
|
||||
payment (-> transaction :transaction/payment )
|
||||
payment (-> transaction :transaction/payment)
|
||||
is-autopay-payment? (some->> (dc/q {:find ['?sp]
|
||||
:in ['$ '?payment]
|
||||
:where ['[?ip :invoice-payment/payment ?payment]
|
||||
@@ -286,8 +284,7 @@
|
||||
(dc/db conn) (:db/id payment))
|
||||
seq
|
||||
(map first)
|
||||
(every? #(instance? java.util.Date %)))
|
||||
]
|
||||
(every? #(instance? java.util.Date %)))]
|
||||
|
||||
(alog/info ::unlinking :transaction (pr-str transaction) :autopay is-autopay-payment? :payment (pr-str payment))
|
||||
|
||||
@@ -295,49 +292,47 @@
|
||||
(throw (ex-info "Payment can't be undone because it isn't cleared." {:validation-error "Payment can't be undone because it isn't cleared."})))
|
||||
(if is-autopay-payment?
|
||||
(audit-transact
|
||||
(-> [{:db/id (:db/id payment)
|
||||
:payment/status :payment-status/pending}
|
||||
[:upsert-transaction
|
||||
{:db/id transaction-id
|
||||
:transaction/approval-status :transaction-approval-status/unapproved
|
||||
:transaction/payment nil
|
||||
:transaction/vendor nil
|
||||
:transaction/location nil
|
||||
:transaction/accounts nil}]
|
||||
(-> [{:db/id (:db/id payment)
|
||||
:payment/status :payment-status/pending}
|
||||
[:upsert-transaction
|
||||
{:db/id transaction-id
|
||||
:transaction/approval-status :transaction-approval-status/unapproved
|
||||
:transaction/payment nil
|
||||
:transaction/vendor nil
|
||||
:transaction/location nil
|
||||
:transaction/accounts nil}]
|
||||
|
||||
[:db/retractEntity (:db/id payment) ]]
|
||||
[:db/retractEntity (:db/id payment)]]
|
||||
|
||||
(into (map (fn [[invoice-payment]]
|
||||
[:db/retractEntity invoice-payment])
|
||||
(dc/q {:find ['?ip]
|
||||
:in ['$ '?p]
|
||||
:where ['[?ip :invoice-payment/payment ?p]]}
|
||||
(dc/db conn)
|
||||
(:db/id payment) ))))
|
||||
(into (map (fn [[invoice-payment]]
|
||||
[:db/retractEntity invoice-payment])
|
||||
(dc/q {:find ['?ip]
|
||||
:in ['$ '?p]
|
||||
:where ['[?ip :invoice-payment/payment ?p]]}
|
||||
(dc/db conn)
|
||||
(:db/id payment)))))
|
||||
(:id context))
|
||||
(audit-transact
|
||||
[{:db/id (:db/id payment)
|
||||
:payment/status :payment-status/pending}
|
||||
[:upsert-transaction
|
||||
{:db/id transaction-id
|
||||
:transaction/approval-status :transaction-approval-status/unapproved
|
||||
:transaction/payment nil
|
||||
:transaction/vendor nil
|
||||
:transaction/location nil
|
||||
:transaction/accounts nil}]]
|
||||
[{:db/id (:db/id payment)
|
||||
:payment/status :payment-status/pending}
|
||||
[:upsert-transaction
|
||||
{:db/id transaction-id
|
||||
:transaction/approval-status :transaction-approval-status/unapproved
|
||||
:transaction/payment nil
|
||||
:transaction/vendor nil
|
||||
:transaction/location nil
|
||||
:transaction/accounts nil}]]
|
||||
(:id context)))
|
||||
(-> (d-transactions/get-by-id transaction-id)
|
||||
approval-status->graphql
|
||||
->graphql)))
|
||||
|
||||
|
||||
(defn transaction-account->entity [{:keys [id account_id amount location]}]
|
||||
#:transaction-account {:amount amount
|
||||
:db/id (or id (random-tempid))
|
||||
:account account_id
|
||||
:location location})
|
||||
|
||||
|
||||
(defn assert-valid-expense-accounts [accounts]
|
||||
(doseq [trans-account accounts
|
||||
:let [account (dc/pull (dc/db conn)
|
||||
@@ -351,7 +346,7 @@
|
||||
(:account/location account)))
|
||||
(let [err (str "Account uses location '" (:location trans-account) "' but expects '" (:account/location account) "'")]
|
||||
(throw (ex-info err
|
||||
{:validation-error err}))))
|
||||
{:validation-error err}))))
|
||||
|
||||
(when (and (empty? (:account/location account))
|
||||
(= "A" (:location trans-account)))
|
||||
@@ -359,13 +354,12 @@
|
||||
(throw (ex-info err
|
||||
{:validation-error err}))))
|
||||
|
||||
|
||||
(when (nil? (:account_id trans-account))
|
||||
(throw (ex-info "Account is missing account" {:validation-error "Account is missing account"})))))
|
||||
|
||||
(defn edit-transaction [context {{:keys [id accounts vendor_id approval_status memo forecast_match]} :transaction} _]
|
||||
(let [existing-transaction (d-transactions/get-by-id id)
|
||||
_ (assert-can-see-client (:id context) (:transaction/client existing-transaction) )
|
||||
_ (assert-can-see-client (:id context) (:transaction/client existing-transaction))
|
||||
_ (assert-valid-expense-accounts accounts)
|
||||
_ (assert-not-locked (:db/id (:transaction/client existing-transaction)) (:transaction/date existing-transaction))
|
||||
account-total (reduce + 0 (map (fn [x] (:amount x)) accounts))
|
||||
@@ -378,17 +372,17 @@
|
||||
set
|
||||
(conj "A")
|
||||
(conj "HQ"))))]
|
||||
|
||||
|
||||
(when (and (not (dollars= (Math/abs (:transaction/amount existing-transaction)) account-total))
|
||||
(or
|
||||
(and (= approval_status :unapproved)
|
||||
(> (count accounts) 0))
|
||||
(not= approval_status :unapproved)))
|
||||
(not= approval_status :unapproved)))
|
||||
(let [error (str "Expense account total (" account-total ") does not equal transaction total (" (Math/abs (:transaction/amount existing-transaction)) ")")]
|
||||
(throw (ex-info error {:validation-error error}))))
|
||||
(throw (ex-info error {:validation-error error}))))
|
||||
(when missing-locations
|
||||
(throw (ex-info (str "Location '" (str/join ", " missing-locations) "' not found on client.") {})) )
|
||||
|
||||
(throw (ex-info (str "Location '" (str/join ", " missing-locations) "' not found on client.") {})))
|
||||
|
||||
(audit-transact (cond-> [[:upsert-transaction {:db/id id
|
||||
:transaction/vendor vendor_id
|
||||
:transaction/memo memo
|
||||
@@ -413,8 +407,8 @@
|
||||
(defn match-transaction [context {:keys [transaction_id payment_id]} _]
|
||||
(let [transaction (d-transactions/get-by-id transaction_id)
|
||||
payment (d-checks/get-by-id payment_id)
|
||||
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
|
||||
_ (assert-can-see-client (:id context) (:payment/client payment) )
|
||||
_ (assert-can-see-client (:id context) (:transaction/client transaction))
|
||||
_ (assert-can-see-client (:id context) (:payment/client payment))
|
||||
_ (assert-not-locked (:db/id (:transaction/client transaction)) (:transaction/date transaction))]
|
||||
(when (not= (:db/id (:transaction/client transaction))
|
||||
(:db/id (:payment/client payment)))
|
||||
@@ -423,7 +417,7 @@
|
||||
(when-not (dollars= (- (:transaction/amount transaction))
|
||||
(:payment/amount payment))
|
||||
(throw (ex-info "Amounts don't match" {:validation-error "Amounts don't match"})))
|
||||
(audit-transact (into
|
||||
(audit-transact (into
|
||||
[{:db/id (:db/id payment)
|
||||
:payment/status :payment-status/cleared
|
||||
:payment/date (coerce/to-date (first (sort [(:payment/date payment)
|
||||
@@ -431,14 +425,14 @@
|
||||
|
||||
[:upsert-transaction
|
||||
{:db/id (:db/id transaction)
|
||||
:transaction/payment (:db/id payment)
|
||||
:transaction/vendor (:db/id (:payment/vendor payment))
|
||||
:transaction/location "A"
|
||||
:transaction/approval-status :transaction-approval-status/approved
|
||||
:transaction/accounts [{:db/id (random-tempid)
|
||||
:transaction-account/account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
|
||||
:transaction-account/location "A"
|
||||
:transaction-account/amount (Math/abs (:transaction/amount transaction))}]}]])
|
||||
:transaction/payment (:db/id payment)
|
||||
:transaction/vendor (:db/id (:payment/vendor payment))
|
||||
:transaction/location "A"
|
||||
:transaction/approval-status :transaction-approval-status/approved
|
||||
:transaction/accounts [{:db/id (random-tempid)
|
||||
:transaction-account/account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
|
||||
:transaction-account/location "A"
|
||||
:transaction-account/amount (Math/abs (:transaction/amount transaction))}]}]])
|
||||
(:id context)))
|
||||
(solr/touch-with-ledger transaction_id)
|
||||
(-> (d-transactions/get-by-id transaction_id)
|
||||
@@ -448,7 +442,7 @@
|
||||
(defn match-transaction-autopay-invoices [context {:keys [transaction_id autopay_invoice_ids]} _]
|
||||
(let [_ (assert-power-user (:id context))
|
||||
transaction (d-transactions/get-by-id transaction_id)
|
||||
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
|
||||
_ (assert-can-see-client (:id context) (:transaction/client transaction))
|
||||
db (dc/db conn)
|
||||
invoice-clients (set (map #(pull-ref db :invoice/client %) autopay_invoice_ids))
|
||||
invoice-amount (reduce + 0.0 (map #(pull-attr db :invoice/total %) autopay_invoice_ids))
|
||||
@@ -474,9 +468,9 @@
|
||||
(:db/id (:transaction/bank-account transaction))
|
||||
(:db/id (:transaction/client transaction)))]
|
||||
(alog/info ::adding-payment-from-autopay-invoice
|
||||
:payment (pr-str payment-tx))
|
||||
:payment (pr-str payment-tx))
|
||||
(audit-transact payment-tx (:id context)))
|
||||
(solr/touch-with-ledger transaction_id)
|
||||
(solr/touch-with-ledger transaction_id)
|
||||
(-> (d-transactions/get-by-id transaction_id)
|
||||
approval-status->graphql
|
||||
->graphql)))
|
||||
@@ -485,8 +479,8 @@
|
||||
(defn match-transaction-unpaid-invoices [context {:keys [transaction_id unpaid_invoice_ids]} _]
|
||||
(let [_ (assert-power-user (:id context))
|
||||
transaction (d-transactions/get-by-id transaction_id)
|
||||
|
||||
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
|
||||
|
||||
_ (assert-can-see-client (:id context) (:transaction/client transaction))
|
||||
_ (assert-not-locked (:db/id (:transaction/client transaction)) (:transaction/date transaction))
|
||||
db (dc/db conn)
|
||||
invoice-clients (set (map #(pull-ref db :invoice/client %) unpaid_invoice_ids))
|
||||
@@ -502,17 +496,17 @@
|
||||
(throw (ex-info "Amounts don't match" {:validation-error "Amounts don't match"})))
|
||||
(when (:transaction/payment transaction)
|
||||
(throw (ex-info "Transaction already linked" {:validation-error "Transaction already linked"})))
|
||||
|
||||
|
||||
(let [payment-tx (i-transactions/add-new-payment (dc/pull db [:transaction/amount :transaction/date :db/id] transaction_id)
|
||||
(map (fn [id]
|
||||
(let [entity (dc/pull db [:invoice/vendor :db/id :invoice/total] id)]
|
||||
[(or (-> entity :invoice/vendor :db/id)
|
||||
(-> entity :invoice/vendor))
|
||||
(-> entity :db/id)
|
||||
(-> entity :invoice/total)]))
|
||||
unpaid_invoice_ids)
|
||||
(:db/id (:transaction/bank-account transaction))
|
||||
(:db/id (:transaction/client transaction)))]
|
||||
(map (fn [id]
|
||||
(let [entity (dc/pull db [:invoice/vendor :db/id :invoice/total] id)]
|
||||
[(or (-> entity :invoice/vendor :db/id)
|
||||
(-> entity :invoice/vendor))
|
||||
(-> entity :db/id)
|
||||
(-> entity :invoice/total)]))
|
||||
unpaid_invoice_ids)
|
||||
(:db/id (:transaction/bank-account transaction))
|
||||
(:db/id (:transaction/client transaction)))]
|
||||
(audit-transact payment-tx (:id context)))
|
||||
(solr/touch-with-ledger transaction_id)
|
||||
|
||||
@@ -527,9 +521,8 @@
|
||||
:count Integer/MAX_VALUE} nil)
|
||||
|
||||
(filter #(not (:payment %)))
|
||||
(map :id ))
|
||||
(map :id))
|
||||
|
||||
|
||||
transaction_ids)
|
||||
_ (mu/log ::here :txids transaction_ids)
|
||||
transaction_ids (all-ids-not-locked transaction_ids)
|
||||
@@ -553,17 +546,16 @@
|
||||
(audit-transact (mapv (fn [t]
|
||||
[:upsert-transaction
|
||||
(remove-nils (rm/apply-rule {:db/id (:db/id t)
|
||||
:transaction/amount (:transaction/amount t)}
|
||||
transaction-rule
|
||||
:transaction/amount (:transaction/amount t)}
|
||||
transaction-rule
|
||||
|
||||
(or (-> t :transaction/bank-account :bank-account/locations)
|
||||
(-> t :transaction/client :client/locations))))])
|
||||
(or (-> t :transaction/bank-account :bank-account/locations)
|
||||
(-> t :transaction/client :client/locations))))])
|
||||
transactions)
|
||||
(:id context))
|
||||
|
||||
(doseq [n transactions]
|
||||
(solr/touch-with-ledger (:db/id n)))
|
||||
)
|
||||
(solr/touch-with-ledger (:db/id n))))
|
||||
(transduce
|
||||
(comp
|
||||
(map d-transactions/get-by-id)
|
||||
@@ -571,12 +563,12 @@
|
||||
(map ->graphql))
|
||||
conj
|
||||
[]
|
||||
transaction_ids ))
|
||||
transaction_ids))
|
||||
|
||||
(def objects
|
||||
{:transaction {:fields {:id {:type :id}
|
||||
:amount {:type 'String}
|
||||
:memo {:type 'String}
|
||||
:memo {:type 'String}
|
||||
:is_locked {:type 'Boolean}
|
||||
:description_original {:type 'String}
|
||||
:description_simple {:type 'String}
|
||||
@@ -628,8 +620,8 @@
|
||||
:resolve :mutation/bulk-code-transactions}
|
||||
:delete_transactions {:type :message
|
||||
:args {:filters {:type :transaction_filters}
|
||||
:ids {:type '(list :id)}
|
||||
:suppress {:type 'Boolean}}
|
||||
:ids {:type '(list :id)}
|
||||
:suppress {:type 'Boolean}}
|
||||
:resolve :mutation/delete-transactions}
|
||||
:edit_transaction {:type :transaction
|
||||
:args {:transaction {:type :edit_transaction}}
|
||||
@@ -711,9 +703,8 @@
|
||||
:mutation/match-transaction-unpaid-invoices match-transaction-unpaid-invoices
|
||||
:mutation/match-transaction-rules match-transaction-rules})
|
||||
|
||||
|
||||
(defn attach [schema]
|
||||
(->
|
||||
(->
|
||||
(merge-with merge schema
|
||||
{:objects objects
|
||||
:queries queries
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
[iol-ion.query :refer [entid]]
|
||||
[slingshot.slingshot :refer [throw+]]))
|
||||
|
||||
|
||||
(defn snake->kebab [s]
|
||||
(str/replace s #"_" "-"))
|
||||
|
||||
@@ -107,8 +106,6 @@
|
||||
(#{"manager" "user" "power-user" "read-only"} (:user/role id))
|
||||
(:user/clients id [])))
|
||||
|
||||
|
||||
|
||||
(defn result->page [results result-count key args]
|
||||
{key (map ->graphql results)
|
||||
:total result-count
|
||||
@@ -197,7 +194,6 @@
|
||||
(= :client/code (first x)))
|
||||
[(entid (dc/db conn) x)]
|
||||
|
||||
|
||||
(sequential? x)
|
||||
(mapcat coerce-client-ids x)
|
||||
|
||||
@@ -218,14 +214,14 @@
|
||||
e)))))
|
||||
|
||||
(defn exception->4xx [f]
|
||||
(try
|
||||
(try
|
||||
(f)
|
||||
(catch Throwable e
|
||||
(throw+ (ex-info (.getMessage e) {:type :form-validation
|
||||
:form-validation-errors [(.getMessage e)]}))
|
||||
(throw+ (ex-info (.getMessage e) {:type :form-validation
|
||||
:form-validation-errors [(.getMessage e)]}))
|
||||
#_(throw (ex-info (.getMessage e)
|
||||
{:type :notification}
|
||||
e)))))
|
||||
{:type :notification}
|
||||
e)))))
|
||||
|
||||
(defn notify-if-locked [client-id date]
|
||||
(try
|
||||
|
||||
@@ -130,7 +130,6 @@
|
||||
:vendor/schedule-payment-dom schedule-payment-dom
|
||||
:vendor/automatically-paid-when-due (:automatically_paid_when_due in)))]
|
||||
|
||||
|
||||
transaction-result (audit-transact [transaction] (:id context))
|
||||
new-vendor (d-vendors/get-by-id (or (-> transaction-result :tempids (get "vendor"))
|
||||
id))]
|
||||
@@ -160,7 +159,6 @@
|
||||
(audit-transact transaction (:id context))
|
||||
to))
|
||||
|
||||
|
||||
(defn get-graphql [context args _]
|
||||
(assert-admin (:id context))
|
||||
(let [args (assoc args :id (:id context))
|
||||
@@ -187,7 +185,6 @@
|
||||
(if-let [query (not-empty (cleanse-query (:query args)))]
|
||||
(let [search-query (str "name:(" query ")")]
|
||||
|
||||
|
||||
(for [{:keys [id name]} (solr/query solr/impl "vendors" {"query" (cond-> search-query
|
||||
(not (is-admin? (:id context))) (str " hidden:false"))
|
||||
"fields" "id, name"})]
|
||||
|
||||
@@ -66,10 +66,9 @@
|
||||
])
|
||||
|
||||
(defn not-found [_]
|
||||
{:status 404
|
||||
{:status 404
|
||||
:headers {}
|
||||
:body ""})
|
||||
|
||||
:body ""})
|
||||
|
||||
(defn home-handler [{:keys [identity]}]
|
||||
(if identity
|
||||
@@ -78,7 +77,6 @@
|
||||
{:status 302
|
||||
:headers {"Location" "/login"}}))
|
||||
|
||||
|
||||
(def match->handler-lookup
|
||||
(-> {:not-found not-found
|
||||
:home home-handler}
|
||||
@@ -90,15 +88,13 @@
|
||||
(merge yodlee2/match->handler)
|
||||
(merge auth/match->handler)
|
||||
(merge invoices/match->handler)
|
||||
(merge exports/match->handler)
|
||||
))
|
||||
(merge exports/match->handler)))
|
||||
|
||||
(def match->handler
|
||||
(fn [route]
|
||||
(or (get match->handler-lookup route)
|
||||
route)))
|
||||
|
||||
|
||||
(def route-handler
|
||||
(make-handler all-routes
|
||||
match->handler))
|
||||
@@ -125,18 +121,17 @@
|
||||
uri
|
||||
:request-method method))
|
||||
|
||||
|
||||
(def auth-backend (jws-backend {:secret (:jwt-secret env) :options {:alg :hs512}}))
|
||||
|
||||
(defn wrap-logging [handler]
|
||||
(fn [request]
|
||||
(mu/with-context (cond-> {:uri (:uri request)
|
||||
:route (:handler (bidi.bidi/match-route all-routes
|
||||
(:uri request)
|
||||
:request-method (:request-method request)))
|
||||
(mu/with-context (cond-> {:uri (:uri request)
|
||||
:route (:handler (bidi.bidi/match-route all-routes
|
||||
(:uri request)
|
||||
:request-method (:request-method request)))
|
||||
|
||||
:client-selection (:client-selection request)
|
||||
:source "request"
|
||||
:source "request"
|
||||
:query (:uri request)
|
||||
:request-method (:request-method request)
|
||||
:user (dissoc (:identity request)
|
||||
@@ -159,20 +154,18 @@
|
||||
:exception e)
|
||||
(throw e)))))))
|
||||
|
||||
|
||||
|
||||
(defn wrap-idle-session-timeout
|
||||
[handler]
|
||||
(fn [request]
|
||||
(let [session (:session request {:version session-version/current-session-version})
|
||||
(let [session (:session request {:version session-version/current-session-version})
|
||||
end-time (coerce/to-date-time (::idle-timeout session))]
|
||||
(if (and end-time (time/before? end-time (time/now)))
|
||||
(if (get (:headers request) "hx-request")
|
||||
{:session nil
|
||||
:status 200
|
||||
:status 200
|
||||
:headers {"hx-redirect" "/login"}}
|
||||
{:session nil
|
||||
:status 302
|
||||
:status 302
|
||||
:headers {"Location" "/login"}})
|
||||
(when-let [response (handler request)]
|
||||
(let [session (:session response session)]
|
||||
@@ -238,7 +231,7 @@
|
||||
seq
|
||||
(pull-many (dc/db conn)
|
||||
'[:db/id :client/name :client/code :client/locations
|
||||
:client/matches :client/feature-flags
|
||||
:client/matches :client/feature-flags
|
||||
{:client/bank-accounts [:db/id
|
||||
{:bank-account/type [:db/ident]}
|
||||
:bank-account/number
|
||||
@@ -305,7 +298,7 @@
|
||||
{:status 200
|
||||
:headers {"hx-trigger" (cheshire/generate-string
|
||||
{"notification" (str (hiccup/html [:div (.getMessage e)]))})
|
||||
"hx-reswap" "none"}} ;; TODO make a warning box so you don't have to reuse the notifaction box, or make it reuse the same box but theme differently
|
||||
"hx-reswap" "none"}} ;; TODO make a warning box so you don't have to reuse the notifaction box, or make it reuse the same box but theme differently
|
||||
:else
|
||||
{:status 500
|
||||
:body (pr-str e)})))))
|
||||
@@ -322,32 +315,48 @@
|
||||
:valid-trimmed-client-ids trimmed-clients
|
||||
:first-client-id (first valid-clients)
|
||||
:clients-trimmed? (not= (count trimmed-clients) (count valid-clients)))))))
|
||||
|
||||
(defn wrap-dev-login [handler]
|
||||
(fn [request]
|
||||
(if (and (= "/dev-login" (:uri request))
|
||||
(some-> env :base-url (.contains "localhost")))
|
||||
(let [identity {:user "Dev User"
|
||||
:user/name "Dev User"
|
||||
:user/role "admin"
|
||||
:db/id 0}]
|
||||
{:status 200
|
||||
:headers {"Content-Type" "text/html"}
|
||||
:body "<p>Logged in as Dev User!</p><a href='/dashboard'>Continue to dashboard</a>"
|
||||
:session {:identity identity
|
||||
:version session-version/current-session-version}})
|
||||
(handler request))))
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defonce app
|
||||
(-> route-handler
|
||||
(wrap-hx-current-url-params)
|
||||
(wrap-guess-route)
|
||||
(wrap-logging)
|
||||
(wrap-trim-clients)
|
||||
(wrap-hydrate-clients)
|
||||
(wrap-store-client-in-session)
|
||||
(wrap-gunzip-jwt)
|
||||
(wrap-authorization auth-backend)
|
||||
(wrap-authentication auth-backend
|
||||
(session-backend {:authfn (fn [auth]
|
||||
(dissoc auth :exp))}))
|
||||
(-> route-handler
|
||||
(wrap-hx-current-url-params)
|
||||
(wrap-guess-route)
|
||||
(wrap-logging)
|
||||
(wrap-trim-clients)
|
||||
(wrap-hydrate-clients)
|
||||
(wrap-store-client-in-session)
|
||||
(wrap-gunzip-jwt)
|
||||
(wrap-dev-login)
|
||||
(wrap-authorization auth-backend)
|
||||
(wrap-authentication auth-backend
|
||||
(session-backend {:authfn (fn [auth]
|
||||
(dissoc auth :exp))}))
|
||||
|
||||
#_(wrap-pprint-session)
|
||||
#_(wrap-pprint-session)
|
||||
|
||||
(session-version/wrap-session-version)
|
||||
(wrap-idle-session-timeout)
|
||||
(wrap-session {:store (cookie-store
|
||||
{:key
|
||||
(byte-array
|
||||
[42, 52, -31, 101, -126, -33, -118, -69, -82, -59, -15, -69, -38, 103, -102, -1])})})
|
||||
(session-version/wrap-session-version)
|
||||
(wrap-idle-session-timeout)
|
||||
(wrap-session {:store (cookie-store
|
||||
{:key
|
||||
(byte-array
|
||||
[42, 52, -31, 101, -126, -33, -118, -69, -82, -59, -15, -69, -38, 103, -102, -1])})})
|
||||
|
||||
#_(wrap-reload)
|
||||
(wrap-params)
|
||||
(mp/wrap-multipart-params)
|
||||
(wrap-edn-params)
|
||||
(wrap-error)))
|
||||
#_(wrap-reload)
|
||||
(wrap-params)
|
||||
(mp/wrap-multipart-params)
|
||||
(wrap-edn-params)
|
||||
(wrap-error)))
|
||||
|
||||
@@ -9,21 +9,21 @@
|
||||
(random-tempid)))
|
||||
|
||||
(defn wrap-integration [f bank-account]
|
||||
(try
|
||||
(try
|
||||
(let [result (f)]
|
||||
@(dc/transact-async conn [{:db/id bank-account
|
||||
:bank-account/integration-status
|
||||
{:db/id (bank-account->integration-id bank-account)
|
||||
:integration-status/state :integration-state/success
|
||||
:integration-status/last-attempt (java.util.Date.)
|
||||
:integration-status/last-updated (java.util.Date.)}}])
|
||||
:bank-account/integration-status
|
||||
{:db/id (bank-account->integration-id bank-account)
|
||||
:integration-status/state :integration-state/success
|
||||
:integration-status/last-attempt (java.util.Date.)
|
||||
:integration-status/last-updated (java.util.Date.)}}])
|
||||
result)
|
||||
(catch Exception e
|
||||
@(dc/transact-async conn [{:db/id bank-account
|
||||
:bank-account/integration-status
|
||||
{:db/id (bank-account->integration-id bank-account)
|
||||
:integration-status/state :integration-state/failed
|
||||
:integration-status/last-attempt (java.util.Date.)
|
||||
:integration-status/message (.getMessage e)}}])
|
||||
:bank-account/integration-status
|
||||
{:db/id (bank-account->integration-id bank-account)
|
||||
:integration-status/state :integration-state/failed
|
||||
:integration-status/last-attempt (java.util.Date.)
|
||||
:integration-status/message (.getMessage e)}}])
|
||||
(alog/warn ::integration-failed :error e)
|
||||
nil)))
|
||||
|
||||
@@ -12,15 +12,15 @@
|
||||
[datomic.api :as dc]
|
||||
[iol-ion.utils :refer [remove-nils]]))
|
||||
|
||||
(defn get-intuit-bank-accounts
|
||||
( [db]
|
||||
(dc/q '[:find ?external-id ?ba ?c
|
||||
:in $
|
||||
:where
|
||||
[?c :client/bank-accounts ?ba]
|
||||
[?ba :bank-account/intuit-bank-account ?iab]
|
||||
[?iab :intuit-bank-account/external-id ?external-id]]
|
||||
db))
|
||||
(defn get-intuit-bank-accounts
|
||||
([db]
|
||||
(dc/q '[:find ?external-id ?ba ?c
|
||||
:in $
|
||||
:where
|
||||
[?c :client/bank-accounts ?ba]
|
||||
[?ba :bank-account/intuit-bank-account ?iab]
|
||||
[?iab :intuit-bank-account/external-id ?external-id]]
|
||||
db))
|
||||
([db & client-codes]
|
||||
(dc/q '[:find ?external-id ?ba ?c
|
||||
:in $ [?cc ...]
|
||||
@@ -32,7 +32,6 @@
|
||||
db
|
||||
client-codes)))
|
||||
|
||||
|
||||
(defn intuit->transaction [transaction]
|
||||
(let [check-number (when (not (str/blank? (:Num transaction)))
|
||||
(try
|
||||
@@ -46,7 +45,6 @@
|
||||
:transaction/status "POSTED"}
|
||||
check-number (assoc :transaction/check-number check-number))))
|
||||
|
||||
|
||||
(defn intuits->transactions [transactions bank-account-id client-id]
|
||||
(->> transactions
|
||||
(map intuit->transaction)
|
||||
|
||||
@@ -11,10 +11,9 @@
|
||||
(t/is (= #inst "2021-01-01T00:00:00-08:00" (:transaction/date (sut/intuit->transaction (assoc base-transaction :Date "2021-01-01")))))
|
||||
(t/is (= #inst "2021-06-01T00:00:00-07:00" (:transaction/date (sut/intuit->transaction (assoc base-transaction :Date "2021-06-01")))))))
|
||||
|
||||
|
||||
(t/deftest intuits->transactions
|
||||
(t/testing "should give unique ids to duplicates"
|
||||
(t/is (= ["2021-10-11T00:00:00.000-07:00-123-this is a description-45.23-0-345"
|
||||
"2021-10-11T00:00:00.000-07:00-123-this is a description-45.23-1-345"] (map :transaction/raw-id (sut/intuits->transactions [base-transaction base-transaction]
|
||||
123
|
||||
345))))))
|
||||
123
|
||||
345))))))
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
[clojure.data.csv :as csv]
|
||||
[datomic.api :as dc]))
|
||||
|
||||
|
||||
|
||||
(def columns [:status :raw-date :description-original :high-level-category nil nil :amount nil nil nil nil nil :bank-account-code :client-code])
|
||||
|
||||
(defn tabulate-data [data]
|
||||
@@ -33,12 +31,12 @@
|
||||
|
||||
(defn import-batch [transactions user]
|
||||
(let [bank-account-code->client (into {}
|
||||
(dc/q '[:find ?bac ?c
|
||||
:in $
|
||||
:where
|
||||
[?c :client/bank-accounts ?ba]
|
||||
[?ba :bank-account/code ?bac]]
|
||||
(dc/db conn)))
|
||||
(dc/q '[:find ?bac ?c
|
||||
:in $
|
||||
:where
|
||||
[?c :client/bank-accounts ?ba]
|
||||
[?ba :bank-account/code ?bac]]
|
||||
(dc/db conn)))
|
||||
bank-account-code->bank-account (into {}
|
||||
(dc/q '[:find ?bac ?ba
|
||||
:in $
|
||||
@@ -46,9 +44,9 @@
|
||||
(dc/db conn)))
|
||||
import-batch (t/start-import-batch :import-source/manual user)
|
||||
transactions (->> transactions
|
||||
(map (fn [t]
|
||||
(manual->transaction t bank-account-code->bank-account bank-account-code->client)))
|
||||
(t/apply-synthetic-ids ))]
|
||||
(map (fn [t]
|
||||
(manual->transaction t bank-account-code->bank-account bank-account-code->client)))
|
||||
(t/apply-synthetic-ids))]
|
||||
(try
|
||||
(doseq [transaction transactions]
|
||||
(when-not (seq (:errors transaction))
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
|
||||
(defn parse-date [{:keys [raw-date]}]
|
||||
(when-not
|
||||
(re-find #"\d{1,2}/\d{1,2}/\d{4}" raw-date)
|
||||
(re-find #"\d{1,2}/\d{1,2}/\d{4}" raw-date)
|
||||
(throw (Exception. (str "Date " raw-date " must match MM/dd/yyyy"))))
|
||||
(try
|
||||
(try
|
||||
|
||||
(parse-u/parse-value :clj-time "MM/dd/yyyy" raw-date)
|
||||
(catch Exception e
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
[manifold.deferred :as de]
|
||||
[manifold.executor :as ex]))
|
||||
|
||||
(defn get-plaid-accounts
|
||||
(defn get-plaid-accounts
|
||||
([db]
|
||||
(-> (dc/q '[:find ?ba ?c ?external-id ?t
|
||||
:in $
|
||||
@@ -40,7 +40,6 @@
|
||||
db
|
||||
client-codes))))
|
||||
|
||||
|
||||
(defn plaid->transaction [t plaid-merchant->vendor-id]
|
||||
(alog/info ::trying-transaction :transaction t)
|
||||
(cond-> #:transaction {:description-original (:name t)
|
||||
@@ -57,7 +56,7 @@
|
||||
:db/id (random-tempid)})
|
||||
(not (str/blank? (:check_number t))) (assoc :transaction/check-number (Long/parseLong (:check_number t)))
|
||||
#_#_(plaid-merchant->vendor-id (:merchant_name t)) (assoc :transaction/default-vendor
|
||||
(plaid-merchant->vendor-id (:merchant_name t)))))
|
||||
(plaid-merchant->vendor-id (:merchant_name t)))))
|
||||
|
||||
(defn build-plaid-merchant->vendor-id []
|
||||
(into {}
|
||||
@@ -66,23 +65,22 @@
|
||||
:where
|
||||
[?v :vendor/plaid-merchant ?pm]
|
||||
[?pm :plaid-merchant/name ?pmn]]
|
||||
(dc/db conn ))))
|
||||
|
||||
(dc/db conn))))
|
||||
|
||||
(def single-thread (ex/fixed-thread-executor 1))
|
||||
|
||||
(defn rebuild-search-index []
|
||||
(de/future-with
|
||||
single-thread
|
||||
(auto-ap.solr/index-documents-raw
|
||||
auto-ap.solr/impl
|
||||
"plaid_merchants"
|
||||
(for [[result] (dc/qseq {:query '[:find (pull ?v [:plaid-merchant/name :db/id])
|
||||
:in $
|
||||
:where [?v :plaid-merchant/name]]
|
||||
:args [(dc/db conn)]})]
|
||||
{"id" (:db/id result)
|
||||
"name" (:plaid-merchant/name result)}))))
|
||||
single-thread
|
||||
(auto-ap.solr/index-documents-raw
|
||||
auto-ap.solr/impl
|
||||
"plaid_merchants"
|
||||
(for [[result] (dc/qseq {:query '[:find (pull ?v [:plaid-merchant/name :db/id])
|
||||
:in $
|
||||
:where [?v :plaid-merchant/name]]
|
||||
:args [(dc/db conn)]})]
|
||||
{"id" (:db/id result)
|
||||
"name" (:plaid-merchant/name result)}))))
|
||||
|
||||
(defn upsert-accounts []
|
||||
(try
|
||||
@@ -96,20 +94,16 @@
|
||||
(remove-nils
|
||||
{:plaid-account/external-id (:account_id a)
|
||||
:plaid-account/last-synced (coerce/to-date (coerce/to-date-time (-> item :status :transactions :last_successful_update))) :plaid-account/balance (or (some-> a
|
||||
:balances
|
||||
:current
|
||||
double)
|
||||
0.0)}))))
|
||||
:balances
|
||||
:current
|
||||
double)
|
||||
0.0)}))))
|
||||
(catch Exception e
|
||||
(alog/warn ::couldnt-upsert-account :error e))))
|
||||
|
||||
|
||||
(catch Exception e
|
||||
(alog/warn ::couldnt-upsert-accounts :error e))))
|
||||
|
||||
|
||||
|
||||
|
||||
(defn import-plaid-int []
|
||||
(let [_ (upsert-accounts)
|
||||
import-batch (t/start-import-batch :import-source/plaid "Automated plaid user")
|
||||
@@ -119,8 +113,8 @@
|
||||
(try
|
||||
(doseq [[bank-account-id client-id external-id access-token] (get-plaid-accounts (dc/db conn))
|
||||
:let [transaction-result (wrap-integration #(p/get-transactions access-token external-id start end)
|
||||
bank-account-id)
|
||||
accounts-by-id (by :account_id (:accounts transaction-result))]
|
||||
bank-account-id)
|
||||
accounts-by-id (by :account_id (:accounts transaction-result))]
|
||||
transaction (:transactions transaction-result)]
|
||||
(when (not (:pending transaction))
|
||||
(t/import-transaction! import-batch (assoc (plaid->transaction (assoc transaction
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
(if (and client-id bank-account-id amount)
|
||||
(let [[matching-checks] (d-checks/get-graphql {:client-id client-id
|
||||
:clients [client-id]
|
||||
:bank-account-id bank-account-id
|
||||
:bank-account-id bank-account-id
|
||||
:amount (- amount)
|
||||
:status :payment-status/pending})]
|
||||
(if (= 1 (count matching-checks))
|
||||
@@ -29,7 +29,6 @@
|
||||
nil))
|
||||
nil))
|
||||
|
||||
|
||||
(defn transaction->existing-payment [_ check-number client-id bank-account-id amount id]
|
||||
(alog/info ::searching
|
||||
:client-id client-id
|
||||
@@ -46,7 +45,7 @@
|
||||
check-number
|
||||
(-> (d-checks/get-graphql {:client-id client-id
|
||||
:clients [client-id]
|
||||
:bank-account-id bank-account-id
|
||||
:bank-account-id bank-account-id
|
||||
:check-number check-number
|
||||
:amount (- amount)
|
||||
:status :payment-status/pending})
|
||||
@@ -70,12 +69,12 @@
|
||||
(group-by first) ;; group by vendors
|
||||
vals)
|
||||
considerations (for [candidate-invoices candidate-invoices-vendor-groups
|
||||
invoice-count (range 1 32)
|
||||
consideration (partition invoice-count 1 candidate-invoices)
|
||||
:when (dollars= (reduce (fn [acc [_ _ amount]]
|
||||
(+ acc amount)) 0.0 consideration)
|
||||
(- amount))]
|
||||
consideration)]
|
||||
invoice-count (range 1 32)
|
||||
consideration (partition invoice-count 1 candidate-invoices)
|
||||
:when (dollars= (reduce (fn [acc [_ _ amount]]
|
||||
(+ acc amount)) 0.0 consideration)
|
||||
(- amount))]
|
||||
consideration)]
|
||||
(alog/info ::unfulfilled-autoapayment-considerations
|
||||
:count (count considerations)
|
||||
:amount amount)
|
||||
@@ -85,13 +84,13 @@
|
||||
(alog/info ::searching-unpaid-invoice
|
||||
:client-id client-id
|
||||
:amount amount)
|
||||
(try
|
||||
(try
|
||||
(let [candidate-invoices-vendor-groups (->> (dc/q {:find ['?vendor-id '?e '?outstanding-balance '?d]
|
||||
:in ['$ '?client-id]
|
||||
:where ['[?e :invoice/client ?client-id]
|
||||
'[?e :invoice/status :invoice-status/unpaid]
|
||||
'(not [_ :invoice-payment/invoice ?e])
|
||||
'[?e :invoice/vendor ?vendor-id]
|
||||
'[?e :invoice/vendor ?vendor-id]
|
||||
'[?e :invoice/outstanding-balance ?outstanding-balance]
|
||||
'[?e :invoice/date ?d]]}
|
||||
(dc/db conn) client-id)
|
||||
@@ -110,10 +109,10 @@
|
||||
:amount amount
|
||||
:count (count considerations))
|
||||
considerations)
|
||||
(catch Exception e
|
||||
(alog/error ::cant-get-considerations
|
||||
:error e)
|
||||
[])))
|
||||
(catch Exception e
|
||||
(alog/error ::cant-get-considerations
|
||||
:error e)
|
||||
[])))
|
||||
|
||||
(defn match-transaction-to-single-unfulfilled-autopayments [amount client-id]
|
||||
(let [considerations (match-transaction-to-unfulfilled-autopayments amount client-id)]
|
||||
@@ -134,10 +133,10 @@
|
||||
:transaction/location "A"
|
||||
:transaction/accounts
|
||||
[#:transaction-account
|
||||
{:db/id (random-tempid)
|
||||
:account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
|
||||
:location "A"
|
||||
:amount (Math/abs (:transaction/amount transaction))}])]]
|
||||
{:db/id (random-tempid)
|
||||
:account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
|
||||
:location "A"
|
||||
:amount (Math/abs (:transaction/amount transaction))}])]]
|
||||
|
||||
(conj {:payment/bank-account bank-account-id
|
||||
:payment/client client-id
|
||||
@@ -169,30 +168,26 @@
|
||||
|
||||
(= 1234 (extract-check-number {:transaction/description-original "Check abc 1234"}))
|
||||
|
||||
(= 1234 (extract-check-number {:transaction/description-original "Check abc 4/10 1234"}))
|
||||
(= 1234 (extract-check-number {:transaction/description-original "Check abc 4/10 1234 12/3"}))
|
||||
(= 1234 (extract-check-number {:transaction/description-original "Check abc 4/10 1234"}))
|
||||
(= 1234 (extract-check-number {:transaction/description-original "Check abc 4/10 1234 12/3"}))
|
||||
|
||||
(not= 1234 (extract-check-number {:transaction/description-original "Checkcard 4/10 1234"}))
|
||||
|
||||
)
|
||||
(not= 1234 (extract-check-number {:transaction/description-original "Checkcard 4/10 1234"})))
|
||||
|
||||
(defn find-expected-deposit [client-id amount date]
|
||||
(when date
|
||||
(when date
|
||||
(-> (dc/q
|
||||
'[:find (pull ?ed [:db/id {:expected-deposit/vendor [:db/id]}])
|
||||
:in $ ?c ?a ?d-start
|
||||
'[:find (pull ?ed [:db/id {:expected-deposit/vendor [:db/id]}])
|
||||
:in $ ?c ?a ?d-start
|
||||
:where
|
||||
[?ed :expected-deposit/client ?c]
|
||||
(not [?ed :expected-deposit/status :expected-deposit-status/cleared])
|
||||
[?ed :expected-deposit/date ?d]
|
||||
[(>= ?d ?d-start)]
|
||||
[?ed :expected-deposit/total ?a2]
|
||||
[(auto-ap.utils/dollars= ?a2 ?a)]
|
||||
]
|
||||
[(auto-ap.utils/dollars= ?a2 ?a)]]
|
||||
(dc/db conn) client-id amount (coerce/to-date (t/plus date (t/days -10))))
|
||||
ffirst)))
|
||||
|
||||
|
||||
(defn categorize-transaction [transaction bank-account existing]
|
||||
(cond (= :transaction-approval-status/suppressed (existing (:transaction/id transaction)))
|
||||
:suppressed
|
||||
@@ -235,7 +230,6 @@
|
||||
(assoc transaction :transaction/check-number check-number)
|
||||
transaction))
|
||||
|
||||
|
||||
(defn maybe-clear-payment [{:transaction/keys [check-number client bank-account amount id] :as transaction}]
|
||||
(when-let [existing-payment (transaction->existing-payment transaction check-number client bank-account amount id)]
|
||||
(assoc transaction
|
||||
@@ -245,10 +239,10 @@
|
||||
:transaction/vendor (:db/id (:payment/vendor existing-payment))
|
||||
:transaction/location "A"
|
||||
:transaction/accounts [#:transaction-account
|
||||
{:db/id (random-tempid)
|
||||
:account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
|
||||
:location "A"
|
||||
:amount (Math/abs (double amount))}])))
|
||||
{:db/id (random-tempid)
|
||||
:account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
|
||||
:location "A"
|
||||
:amount (Math/abs (double amount))}])))
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn maybe-autopay-invoices [{:transaction/keys [amount client bank-account] :as transaction}]
|
||||
@@ -266,8 +260,7 @@
|
||||
:transaction-account/amount amount
|
||||
:transaction-account/location "A"}]
|
||||
:transaction/approval-status :transaction-approval-status/approved
|
||||
:transaction/vendor (:db/id (:expected-deposit/vendor expected-deposit))
|
||||
))))
|
||||
:transaction/vendor (:db/id (:expected-deposit/vendor expected-deposit))))))
|
||||
|
||||
(defn maybe-code [{:transaction/keys [client amount] :as transaction} apply-rules valid-locations]
|
||||
(mu/trace
|
||||
@@ -304,7 +297,6 @@
|
||||
(maybe-apply-default-vendor)
|
||||
remove-nils)))
|
||||
|
||||
|
||||
(defn get-existing [bank-account]
|
||||
(into {}
|
||||
(dc/q '[:find ?tid ?as2
|
||||
@@ -317,7 +309,7 @@
|
||||
|
||||
(defprotocol ImportBatch
|
||||
(import-transaction! [this transaction])
|
||||
(get-stats [this ])
|
||||
(get-stats [this])
|
||||
(get-pending-balance [this bank-account])
|
||||
(finish! [this])
|
||||
(fail! [this error]))
|
||||
@@ -326,21 +318,21 @@
|
||||
:db/id
|
||||
:bank-account/locations
|
||||
:bank-account/start-date
|
||||
{:client/_bank-accounts [:client/code :client/locked-until :client/locations :db/id]} ])
|
||||
{:client/_bank-accounts [:client/code :client/locked-until :client/locations :db/id]}])
|
||||
|
||||
(defn start-import-batch [source user]
|
||||
(let [stats (atom {:import-batch/imported 0
|
||||
:import-batch/suppressed 0
|
||||
:import-batch/error 0
|
||||
:import-batch/not-ready 0
|
||||
:import-batch/extant 0})
|
||||
:import-batch/suppressed 0
|
||||
:import-batch/error 0
|
||||
:import-batch/not-ready 0
|
||||
:import-batch/extant 0})
|
||||
pending-balance (atom {})
|
||||
extant-cache (atom (cache/ttl-cache-factory {} :ttl 60000 ))
|
||||
extant-cache (atom (cache/ttl-cache-factory {} :ttl 60000))
|
||||
import-id (get (:tempids @(dc/transact-async conn [{:db/id "import-batch"
|
||||
:import-batch/date (coerce/to-date (t/now))
|
||||
:import-batch/source source
|
||||
:import-batch/status :import-status/started
|
||||
:import-batch/user-name user}])) "import-batch")
|
||||
:import-batch/date (coerce/to-date (t/now))
|
||||
:import-batch/source source
|
||||
:import-batch/status :import-status/started
|
||||
:import-batch/user-name user}])) "import-batch")
|
||||
rule-applying-function (rm/rule-applying-fn (tr/get-all))]
|
||||
(alog/info ::starting-transaction-import
|
||||
:source source)
|
||||
@@ -349,17 +341,17 @@
|
||||
(import-transaction! [_ transaction]
|
||||
(let [bank-account (dc/pull (dc/db conn)
|
||||
bank-account-pull
|
||||
(:transaction/bank-account transaction))
|
||||
(:transaction/bank-account transaction))
|
||||
extant (get (swap! extant-cache cache/through-cache (:transaction/bank-account transaction) get-existing)
|
||||
(:transaction/bank-account transaction))
|
||||
action (categorize-transaction transaction bank-account extant)]
|
||||
(try
|
||||
(try
|
||||
(when (not= "POSTED" (:transaction/status transaction))
|
||||
|
||||
(swap! pending-balance (fn [pb]
|
||||
(update pb
|
||||
(update pb
|
||||
(:transaction/bank-account transaction)
|
||||
(fnil + 0.0)
|
||||
(fnil + 0.0)
|
||||
(:transaction/amount transaction)))))
|
||||
(catch Exception e
|
||||
(alog/warn ::cant-capture-pending
|
||||
@@ -372,7 +364,7 @@
|
||||
:error :import-batch/error
|
||||
:not-ready :import-batch/not-ready) inc))
|
||||
(when (= :import action)
|
||||
(try
|
||||
(try
|
||||
(let [result (audit-transact [[:upsert-transaction (transaction->txs transaction bank-account rule-applying-function)]
|
||||
{:db/id import-id
|
||||
:import-batch/entry (:db/id transaction)}]
|
||||
@@ -390,14 +382,14 @@
|
||||
(get-stats [_]
|
||||
@stats)
|
||||
(get-pending-balance [_ bank-account]
|
||||
(or (get @pending-balance bank-account)
|
||||
0.0))
|
||||
(or (get @pending-balance bank-account)
|
||||
0.0))
|
||||
|
||||
(fail! [_ error]
|
||||
(alog/error ::cant-complete-import
|
||||
:import-id import-id
|
||||
:error error)
|
||||
|
||||
|
||||
@(dc/transact-async conn [(merge {:db/id import-id
|
||||
:import-batch/status :import-status/completed
|
||||
:import-batch/error-message (str error)}
|
||||
@@ -407,12 +399,10 @@
|
||||
(alog/info ::finished :import-id import-id :source source :stats (pr-str @stats))
|
||||
@(dc/transact conn [(merge {:db/id import-id
|
||||
|
||||
:import-batch/status :import-status/completed}
|
||||
@stats)])))))
|
||||
:import-batch/status :import-status/completed}
|
||||
@stats)])))))
|
||||
|
||||
|
||||
|
||||
(defn synthetic-key [{:transaction/keys [date bank-account description-original amount client] } index]
|
||||
(defn synthetic-key [{:transaction/keys [date bank-account description-original amount client]} index]
|
||||
(str (str (some-> date coerce/to-date-time atime/localize)) "-" bank-account "-" description-original "-" amount "-" index "-" client))
|
||||
|
||||
(defn apply-synthetic-ids [transactions]
|
||||
@@ -424,7 +414,7 @@
|
||||
(let [raw-id (synthetic-key transaction index)]
|
||||
(assoc transaction
|
||||
:transaction/id #_{:clj-kondo/ignore [:unresolved-var]}
|
||||
(di/sha-256 raw-id)
|
||||
(di/sha-256 raw-id)
|
||||
:transaction/raw-id raw-id
|
||||
:db/id (random-tempid))))
|
||||
(range)
|
||||
|
||||
@@ -73,10 +73,10 @@
|
||||
(alog/info ::finished-import)
|
||||
(t/finish! import-batch)
|
||||
(doseq [[_ bank-account _ _ ya] account-lookup]
|
||||
(try
|
||||
(try
|
||||
@(dc/transact auto-ap.datomic/conn
|
||||
[{:db/id ya
|
||||
:yodlee-account/pending-balance (t/get-pending-balance import-batch bank-account)}])
|
||||
[{:db/id ya
|
||||
:yodlee-account/pending-balance (t/get-pending-balance import-batch bank-account)}])
|
||||
(catch Exception e
|
||||
(alog/error ::cant-persist-yodlee-account-pending-balance
|
||||
:error e)))))
|
||||
@@ -95,5 +95,4 @@
|
||||
nil)
|
||||
(Thread/sleep 10000)))))
|
||||
|
||||
|
||||
(def import-yodlee2 (allow-once import-yodlee2-int))
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
;; (def base-url "https://sandbox-quickbooks.api.intuit.com/v3")
|
||||
|
||||
|
||||
(def prod-client-id "ABFRwAiOqQiLN66HKplXfyRE3ipD390DHsrUquflRCiOa81mxa")
|
||||
(def prod-client-secret "xDUj04GeQXpLvrhxep1jjDYwjJWbzzOPrirUQTKF")
|
||||
|
||||
@@ -27,21 +26,18 @@
|
||||
;; "accessToken":,
|
||||
;;
|
||||
|
||||
|
||||
|
||||
(def prod-company-id "123146163906404")
|
||||
|
||||
|
||||
(def prod-base-url "https://quickbooks.api.intuit.com/v3")
|
||||
|
||||
(defn set-access-token [t]
|
||||
(s3/put-object :bucket-name (:data-bucket env)
|
||||
(s3/put-object :bucket-name (:data-bucket env)
|
||||
:key (str "intuit/access-token")
|
||||
:input-stream (io/make-input-stream (.getBytes t) {})
|
||||
:metadata {:content-type "application/text"
|
||||
:content-length (count (.getBytes t))}))
|
||||
(defn set-refresh-token [t]
|
||||
(s3/put-object :bucket-name (:data-bucket env)
|
||||
(s3/put-object :bucket-name (:data-bucket env)
|
||||
:key (str "intuit/refresh-token")
|
||||
:input-stream (io/make-input-stream (.getBytes t) {})
|
||||
:metadata {:content-type "application/text"
|
||||
@@ -53,7 +49,6 @@
|
||||
:bucket-name "data.prod.app.integreatconsult.com"
|
||||
:key "intuit/refresh-token")))))
|
||||
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn init-tokens [access refresh]
|
||||
(set-access-token access)
|
||||
@@ -74,17 +69,16 @@
|
||||
(defn get-basic-auth []
|
||||
(Base64/encodeBase64String (.getBytes (str prod-client-id ":" prod-client-secret))))
|
||||
|
||||
|
||||
(defn get-fresh-access-token []
|
||||
(let [refresh-token (lookup-refresh-token)
|
||||
response (:body (client/post (str "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer" )
|
||||
response (:body (client/post (str "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer")
|
||||
|
||||
{:headers {"Accept" "application/json"
|
||||
"Content-Type" "application/x-www-form-urlencoded"
|
||||
"Authorization" (str "Basic " (get-basic-auth))}
|
||||
:form-params {"grant_type" "refresh_token"
|
||||
"refresh_token" refresh-token}
|
||||
:as :json}))]
|
||||
{:headers {"Accept" "application/json"
|
||||
"Content-Type" "application/x-www-form-urlencoded"
|
||||
"Authorization" (str "Basic " (get-basic-auth))}
|
||||
:form-params {"grant_type" "refresh_token"
|
||||
"refresh_token" refresh-token}
|
||||
:as :json}))]
|
||||
(set-access-token (:access_token response))
|
||||
(set-refresh-token (:refresh_token response))
|
||||
(:access_token response)))
|
||||
@@ -94,21 +88,20 @@
|
||||
(defn with-auth [t token]
|
||||
(assoc t "Authorization" (str "Bearer " token)))
|
||||
|
||||
#_(client/get (str base-url "/company/4620816365202617680")
|
||||
{:headers base-headers
|
||||
:as :json})
|
||||
#_(client/get (str base-url "/company/4620816365202617680")
|
||||
{:headers base-headers
|
||||
:as :json})
|
||||
|
||||
(defn get-bank-accounts-raw [token]
|
||||
(->> (:body (client/get (str prod-base-url "/company/" prod-company-id "/query" )
|
||||
(->> (:body (client/get (str prod-base-url "/company/" prod-company-id "/query")
|
||||
{:headers
|
||||
(with-auth prod-base-headers token)
|
||||
:as :json
|
||||
:query-params {"query" "SELECT * From Account maxresults 1000"}}))
|
||||
:QueryResponse))
|
||||
|
||||
|
||||
(defn get-bank-accounts [token]
|
||||
(->> (:body (client/get (str prod-base-url "/company/" prod-company-id "/query" )
|
||||
(->> (:body (client/get (str prod-base-url "/company/" prod-company-id "/query")
|
||||
{:headers
|
||||
(with-auth prod-base-headers token)
|
||||
:as :json
|
||||
@@ -116,7 +109,7 @@
|
||||
:QueryResponse
|
||||
:Account
|
||||
#_(filter
|
||||
#(#{"Bank" "Credit Card"} (:AccountType %)))
|
||||
#(#{"Bank" "Credit Card"} (:AccountType %)))
|
||||
(map (juxt :Id :Name :CurrentBalance :MetaData))
|
||||
(map (fn [[id name current-balance metadata]]
|
||||
{:id id
|
||||
@@ -124,10 +117,9 @@
|
||||
:last-updated (c/to-date-time (-> metadata :LastUpdatedTime))
|
||||
:current-balance (try (double current-balance) (catch Exception _ nil))}))))
|
||||
|
||||
|
||||
(defn get-all-transactions [start end]
|
||||
(let [token (get-fresh-access-token)]
|
||||
(:body (client/get (str prod-base-url "/company/" prod-company-id "/reports/TransactionList" "?minorversion=63&start_date=" start "&end_date=" end)
|
||||
(:body (client/get (str prod-base-url "/company/" prod-company-id "/reports/TransactionList" "?minorversion=63&start_date=" start "&end_date=" end)
|
||||
{:headers (with-auth prod-base-headers token)
|
||||
:as :json}))))
|
||||
|
||||
|
||||
@@ -12,12 +12,11 @@
|
||||
(defn line->id [{:keys [source id client-code]}]
|
||||
(str client-code "-" source "-" id))
|
||||
|
||||
|
||||
(defn csv->graphql-rows [lines]
|
||||
(for [lines (partition-by line->id (drop 1 lines))
|
||||
:let [{:keys [source client-code date vendor-name note cleared-against] :as line} (first lines)]]
|
||||
:let [{:keys [source client-code date vendor-name note cleared-against] :as line} (first lines)]]
|
||||
{:source source
|
||||
:external_id (line->id line)
|
||||
:external_id (line->id line)
|
||||
:client_code client-code
|
||||
:date date
|
||||
:note note
|
||||
@@ -33,8 +32,8 @@
|
||||
{:account_identifier account-identifier
|
||||
:location (some-> location str/trim)
|
||||
:debit (if (str/blank? debit)
|
||||
0.0
|
||||
(Double/parseDouble debit))
|
||||
0.0
|
||||
(Double/parseDouble debit))
|
||||
:credit (if (str/blank? credit)
|
||||
0.0
|
||||
(Double/parseDouble credit))})
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
|
||||
(mapv (fn [[i]] {:db/id i
|
||||
:invoice/outstanding-balance 0.0
|
||||
:invoice/status :invoice-status/paid}))
|
||||
))
|
||||
:invoice/status :invoice-status/paid}))))
|
||||
|
||||
(alog/info ::closed :count (count invoices-to-close))))
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns auto-ap.jobs.core
|
||||
(:require [auto-ap.utils :refer [heartbeat]]
|
||||
[mount.core :as mount]
|
||||
[auto-ap.datomic :refer [conn ]]
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.logging :as alog]
|
||||
[nrepl.server :refer [start-server]]
|
||||
[auto-ap.background.metrics :refer [metrics-setup container-tags container-data logging-context]]
|
||||
@@ -13,8 +13,8 @@
|
||||
:service name}
|
||||
(mu/trace ::execute-background-job
|
||||
[]
|
||||
(try
|
||||
(mount/start (mount/only #{#'conn #'metrics-setup #'container-tags #'logging-context #'container-data }))
|
||||
(try
|
||||
(mount/start (mount/only #{#'conn #'metrics-setup #'container-tags #'logging-context #'container-data}))
|
||||
(start-server :port 9000 :bind "0.0.0.0" #_#_:handler (cider-nrepl-handler))
|
||||
((heartbeat f name))
|
||||
(alog/info ::stopping :job name)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
(javax.mail Session)
|
||||
(javax.mail.internet MimeMessage)))
|
||||
|
||||
(defn send-email-about-failed-message [mail-bucket mail-key message]
|
||||
(defn send-email-about-failed-message [mail-bucket mail-key message]
|
||||
(let [target-key (str "failed-emails/" mail-key ".eml")
|
||||
target-url (str "https://" (:data-bucket env) "/" target-key)]
|
||||
(alog/info ::sending-failure-email :who (:import-failure-destination-emails env))
|
||||
@@ -29,7 +29,6 @@
|
||||
:body {:html (str "<div>You can download the original email <a href=\"" target-url "\">here</a>.<p><pre>" message "</pre></p></div>")
|
||||
:text (str "<div>You can download the original email here: " target-url)}}})))
|
||||
|
||||
|
||||
(defn process-sqs []
|
||||
(alog/info ::fetching-sqs)
|
||||
(doseq [message (:messages (sqs/receive-message {:queue-url (:invoice-import-queue-url env)
|
||||
@@ -79,27 +78,20 @@
|
||||
(defn -main [& _]
|
||||
(execute "import-uploaded-invoices" process-sqs))
|
||||
|
||||
|
||||
(comment
|
||||
(comment
|
||||
(with-open [i (io/output-stream "/tmp/bryce.pdf")]
|
||||
(clojure.java.io/copy
|
||||
(clojure.java.io/copy
|
||||
(-> (s3/get-object :bucket-name (:data-bucket env)
|
||||
:key "invoice-files/f0e73dcb-e5e5-4c81-b82b-319b7caedacf.pdf"
|
||||
|
||||
)
|
||||
:key "invoice-files/f0e73dcb-e5e5-4c81-b82b-319b7caedacf.pdf")
|
||||
|
||||
:input-stream)
|
||||
i))
|
||||
|
||||
(parse/parse-file "/tmp/bryce.pdf" "/tmp/bryce.pdf")
|
||||
|
||||
|
||||
(-> (clojure.java.shell/sh "pdftotext" "-layout" "/tmp/bryce.pdf" "-")
|
||||
:out
|
||||
)
|
||||
(-> (clojure.java.shell/sh "pdftotext" "-layout" "/tmp/bryce.pdf" "-")
|
||||
:out)
|
||||
|
||||
|
||||
1
|
||||
|
||||
(user/init-repl)
|
||||
|
||||
)
|
||||
(user/init-repl))
|
||||
@@ -23,39 +23,39 @@
|
||||
[?t :transaction/client ?c]])))
|
||||
|
||||
(defn get-pinecone [transaction-id]
|
||||
(->
|
||||
(http2/get (-> "https://transactions-a8257ba.svc.us-west4-gcp-free.pinecone.io/vectors/fetch"
|
||||
(->
|
||||
(http2/get (-> "https://transactions-a8257ba.svc.us-west4-gcp-free.pinecone.io/vectors/fetch"
|
||||
url/url
|
||||
(assoc :query {:ids transaction-id})
|
||||
str)
|
||||
{:headers {"Api-Key" "f2d3a78e-bcea-4fcd-88b6-2527b8423607"}
|
||||
:as :json
|
||||
:keywordize? false})
|
||||
:body
|
||||
:vectors
|
||||
((keyword (str transaction-id)))
|
||||
:values))
|
||||
:body
|
||||
:vectors
|
||||
((keyword (str transaction-id)))
|
||||
:values))
|
||||
|
||||
(defn get-pinecone-similarities [transaction-id]
|
||||
(if-let [vector (get-pinecone transaction-id)]
|
||||
(filter
|
||||
(fn [{:keys [score]}]
|
||||
(> score 0.95))
|
||||
(->
|
||||
(http2/post (-> "https://transactions-a8257ba.svc.us-west4-gcp-free.pinecone.io/query"
|
||||
url/url
|
||||
str)
|
||||
{:headers {"Api-Key" "f2d3a78e-bcea-4fcd-88b6-2527b8423607"}
|
||||
:form-params {"vector" vector
|
||||
"topK" 200,
|
||||
"includeMetadata" true
|
||||
"namespace" ""}
|
||||
:content-type :json
|
||||
:as :json})
|
||||
:body
|
||||
:matches
|
||||
(doto (#(alog/info ::similarities-found :transaction transaction-id :count (count %) :sample (take 3 %))))))
|
||||
|
||||
(filter
|
||||
(fn [{:keys [score]}]
|
||||
(> score 0.95))
|
||||
(->
|
||||
(http2/post (-> "https://transactions-a8257ba.svc.us-west4-gcp-free.pinecone.io/query"
|
||||
url/url
|
||||
str)
|
||||
{:headers {"Api-Key" "f2d3a78e-bcea-4fcd-88b6-2527b8423607"}
|
||||
:form-params {"vector" vector
|
||||
"topK" 200,
|
||||
"includeMetadata" true
|
||||
"namespace" ""}
|
||||
:content-type :json
|
||||
:as :json})
|
||||
:body
|
||||
:matches
|
||||
(doto (#(alog/info ::similarities-found :transaction transaction-id :count (count %) :sample (take 3 %))))))
|
||||
|
||||
(do (alog/info ::no-matches-for :transaction transaction-id)
|
||||
[])))
|
||||
|
||||
|
||||
@@ -10,16 +10,15 @@
|
||||
[config.core :refer [env]]
|
||||
[datomic.api :as dc]))
|
||||
|
||||
|
||||
(defn historical-load-sales [client days]
|
||||
(alog/info ::new-sales-loading :client (:client/code client) :days days)
|
||||
(let [client (dc/pull (dc/db auto-ap.datomic/conn)
|
||||
square3/square-read
|
||||
client)
|
||||
days (cond-> days (string? days) ( #(Long/parseLong %)))]
|
||||
square3/square-read
|
||||
client)
|
||||
days (cond-> days (string? days) (#(Long/parseLong %)))]
|
||||
(doseq [square-location (:client/square-locations client)
|
||||
:when (:square-location/client-location square-location)]
|
||||
|
||||
|
||||
(println "orders")
|
||||
(doseq [d (per/periodic-seq (time/plus (time/today) (time/days (- days)))
|
||||
(time/plus (time/today) (time/days 2))
|
||||
@@ -28,14 +27,13 @@
|
||||
@(square3/upsert client square-location (coerce/to-date-time d) (coerce/to-date-time (time/plus d (time/days 1)))))
|
||||
|
||||
(println "refunds")
|
||||
@(square3/upsert-refunds client square-location)
|
||||
@(square3/upsert-payouts client square-location (time/plus (time/now) (time/days (- days))) (time/now)))))
|
||||
|
||||
@(square3/upsert-refunds client square-location)
|
||||
@(square3/upsert-payouts client square-location (time/plus (time/now) (time/days (- days))) (time/now)))))
|
||||
|
||||
(defn load-historical-sales [args]
|
||||
(let [{:keys [days client]} args
|
||||
client (cond-> client
|
||||
( string? client) ( #( Long/parseLong %)))]
|
||||
client (cond-> client
|
||||
(string? client) (#(Long/parseLong %)))]
|
||||
(historical-load-sales client days)))
|
||||
|
||||
(defn -main [& _]
|
||||
|
||||
@@ -28,19 +28,17 @@
|
||||
(defn read-xml [stream]
|
||||
(-> (slurp stream)
|
||||
(.getBytes)
|
||||
(java.io.ByteArrayInputStream. )
|
||||
(java.io.ByteArrayInputStream.)
|
||||
xml/parse
|
||||
zip/xml-zip))
|
||||
|
||||
|
||||
|
||||
(defn mark-key [k]
|
||||
(s3/copy-object {:source-bucket-name bucket-name
|
||||
:destination-bucket-name bucket-name
|
||||
:destination-key (str/replace-first k "pending" "imported")
|
||||
:source-key k})
|
||||
#_(s3/delete-object {:bucket-name bucket-name
|
||||
:key k}))
|
||||
:key k}))
|
||||
|
||||
(defn is-csv-file? [x]
|
||||
(= "dat" (last (str/split x #"[\\.]"))))
|
||||
@@ -54,7 +52,7 @@
|
||||
(and (str/includes? k "GeneralProduce")
|
||||
(str/includes? k "FRANCHISEE")
|
||||
(is-csv-file? k))
|
||||
:general-produce
|
||||
:general-produce
|
||||
|
||||
:else
|
||||
:unknown))
|
||||
@@ -66,15 +64,15 @@
|
||||
[k input-stream clients]
|
||||
(log/info ::parsing-general-produce :key k)
|
||||
(let [missing-client-hints (atom #{})]
|
||||
(try
|
||||
(try
|
||||
(->> (read-csv input-stream)
|
||||
(drop 1)
|
||||
#_(filter (fn [[_ _ _ _ _ _ _ _ _ _ _ break-flag]]
|
||||
(= "Y" break-flag)))
|
||||
(map (fn [[_ location-hint invoice-number ship-date invoice-total ]]
|
||||
(map (fn [[_ location-hint invoice-number ship-date invoice-total]]
|
||||
(let [matching-client (and location-hint
|
||||
(parse/exact-match clients location-hint))
|
||||
location (parse/best-location-match matching-client location-hint location-hint )
|
||||
location (parse/best-location-match matching-client location-hint location-hint)
|
||||
vendor (d/pull (d/db conn) '[:vendor/default-account] :vendor/general-produce)]
|
||||
(when-not (and matching-client
|
||||
(not (@missing-client-hints location-hint))
|
||||
@@ -99,8 +97,7 @@
|
||||
(-> vendor :vendor/default-account :db/id)
|
||||
:invoice-expense-account/location location
|
||||
:invoice-expense-account/amount (Math/abs (Double/parseDouble invoice-total))
|
||||
:db/id (random-tempid)
|
||||
}]})))
|
||||
:db/id (random-tempid)}]})))
|
||||
(filter :invoice/client)
|
||||
(reduce (fn [[seen-so-far list] i]
|
||||
(let [k [(:invoice/invoice-number i) (:invoice/client i)]]
|
||||
@@ -108,8 +105,7 @@
|
||||
[seen-so-far list]
|
||||
[(conj seen-so-far k) (conj list i)])))
|
||||
[#{} []])
|
||||
(second)
|
||||
)
|
||||
(second))
|
||||
(catch Exception e
|
||||
(log/error ::cant-import-general-produce
|
||||
:error e)
|
||||
@@ -123,8 +119,8 @@
|
||||
|
||||
(defn zip-seq [zipper]
|
||||
(->> (zip/xml-zip (zip/node zipper))
|
||||
(iterate zip/next )
|
||||
(take-while (complement zip/end?))))
|
||||
(iterate zip/next)
|
||||
(take-while (complement zip/end?))))
|
||||
|
||||
(defmethod extract-invoice-details :cintas
|
||||
[k input-stream clients]
|
||||
@@ -160,10 +156,10 @@
|
||||
atime/localize
|
||||
(atime/unparse atime/iso-date)
|
||||
(atime/parse atime/iso-date))))
|
||||
location (parse/best-location-match matching-client location-hint location-hint )
|
||||
location (parse/best-location-match matching-client location-hint location-hint)
|
||||
due (-> invoice-date
|
||||
(time/plus (time/days 30))
|
||||
(coerce/to-date))
|
||||
(time/plus (time/days 30))
|
||||
(coerce/to-date))
|
||||
total (->> node-seq
|
||||
(filter (fn [zipper]
|
||||
(= (:tag (zip/node zipper))
|
||||
@@ -178,7 +174,7 @@
|
||||
:content
|
||||
first
|
||||
Double/parseDouble)
|
||||
invoice {:db/id (random-tempid )
|
||||
invoice {:db/id (random-tempid)
|
||||
:invoice/vendor :vendor/cintas
|
||||
:invoice/import-status :import-status/imported
|
||||
:invoice/status :invoice-status/unpaid
|
||||
@@ -188,37 +184,36 @@
|
||||
:invoice/total total
|
||||
:invoice/outstanding-balance total
|
||||
:invoice/invoice-number (->> node-seq
|
||||
(map zip/node)
|
||||
(filter (fn [node]
|
||||
(= (:tag node)
|
||||
:InvoiceDetailRequestHeader)))
|
||||
first
|
||||
(#(-> % :attrs :invoiceID)))
|
||||
(map zip/node)
|
||||
(filter (fn [node]
|
||||
(= (:tag node)
|
||||
:InvoiceDetailRequestHeader)))
|
||||
first
|
||||
(#(-> % :attrs :invoiceID)))
|
||||
:invoice/due due
|
||||
|
||||
:invoice/scheduled-payment (when-not ((into #{} (->> matching-client
|
||||
:client/feature-flags))
|
||||
"manually-pay-cintas")
|
||||
due)
|
||||
due)
|
||||
|
||||
:invoice/date (coerce/to-date invoice-date)
|
||||
:invoice/expense-accounts [{:invoice-expense-account/account
|
||||
(-> vendor :vendor/default-account :db/id)
|
||||
:invoice-expense-account/location location
|
||||
:invoice-expense-account/amount (Math/abs total)
|
||||
:db/id (random-tempid)
|
||||
}]}]
|
||||
:db/id (random-tempid)}]}]
|
||||
(log/info ::cintas-invoice-importing
|
||||
:invoice invoice)
|
||||
[invoice])
|
||||
(do
|
||||
(do
|
||||
;; disabling logging for cintas
|
||||
#_(log/warn ::missing-client
|
||||
:client-hint location-hint)
|
||||
:client-hint location-hint)
|
||||
[]))))
|
||||
|
||||
(defn mark-error [k]
|
||||
(s3/copy-object {:source-bucket-name bucket-name
|
||||
(s3/copy-object {:source-bucket-name bucket-name
|
||||
:destination-bucket-name bucket-name
|
||||
:source-key k
|
||||
:destination-key (str "ntg-invoices/error/"
|
||||
@@ -232,17 +227,17 @@
|
||||
(s3/copy-object {:source-bucket-name bucket-name
|
||||
:destination-bucket-name bucket-name
|
||||
:source-key k
|
||||
:destination-key invoice-key })
|
||||
:destination-key invoice-key})
|
||||
invoice-key))
|
||||
|
||||
(defn get-all-keys
|
||||
([]
|
||||
(let [first-page-result (s3/list-objects-v2 {:bucket-name bucket-name
|
||||
(let [first-page-result (s3/list-objects-v2 {:bucket-name bucket-name
|
||||
:prefix "ntg-invoices/pending"})]
|
||||
(lazy-seq (concat (:object-summaries first-page-result) (get-all-keys (:next-continuation-token first-page-result))))))
|
||||
([next-token ]
|
||||
(when next-token
|
||||
(let [page-result (s3/list-objects-v2 {:bucket-name bucket-name
|
||||
([next-token]
|
||||
(when next-token
|
||||
(let [page-result (s3/list-objects-v2 {:bucket-name bucket-name
|
||||
:prefix "ntg-invoices/pending"
|
||||
:continuation-token next-token})]
|
||||
(println "getting next page " next-token)
|
||||
@@ -250,60 +245,58 @@
|
||||
(lazy-seq (concat (:object-summaries page-result) (get-all-keys (:next-continuation-token page-result)))))))))
|
||||
|
||||
(defn recent? [k]
|
||||
(time/after? (:last-modified k) (time/plus (time/now) (time/days -15)))
|
||||
)
|
||||
(time/after? (:last-modified k) (time/plus (time/now) (time/days -15))))
|
||||
|
||||
(defn import-ntg-invoices
|
||||
([] (import-ntg-invoices (->> (get-all-keys)
|
||||
(filter recent?)
|
||||
(map :key))))
|
||||
([keys]
|
||||
(let [clients (map first (d/q '[:find (pull ?c [:client/code
|
||||
:db/id
|
||||
:client/feature-flags
|
||||
{:client/location-matches [:location-match/matches :location-match/location]}
|
||||
:client/name
|
||||
:client/matches
|
||||
:client/locations])
|
||||
:where [?c :client/code]]
|
||||
(d/db conn)))]
|
||||
(log/info ::found-invoice-keys
|
||||
:keys keys )
|
||||
(let [transaction (->> keys
|
||||
(mapcat (fn [k]
|
||||
(try
|
||||
(let [invoice-key (copy-readable-version k)
|
||||
invoice-url (str "https://" bucket-name "/" invoice-key)]
|
||||
(with-open [is (-> (s3/get-object {:bucket-name bucket-name
|
||||
:key k})
|
||||
:input-stream)]
|
||||
(->> (extract-invoice-details k
|
||||
is
|
||||
clients)
|
||||
(set)
|
||||
(map (fn [i]
|
||||
(log/info ::importing-invoice
|
||||
:invoice i)
|
||||
i))
|
||||
(mapv (fn [i]
|
||||
(if (= :vendor/cintas (:invoice/vendor i))
|
||||
[:propose-invoice (assoc i :invoice/source-url invoice-url)]
|
||||
[:propose-invoice i]))))))
|
||||
(catch Exception e
|
||||
(log/error ::cant-load-file
|
||||
:key k
|
||||
:exception e)
|
||||
(mark-error k)
|
||||
[]))))
|
||||
(into []))]
|
||||
(doseq [t transaction]
|
||||
(audit-transact [t] {:user/name "sysco importer" :user/role "admin"}))
|
||||
(log/info ::success
|
||||
:count (count transaction)
|
||||
:sample (take 3 transaction)))
|
||||
(doseq [k keys]
|
||||
(mark-key k)))))
|
||||
|
||||
(let [clients (map first (d/q '[:find (pull ?c [:client/code
|
||||
:db/id
|
||||
:client/feature-flags
|
||||
{:client/location-matches [:location-match/matches :location-match/location]}
|
||||
:client/name
|
||||
:client/matches
|
||||
:client/locations])
|
||||
:where [?c :client/code]]
|
||||
(d/db conn)))]
|
||||
(log/info ::found-invoice-keys
|
||||
:keys keys)
|
||||
(let [transaction (->> keys
|
||||
(mapcat (fn [k]
|
||||
(try
|
||||
(let [invoice-key (copy-readable-version k)
|
||||
invoice-url (str "https://" bucket-name "/" invoice-key)]
|
||||
(with-open [is (-> (s3/get-object {:bucket-name bucket-name
|
||||
:key k})
|
||||
:input-stream)]
|
||||
(->> (extract-invoice-details k
|
||||
is
|
||||
clients)
|
||||
(set)
|
||||
(map (fn [i]
|
||||
(log/info ::importing-invoice
|
||||
:invoice i)
|
||||
i))
|
||||
(mapv (fn [i]
|
||||
(if (= :vendor/cintas (:invoice/vendor i))
|
||||
[:propose-invoice (assoc i :invoice/source-url invoice-url)]
|
||||
[:propose-invoice i]))))))
|
||||
(catch Exception e
|
||||
(log/error ::cant-load-file
|
||||
:key k
|
||||
:exception e)
|
||||
(mark-error k)
|
||||
[]))))
|
||||
(into []))]
|
||||
(doseq [t transaction]
|
||||
(audit-transact [t] {:user/name "sysco importer" :user/role "admin"}))
|
||||
(log/info ::success
|
||||
:count (count transaction)
|
||||
:sample (take 3 transaction)))
|
||||
(doseq [k keys]
|
||||
(mark-key k)))))
|
||||
|
||||
(defn -main [& _]
|
||||
(execute "ntg" import-ntg-invoices))
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
(def bucket (:data-bucket env))
|
||||
|
||||
(defn s3->csv [url]
|
||||
(try
|
||||
(try
|
||||
(->> (-> (s3/get-object {:bucket-name bucket
|
||||
:key (str "bulk-import/" url)})
|
||||
:input-stream
|
||||
@@ -26,9 +26,9 @@
|
||||
csv/read-csv))
|
||||
(catch Exception e
|
||||
(alog/error
|
||||
:file-not-found
|
||||
:error e
|
||||
:url url)
|
||||
:file-not-found
|
||||
:error e
|
||||
:url url)
|
||||
(throw e))))
|
||||
|
||||
(defn register-invoice-import* [data]
|
||||
@@ -45,106 +45,100 @@
|
||||
(reduce + 0.0
|
||||
(->> values
|
||||
(map (fn [[_ _ _ _ amount]]
|
||||
(- (Double/parseDouble amount))))))
|
||||
]))
|
||||
(- (Double/parseDouble amount))))))]))
|
||||
(into {}))]
|
||||
(->>
|
||||
(for [[i
|
||||
invoice-expense-account-id
|
||||
target-account
|
||||
target-date
|
||||
amount
|
||||
_
|
||||
location] (drop 1 data)
|
||||
:let [invoice-id (i->invoice-id i)
|
||||
(->>
|
||||
(for [[i
|
||||
invoice-expense-account-id
|
||||
target-account
|
||||
target-date
|
||||
amount
|
||||
_
|
||||
location] (drop 1 data)
|
||||
:let [invoice-id (i->invoice-id i)
|
||||
|
||||
invoice (dc/pull db '[*] invoice-id)
|
||||
current-total (:invoice/total invoice)
|
||||
target-total (invoice-totals invoice-id) ;; TODO should include expense accounts not visible
|
||||
new-account? (not (boolean (or (some-> invoice-expense-account-id not-empty Long/parseLong)
|
||||
(:db/id (first (:invoice/expense-accounts invoice))))))
|
||||
invoice (dc/pull db '[*] invoice-id)
|
||||
current-total (:invoice/total invoice)
|
||||
target-total (invoice-totals invoice-id) ;; TODO should include expense accounts not visible
|
||||
new-account? (not (boolean (or (some-> invoice-expense-account-id not-empty Long/parseLong)
|
||||
(:db/id (first (:invoice/expense-accounts invoice))))))
|
||||
|
||||
invoice-expense-account-id (or (some-> invoice-expense-account-id not-empty Long/parseLong)
|
||||
(:db/id (first (:invoice/expense-accounts invoice)))
|
||||
(str (UUID/randomUUID)))
|
||||
invoice-expense-account (when-not new-account?
|
||||
(or (dc/pull db '[*] invoice-expense-account-id)
|
||||
(dc/pull db '[*] [:invoice-expense-account/original-id invoice-expense-account-id])))
|
||||
current-account-id (:db/id (:invoice-expense-account/account invoice-expense-account))
|
||||
target-account-id (Long/parseLong (str/trim target-account))
|
||||
invoice-expense-account-id (or (some-> invoice-expense-account-id not-empty Long/parseLong)
|
||||
(:db/id (first (:invoice/expense-accounts invoice)))
|
||||
(str (UUID/randomUUID)))
|
||||
invoice-expense-account (when-not new-account?
|
||||
(or (dc/pull db '[*] invoice-expense-account-id)
|
||||
(dc/pull db '[*] [:invoice-expense-account/original-id invoice-expense-account-id])))
|
||||
current-account-id (:db/id (:invoice-expense-account/account invoice-expense-account))
|
||||
target-account-id (Long/parseLong (str/trim target-account))
|
||||
|
||||
target-date (coerce/to-date (atime/parse target-date atime/normal-date))
|
||||
current-date (:invoice/date invoice)
|
||||
|
||||
target-date (coerce/to-date (atime/parse target-date atime/normal-date))
|
||||
current-date (:invoice/date invoice)
|
||||
|
||||
current-expense-account-amount (:invoice-expense-account/amount invoice-expense-account 0.0)
|
||||
target-expense-account-amount (- (Double/parseDouble amount))
|
||||
current-expense-account-amount (:invoice-expense-account/amount invoice-expense-account 0.0)
|
||||
target-expense-account-amount (- (Double/parseDouble amount))
|
||||
|
||||
current-expense-account-location (:invoice-expense-account/location invoice-expense-account)
|
||||
target-expense-account-location location
|
||||
|
||||
current-expense-account-location (:invoice-expense-account/location invoice-expense-account)
|
||||
target-expense-account-location location
|
||||
[[_ _ invoice-payment]] (vec (dc/q
|
||||
'[:find ?p ?a ?ip
|
||||
:in $ ?i
|
||||
:where [?ip :invoice-payment/invoice ?i]
|
||||
[?ip :invoice-payment/amount ?a]
|
||||
[?ip :invoice-payment/payment ?p]]
|
||||
db invoice-id))]
|
||||
:when current-total]
|
||||
|
||||
[(when (not (dollars= current-total target-total))
|
||||
{:db/id invoice-id
|
||||
:invoice/total target-total})
|
||||
|
||||
[[_ _ invoice-payment]] (vec (dc/q
|
||||
'[:find ?p ?a ?ip
|
||||
:in $ ?i
|
||||
:where [?ip :invoice-payment/invoice ?i]
|
||||
[?ip :invoice-payment/amount ?a]
|
||||
[?ip :invoice-payment/payment ?p]
|
||||
]
|
||||
db invoice-id))]
|
||||
:when current-total]
|
||||
(when (and (not (dollars= 0.0 target-total))
|
||||
(= :invoice-status/voided (:db/ident (:invoice/status invoice))))
|
||||
{:db/id invoice-id
|
||||
:invoice/total target-total
|
||||
:invoice/status :invoice-status/paid})
|
||||
|
||||
[
|
||||
(when (not (dollars= current-total target-total))
|
||||
{:db/id invoice-id
|
||||
:invoice/total target-total})
|
||||
(when new-account?
|
||||
{:db/id invoice-id
|
||||
:invoice/expense-accounts invoice-expense-account-id})
|
||||
|
||||
(when (and (not (dollars= 0.0 target-total))
|
||||
(= :invoice-status/voided (:db/ident (:invoice/status invoice))))
|
||||
{:db/id invoice-id
|
||||
:invoice/total target-total
|
||||
:invoice/status :invoice-status/paid})
|
||||
(when (and target-date (not= current-date target-date))
|
||||
{:db/id invoice-id
|
||||
:invoice/date target-date})
|
||||
|
||||
(when new-account?
|
||||
{:db/id invoice-id
|
||||
:invoice/expense-accounts invoice-expense-account-id})
|
||||
(when (and
|
||||
(not (dollars= current-total target-total))
|
||||
invoice-payment)
|
||||
[:db/retractEntity invoice-payment])
|
||||
|
||||
(when (and target-date (not= current-date target-date))
|
||||
{:db/id invoice-id
|
||||
:invoice/date target-date})
|
||||
(when (or new-account?
|
||||
(not (dollars= current-expense-account-amount target-expense-account-amount)))
|
||||
{:db/id invoice-expense-account-id
|
||||
:invoice-expense-account/amount target-expense-account-amount})
|
||||
|
||||
(when (and
|
||||
(not (dollars= current-total target-total))
|
||||
invoice-payment)
|
||||
[:db/retractEntity invoice-payment])
|
||||
(when (not= current-expense-account-location
|
||||
target-expense-account-location)
|
||||
{:db/id invoice-expense-account-id
|
||||
:invoice-expense-account/location target-expense-account-location})
|
||||
|
||||
(when (or new-account?
|
||||
(not (dollars= current-expense-account-amount target-expense-account-amount)))
|
||||
{:db/id invoice-expense-account-id
|
||||
:invoice-expense-account/amount target-expense-account-amount})
|
||||
|
||||
(when (not= current-expense-account-location
|
||||
target-expense-account-location)
|
||||
{:db/id invoice-expense-account-id
|
||||
:invoice-expense-account/location target-expense-account-location})
|
||||
|
||||
(when (not= current-account-id target-account-id )
|
||||
{:db/id invoice-expense-account-id
|
||||
:invoice-expense-account/account target-account-id})])
|
||||
(mapcat identity)
|
||||
(filter identity)
|
||||
vec)))
|
||||
(when (not= current-account-id target-account-id)
|
||||
{:db/id invoice-expense-account-id
|
||||
:invoice-expense-account/account target-account-id})])
|
||||
(mapcat identity)
|
||||
(filter identity)
|
||||
vec)))
|
||||
|
||||
(defn register-invoice-import [args]
|
||||
(let [{:keys [invoice-url]} args
|
||||
data (s3->csv invoice-url)]
|
||||
(alog/info ::rows
|
||||
:count (count data))
|
||||
:count (count data))
|
||||
(doseq [n (partition-all 50 (register-invoice-import* data))]
|
||||
(alog/info ::transacting
|
||||
:count (count n)
|
||||
:sample (take 2 n))
|
||||
:count (count n)
|
||||
:sample (take 2 n))
|
||||
(audit-transact n {:user/name "register-invoice-import"
|
||||
:user/role "admin"}))))
|
||||
|
||||
|
||||
@@ -20,32 +20,29 @@
|
||||
(def buffered (ex/fixed-thread-executor 100))
|
||||
|
||||
(defn order-of-insert [entity-dependencies]
|
||||
(loop [entity-dependencies entity-dependencies
|
||||
(loop [entity-dependencies entity-dependencies
|
||||
order []]
|
||||
(let [next-order (for [[entity deps] entity-dependencies
|
||||
:when (not (seq deps))]
|
||||
entity)
|
||||
next-deps (reduce
|
||||
(fn [entity-dependencies next-entity]
|
||||
(into {}
|
||||
(map
|
||||
(fn [[k v]]
|
||||
[k (disj v next-entity)])
|
||||
entity-dependencies)))
|
||||
(apply dissoc entity-dependencies next-order)
|
||||
next-order)]
|
||||
(fn [entity-dependencies next-entity]
|
||||
(into {}
|
||||
(map
|
||||
(fn [[k v]]
|
||||
[k (disj v next-entity)])
|
||||
entity-dependencies)))
|
||||
(apply dissoc entity-dependencies next-order)
|
||||
next-order)]
|
||||
(if (seq next-deps)
|
||||
(recur next-deps (into order next-order))
|
||||
(into order next-order)))))
|
||||
|
||||
|
||||
|
||||
|
||||
(def loaded (atom #{}))
|
||||
|
||||
(defn upsert-batch
|
||||
[batch context]
|
||||
(de/future-with request-pool
|
||||
(de/future-with request-pool
|
||||
(mu/with-context context
|
||||
(transact-with-backoff batch))
|
||||
batch))
|
||||
@@ -68,39 +65,39 @@
|
||||
(mu/with-context {:entity entity}
|
||||
(mu/log ::starting)
|
||||
@(s/consume (fn [batch]
|
||||
(mu/with-context {:entity entity}
|
||||
(try
|
||||
(swap! so-far #(+ % (count batch)))
|
||||
(mu/log ::loaded :count (count batch)
|
||||
:so-far @so-far)
|
||||
(catch Exception e
|
||||
(mu/log ::error
|
||||
:exception e)
|
||||
(throw e)))))
|
||||
(->> (partition-all 1000 entities)
|
||||
(s/->source)
|
||||
(s/onto buffered)
|
||||
(s/map (fn [entities]
|
||||
(when @die?
|
||||
(reset! die? false)
|
||||
(throw (Exception. "dead")))
|
||||
(upsert-batch entities {:entity entity
|
||||
:service "restore-from-backup"
|
||||
:background-job "restore-from-backup"})))
|
||||
(s/buffer 20)
|
||||
(s/realize-each)))
|
||||
(mu/with-context {:entity entity}
|
||||
(try
|
||||
(swap! so-far #(+ % (count batch)))
|
||||
(mu/log ::loaded :count (count batch)
|
||||
:so-far @so-far)
|
||||
(catch Exception e
|
||||
(mu/log ::error
|
||||
:exception e)
|
||||
(throw e)))))
|
||||
(->> (partition-all 1000 entities)
|
||||
(s/->source)
|
||||
(s/onto buffered)
|
||||
(s/map (fn [entities]
|
||||
(when @die?
|
||||
(reset! die? false)
|
||||
(throw (Exception. "dead")))
|
||||
(upsert-batch entities {:entity entity
|
||||
:service "restore-from-backup"
|
||||
:background-job "restore-from-backup"})))
|
||||
(s/buffer 20)
|
||||
(s/realize-each)))
|
||||
(swap! loaded conj entity))))
|
||||
|
||||
(defn load-from-backup
|
||||
(defn load-from-backup
|
||||
([backup-id connection] (load-from-backup backup-id connection nil))
|
||||
([backup-id connection starting-at]
|
||||
(let [schema (edn/read-string (slurp (pull-file backup-id "schema.edn")))
|
||||
full-dependencies (edn/read-string (slurp (pull-file backup-id "full-dependencies.edn")))
|
||||
entity-dependencies (edn/read-string (slurp (pull-file backup-id "entity-dependencies.edn")))]
|
||||
@(dc/transact connection [{:db/ident :entity/migration-key
|
||||
:db/unique :db.unique/identity
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/valueType :db.type/long}])
|
||||
:db/unique :db.unique/identity
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/valueType :db.type/long}])
|
||||
@(dc/transact connection (map
|
||||
(fn [s]
|
||||
(set/rename-keys s {:db/id :entity/migration-key}))
|
||||
@@ -108,14 +105,13 @@
|
||||
|
||||
;; TEMP - this has been fixed in current export (ezcater-olaciotn)
|
||||
@(dc/transact connection [{:entity/migration-key 17592257603901 :vendor/name "unknown"}
|
||||
{:entity/migration-key 17592232621701}
|
||||
{:entity/migration-key 17592263907739}
|
||||
{:entity/migration-key 17592271516922}])
|
||||
|
||||
{:entity/migration-key 17592232621701}
|
||||
{:entity/migration-key 17592263907739}
|
||||
{:entity/migration-key 17592271516922}])
|
||||
|
||||
(doseq [entity (cond->> (order-of-insert entity-dependencies)
|
||||
true (filter #(not= "audit" %))
|
||||
starting-at (drop-while #(not= starting-at %)))
|
||||
starting-at (drop-while #(not= starting-at %)))
|
||||
:let [_ (reset! so-far 0)
|
||||
_ (mu/log ::querying :entity entity)
|
||||
entities (mu/trace ::file-pulled
|
||||
@@ -136,9 +132,8 @@
|
||||
(mu/log ::refresh-running-balance-cache-complete)
|
||||
(mu/log ::done))
|
||||
|
||||
|
||||
(defn -main [& _]
|
||||
(try
|
||||
(try
|
||||
(println "restore")
|
||||
(execute "restore-from-backup" #(restore-fresh-from-backup (:args env)))
|
||||
(catch Exception e
|
||||
@@ -151,13 +146,11 @@
|
||||
(throw e))))
|
||||
|
||||
;; cloud load
|
||||
#_(comment
|
||||
#_(comment
|
||||
|
||||
;; /datomic-backup/079df203-eae0-4acf-94d5-8608ba8b8a9a
|
||||
(load-from-backup "079df203-eae0-4acf-94d5-8608ba8b8a9a" auto-ap.datomic/conn ["charge"])
|
||||
(load-from-backup "079df203-eae0-4acf-94d5-8608ba8b8a9a" auto-ap.datomic/conn ["charge"])
|
||||
|
||||
(load-entity "charge" (ednl/slurp "/tmp/tmp-edn"))
|
||||
(load-entity "charge" (ednl/slurp "/tmp/tmp-edn")))
|
||||
|
||||
|
||||
)
|
||||
;; => nil
|
||||
|
||||
@@ -39,17 +39,14 @@
|
||||
(dc/db conn)
|
||||
number)))
|
||||
|
||||
|
||||
(defn delete-all []
|
||||
@(dc/transact-async conn
|
||||
(->>
|
||||
(dc/q '[:find ?ss
|
||||
:where [?ss :sales-summary/date]]
|
||||
(dc/db conn))
|
||||
(map (fn [[ ss]]
|
||||
[:db/retractEntity ss])))))
|
||||
|
||||
|
||||
(->>
|
||||
(dc/q '[:find ?ss
|
||||
:where [?ss :sales-summary/date]]
|
||||
(dc/db conn))
|
||||
(map (fn [[ss]]
|
||||
[:db/retractEntity ss])))))
|
||||
|
||||
(defn dirty-sales-summaries [c]
|
||||
(let [client-id (dc/entid (dc/db conn) c)]
|
||||
@@ -99,53 +96,53 @@
|
||||
"food app refunds" 41400})
|
||||
|
||||
(defn get-payment-items [c date]
|
||||
(->>
|
||||
(dc/q '[:find ?processor ?type-name (sum ?total)
|
||||
:with ?c
|
||||
:in $ [?clients ?start-date ?end-date]
|
||||
:where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
|
||||
[?e :sales-order/charges ?c]
|
||||
[?c :charge/type-name ?type-name]
|
||||
(or-join [?c ?processor]
|
||||
(and [?c :charge/processor ?p]
|
||||
[?p :db/ident ?processor])
|
||||
(and
|
||||
(not [?c :charge/processor])
|
||||
[(ground :ccp-processor/na) ?processor]))
|
||||
[?c :charge/total ?total]]
|
||||
(dc/db conn)
|
||||
[[c] date date])
|
||||
(reduce
|
||||
(fn [acc [processor type-name total]]
|
||||
(update
|
||||
acc
|
||||
(cond (= type-name "CARD")
|
||||
"Card Payments"
|
||||
(= type-name "CASH")
|
||||
"Cash Payments"
|
||||
(#{"SQUARE_GIFT_CARD" "WALLET" "GIFT_CARD"} type-name)
|
||||
"Gift Card Payments"
|
||||
(#{:ccp-processor/toast
|
||||
#_:ccp-processor/ezcater
|
||||
#_:ccp-processor/koala
|
||||
:ccp-processor/doordash
|
||||
:ccp-processor/grubhub
|
||||
:ccp-processor/uber-eats} processor)
|
||||
"Food App Payments"
|
||||
:else
|
||||
"Unknown")
|
||||
(fnil + 0.0)
|
||||
total))
|
||||
{})
|
||||
(map (fn [[k v]]
|
||||
{:db/id (str (java.util.UUID/randomUUID))
|
||||
:sales-summary-item/sort-order 0
|
||||
:sales-summary-item/category k
|
||||
|
||||
:ledger-mapped/amount (if (= "Card Payments" k)
|
||||
(- v (get-fee c date))
|
||||
v)
|
||||
:ledger-mapped/ledger-side :ledger-side/debit}))))
|
||||
(->>
|
||||
(dc/q '[:find ?processor ?type-name (sum ?total)
|
||||
:with ?c
|
||||
:in $ [?clients ?start-date ?end-date]
|
||||
:where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
|
||||
[?e :sales-order/charges ?c]
|
||||
[?c :charge/type-name ?type-name]
|
||||
(or-join [?c ?processor]
|
||||
(and [?c :charge/processor ?p]
|
||||
[?p :db/ident ?processor])
|
||||
(and
|
||||
(not [?c :charge/processor])
|
||||
[(ground :ccp-processor/na) ?processor]))
|
||||
[?c :charge/total ?total]]
|
||||
(dc/db conn)
|
||||
[[c] date date])
|
||||
(reduce
|
||||
(fn [acc [processor type-name total]]
|
||||
(update
|
||||
acc
|
||||
(cond (= type-name "CARD")
|
||||
"Card Payments"
|
||||
(= type-name "CASH")
|
||||
"Cash Payments"
|
||||
(#{"SQUARE_GIFT_CARD" "WALLET" "GIFT_CARD"} type-name)
|
||||
"Gift Card Payments"
|
||||
(#{:ccp-processor/toast
|
||||
#_:ccp-processor/ezcater
|
||||
#_:ccp-processor/koala
|
||||
:ccp-processor/doordash
|
||||
:ccp-processor/grubhub
|
||||
:ccp-processor/uber-eats} processor)
|
||||
"Food App Payments"
|
||||
:else
|
||||
"Unknown")
|
||||
(fnil + 0.0)
|
||||
total))
|
||||
{})
|
||||
(map (fn [[k v]]
|
||||
{:db/id (str (java.util.UUID/randomUUID))
|
||||
:sales-summary-item/sort-order 0
|
||||
:sales-summary-item/category k
|
||||
|
||||
:ledger-mapped/amount (if (= "Card Payments" k)
|
||||
(- v (get-fee c date))
|
||||
v)
|
||||
:ledger-mapped/ledger-side :ledger-side/debit}))))
|
||||
|
||||
(defn get-discounts [c date]
|
||||
(when-let [discount (ffirst (dc/q '[:find (sum ?discount)
|
||||
@@ -162,7 +159,7 @@
|
||||
:ledger-mapped/ledger-side :ledger-side/debit}))
|
||||
|
||||
(defn get-refund-items [c date]
|
||||
(->>
|
||||
(->>
|
||||
(dc/q '[:find ?type-name (sum ?t)
|
||||
:with ?e
|
||||
:in $ [?clients ?start-date ?end-date]
|
||||
@@ -173,26 +170,24 @@
|
||||
(dc/db conn)
|
||||
[[c] date date])
|
||||
(reduce
|
||||
(fn [acc [type-name total]]
|
||||
(update
|
||||
acc
|
||||
(cond (= type-name "CARD")
|
||||
"Card Refunds"
|
||||
(= type-name "CASH")
|
||||
"Cash Refunds"
|
||||
:else
|
||||
"Food App Refunds")
|
||||
(fnil + 0.0)
|
||||
total))
|
||||
{})
|
||||
(fn [acc [type-name total]]
|
||||
(update
|
||||
acc
|
||||
(cond (= type-name "CARD")
|
||||
"Card Refunds"
|
||||
(= type-name "CASH")
|
||||
"Cash Refunds"
|
||||
:else
|
||||
"Food App Refunds")
|
||||
(fnil + 0.0)
|
||||
total))
|
||||
{})
|
||||
(map (fn [[k v]]
|
||||
{:db/id (str (java.util.UUID/randomUUID))
|
||||
:sales-summary-item/sort-order 3
|
||||
:sales-summary-item/category k
|
||||
:ledger-mapped/amount v
|
||||
:ledger-mapped/ledger-side :ledger-side/credit}))))
|
||||
|
||||
|
||||
{:db/id (str (java.util.UUID/randomUUID))
|
||||
:sales-summary-item/sort-order 3
|
||||
:sales-summary-item/category k
|
||||
:ledger-mapped/amount v
|
||||
:ledger-mapped/ledger-side :ledger-side/credit}))))
|
||||
|
||||
(defn get-fees [c date]
|
||||
(when-let [fee (get-fee c date)]
|
||||
@@ -278,17 +273,17 @@
|
||||
|
||||
(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] :as existing-summary} (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 (->>
|
||||
: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))
|
||||
@@ -301,20 +296,19 @@
|
||||
(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)
|
||||
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 all-items}]
|
||||
(if (seq (:sales-summary/items result))
|
||||
(do
|
||||
(alog/info ::upserting-summaries
|
||||
:category-count (count (:sales-summary/items result)))
|
||||
@(dc/transact conn [[:upsert-sales-summary result]]))
|
||||
@(dc/transact conn [{:db/id id :sales-summary/dirty false}]))))))
|
||||
|
||||
all-items (concat calculated-items manual-items)
|
||||
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 all-items}]
|
||||
(if (seq (:sales-summary/items result))
|
||||
(do
|
||||
(alog/info ::upserting-summaries
|
||||
:category-count (count (:sales-summary/items result)))
|
||||
@(dc/transact conn [[:upsert-sales-summary result]]))
|
||||
@(dc/transact conn [{:db/id id :sales-summary/dirty false}]))))))
|
||||
|
||||
(defn reset-summaries []
|
||||
@(dc/transact conn (->> (dc/q '[:find ?sos
|
||||
@@ -324,9 +318,6 @@
|
||||
(map (fn [[sos]]
|
||||
[:db/retractEntity sos])))))
|
||||
|
||||
|
||||
|
||||
|
||||
(comment
|
||||
(auto-ap.datomic/transact-schema conn)
|
||||
|
||||
@@ -336,26 +327,19 @@
|
||||
|
||||
(dirty-sales-summaries [:client/code "NGWH"])
|
||||
|
||||
|
||||
(apply mark-dirty [:client/code "NGWH"] (last-n-days 5))
|
||||
|
||||
(iol-ion.tx.upsert-sales-summary-ledger/summary->journal-entry (dc/db conn) 17592314245819)
|
||||
|
||||
|
||||
(iol-ion.tx.upsert-sales-summary-ledger/upsert-sales-summary (dc/db conn) {:db/id 17592314241429})
|
||||
|
||||
|
||||
|
||||
(mark-all-dirty 5)
|
||||
(delete-all)
|
||||
|
||||
|
||||
(sales-summaries-v2)
|
||||
|
||||
|
||||
1
|
||||
|
||||
|
||||
(dc/q '[:find (pull ?sos [* {:sales-summary/sales-items [*]}])
|
||||
:in $
|
||||
:where [?sos :sales-summary/client [:client/code "NGHW"]]
|
||||
@@ -386,15 +370,7 @@
|
||||
@(dc/transact conn [{:db/id :sales-summary/total-tax :db/ident :sales-summary/total-tax-legacy}
|
||||
{:db/id :sales-summary/total-tip :db/ident :sales-summary/total-tip-legacy}])
|
||||
|
||||
(auto-ap.datomic/transact-schema conn)
|
||||
|
||||
|
||||
|
||||
|
||||
)
|
||||
|
||||
|
||||
(auto-ap.datomic/transact-schema conn))
|
||||
|
||||
(defn -main [& _]
|
||||
(execute "sales-summaries" sales-summaries-v2))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user