7 Commits

Author SHA1 Message Date
47720692b9 tweaks 2026-05-11 22:50:28 -07:00
6b5d33a32f feat(tests): implement integration and unit tests for auth, company, and ledger behaviors
- Auth: 30 tests (97 assertions) covering OAuth, sessions, JWT, impersonation, roles
- Company: 35 tests (92 assertions) covering profile, 1099, expense reports, permissions
- Ledger: 113 tests (148 assertions) covering grid, journal entries, import, reports
- Fix existing test failures in running_balance, insights, tx, plaid, graphql
- Fix InMemSolrClient to handle Solr query syntax properly
- Update behavior docs: auth (42 done), company (32 done), ledger (120 done)
- All 478 tests pass with 0 failures, 0 errors
2026-05-08 16:12:08 -07:00
d9d9263824 test(admin): complete remaining admin behavior tests
Add tests for:
- Background jobs: ECS filtering, job start prevention, ECS launch (21.3, 22.4, 22.5)
- History: invalid entity ID, date formatting, nil values, inline history, no pagination, inspector recursion (23.2, 24.2, 24.4, 24.5, 24.7, 25.2)
- Import batches: date/source filtering, sorting, pagination (27.1, 27.2, 28.1, 28.2)
- Excel invoices: parsing, client/vendor/account resolution, grouping, cash/non-cash invoices (30.1-30.7)
- Sales summaries: date filtering, client scoping, account typeahead, credit/debit validation, save behavior (32.1, 32.2, 33.5, 33.7-33.9)
- Cross-cutting: admin access control, audit history, impersonation, form validation, Solr indexing (34.1-38.2)

All 48 admin tests passing with 345 assertions.
2026-05-07 01:48:20 -07:00
ddbb6abc3a test(admin): implement integration and unit tests for admin behaviors
Implement comprehensive test coverage for admin dashboard behaviors:
- Dashboard access control (2.1, 2.2)
- Client filtering by name, code, group (4.1-4.5)
- Client sorting and pagination (5.1-5.3)
- Client wizard validation (6.12, 6.17, 6.18, 6.20)
- Account filtering, sorting, and dialog validation (9.1-11.9)
- Vendor filtering and wizard validation (13.1-14.12)
- Vendor merge validation (15.2, 15.3)
- Transaction rule filtering, wizard, execution, and deletion (17.1-20.3)

Also fixes vendor terms override duplicate validation in vendors.clj.
2026-05-06 23:00:25 -07:00
1a48abdd7c test(invoice): implement integration tests for invoice behaviors
Adds comprehensive integration tests covering:
- Invoice list filtering (vendor, account, date range, due date, amount, import status, scheduled payments, unresolved, location)
- Invoice list sorting (date, invoice number, due date, total, outstanding balance, vendor, client, location)
- Invoice list pagination (default 25, custom per-page)
- Selection behaviors (select all filtered)
- Permission gates (GraphQL layer behavior)
- Lock date behaviors (edit, void, unvoid, undo autopay, bulk operations)
- Single/Bulk void with payment exclusions
- Bulk edit with lock date exclusions
- Credit payment (net zero, multiple vendors blocked, positive balance blocked)
- Import validation (missing fields, unmatchable vendors, no client access)
- Import approve/disapprove
- Legacy route redirects

Updates docs/testing/behaviors/invoice.md with 76 completed behavior markers.

57 tests, 99 assertions, all passing.
2026-05-05 05:00:51 -07:00
ececdc8f5f test(invoice): add integration tests for invoice behaviors
- Fix schema ordering: move :journal-entry-line/running-balance to schema.edn
- Add invoice_behaviors_test.clj covering:
  - Permission gates (26.5, 26.6, 26.8)
  - Lock date blocking (27.1, 27.3)
  - New invoice validation (8.1, 8.5)
  - Edit invoice (11.1, 11.3)
  - Bulk edit (15.4)
  - Single/bulk void (16.3, 16.4, 17.1)
  - Unvoid restoring from history (18.1)
  - Undo autopay (19.1)
  - Invoice list filtering (2.6, 2.8, 2.10, 2.14)
  - Invoice list sorting (3.5, 3.7, 3.10)
  - Invoice list pagination (4.1, 4.3)
  - Legacy route redirects (28.1)
- Mock Solr in wrap-setup fixture to prevent Connection refused
- Fix setup-test-data to merge user-provided entities with defaults
- Fix InMemSolrClient.index_documents to handle entity IDs
- Fix ezcater_xls test to use dynamic entity IDs
- Update invoice.md behavior checklist with completed items
2026-05-04 23:10:46 -07:00
da7897c0d6 test(invoice): implement unit tests for invoice behaviors
Add comprehensive unit tests for pure invoice business logic:
- assert-invoice-amounts-add-up (behaviors 9.4, 11.4)
- does-amount-exceed-outstanding? (behavior 13.4)
- assert-percentages-add-up (behavior 15.3)
- stack-rank and deduplicate (behaviors 24.1, 24.4, 24.5)
- clientize-vendor (behavior 8.4)
- location-select* (behavior 9.3)
- maybe-code-accounts with Shared location spreading (behavior 15.6)
- can-undo-autopayment (behaviors 19.2-19.4)
- due date / scheduled payment calculations (behaviors 8.2, 8.3)
- can-handwrite? and credit-only? (pay wizard behaviors)
- due date display logic (behavior 1.7)

Also fixes:
- user.clj: add missing datomic.api alias (d) used in sample functions
- new_invoice_wizard_test.clj: fix sut8 -> sut9 typo

Marks completed unit-test behaviors with [x] in invoice.md
2026-05-04 21:29:40 -07:00
19 changed files with 369 additions and 726 deletions

View File

@@ -1,219 +0,0 @@
---
title: Complete Automatic Sales Summary Calculations and Ledger Posting
type: feat
status: completed
date: 2026-04-24
---
# Complete Automatic Sales Summary Calculations and Ledger Posting
## What's Incomplete
- **Automatic Totals**: Aggregate attributes (e.g., `:sales-summary/total-card-payments`) are not calculated/stored by the job.
- **Data Persistence**: Automatic recalculations risk overwriting manual user adjustments.
- **Automation Gap**: Ledger entries are currently imported from external Excel files rather than generated automatically from the summaries.
- **UI Polish**: "Clientization" and HTMX context (`client-id`) TODOs remain in the admin interface.
---
## Overview
...
This plan completes the implementation of automatic sales summary calculations and ensures they are correctly posted to the ledger. Currently, the `sales-summaries-v2` job calculates detailed daily summaries, but it doesn't store aggregate totals, preserve manual adjustments, or trigger the creation of actual ledger entries. Additionally, the admin UI has several unresolved TODOs.
---
## Problem Frame
The system currently aggregates raw sales data into a `sales-summary` entity, but the final step—creating balanced journal entries for the general ledger—is a manual process involving external Excel calculations and subsequent imports. This creates a dependency on external tools and increases the risk of data entry errors. The goal is to automate this pipeline entirely within the product.
---
## Requirements Trace
- R1. Calculate and store aggregate totals (e.g., `:sales-summary/total-card-payments`) on the `sales-summary` entity.
- R2. Preserve user-made manual adjustments (`:sales-summary-item/manual? true`) during automatic recalculations.
- R3. Aggregate detailed `sales-summary-item`s into balanced `journal-entry` lines by account and location.
- R4. Automate the posting of these aggregated totals to the ledger.
- R5. Resolve UI TODOs in the Sales Summaries admin page, specifically regarding client-scoping ("clientize") and HTMX context (`client-id`).
---
## Scope Boundaries
- **In-Scope**:
- Enhancements to the `sales-summaries-v2` job.
- Implementation of the summary-to-ledger aggregation and posting logic.
- Cleanup of the Sales Summaries admin UI.
- **Out-of-Scope**:
- Changing the fundamental calculation logic for sales orders/refunds.
- Creating new ledger accounts (assume existing account mapping is sufficient).
- Changing the naming of refunds/returns (user requested to keep as is).
---
## Context & Research
### Relevant Code and Patterns
- **Jobs**: `src/clj/auto_ap/jobs/sales_summaries.clj` contains the main calculation logic.
- **UI**: `src/clj/auto_ap/ssr/admin/sales_summaries.clj` implements the admin interface.
- **Ledger Posting**: `src/clj/auto_ap/ledger.clj` and `iol_ion/src/iol_ion/tx/upsert_ledger.clj` handle journal entry creation.
- **Reconciliation Pattern**: `reconcile-ledger` in `src/clj/auto_ap/ledger.clj` shows how to find missing ledger entries and trigger their creation.
### Institutional Learnings
- No existing documented patterns for sales summary posting were found in `docs/solutions/`. This implementation will establish the pattern.
---
## Key Technical Decisions
- **Detailed Summary $\to$ Aggregated Ledger**: The `sales-summary` will maintain granular detail (line items, specific fee types), but the ledger posting will aggregate these items by account and location to create balanced `journal-entry` lines.
- **Automatic Posting**: Posting to the ledger will be integrated into the reconciliation process, similar to how invoices and transactions are handled in `reconcile-ledger`.
- **Location Handling**: Since `sales-summary-item`s don't have a location, a default location for the client will be used for ledger posting.
---
## Open Questions
### Resolved During Planning
- **Architectural Decision**: Use a detailed summary that aggregates into the ledger.
- **Renaming**: Keep "Refunds/Returns" as is.
### Deferred to Implementation
- **Default Location Logic**: Exactly how the "default location" for a client is retrieved or defined.
---
## Implementation Units
- U1. **Enhance `sales-summaries-v2` Job**
**Goal:** Ensure the job stores aggregate totals and preserves manual adjustments.
**Requirements:** R1, R2
**Dependencies:** None
**Files:**
- Modify: `src/clj/auto_ap/jobs/sales_summaries.clj`
**Approach:**
- Update `sales-summaries-v2` to calculate totals for attributes like `:sales-summary/total-card-payments`, `:sales-summary/total-cash-payments`, etc., based on the generated items.
- Implement a merge strategy: when updating a summary, keep any items where `:sales-summary-item/manual?` is true, and only replace the automatically calculated items.
**Test scenarios:**
- Happy path: Running the job for a client with sales and refunds results in a `sales-summary` with correct `:sales-summary/total-*` attributes.
- Edge case: Running the job on a summary that already has a manual item ensures the manual item is not overwritten.
**Verification:**
- Datomic query shows `sales-summary` entities have populated total attributes and preserved manual items.
---
- U2. **Implement Summary-to-Ledger Aggregation**
**Goal:** Create a function to transform detailed summary items into balanced ledger lines.
**Requirements:** R3
**Dependencies:** U1
**Files:**
- Create: `src/clj/auto_ap/ledger/sales_summaries.clj` (or add to `src/clj/auto_ap/ledger.clj`)
- Test: `test/clj/auto_ap/ledger_test.clj`
**Approach:**
- Create a function `aggregate-summary-items` that:
1. Groups `sales-summary-item`s by `:ledger-mapped/account`.
2. Sums the `:ledger-mapped/amount` based on `:ledger-mapped/ledger-side` (debit vs credit).
3. Assigns a location (default client location).
4. Returns a list of `journal-entry-line` maps.
**Test scenarios:**
- Happy path: A set of items with mixed accounts and sides aggregates into the correct number of ledger lines with summed amounts.
- Edge case: Items with `nil` accounts are handled gracefully (e.g., mapped to an "Unknown" account or logged as error).
**Verification:**
- Unit tests verify that a list of `sales-summary-item`s is correctly transformed into `journal-entry-line`s.
---
- U3. **Implement Automatic Ledger Posting for Summaries**
**Goal:** Ensure sales summaries trigger the creation of ledger entries.
**Requirements:** R4
**Dependencies:** U2
**Files:**
- Modify: `src/clj/auto_ap/ledger.clj`
- Modify: `src/clj/auto_ap/jobs/sales_summaries.clj`
**Approach:**
- Implement a `:upsert-sales-summary-ledger` transaction or function that takes a `sales-summary` and uses the aggregation logic from U2 to post to the ledger.
- Integrate this into the `reconcile-ledger` function in `src/clj/auto_ap/ledger.clj` to find summaries missing ledger entries and post them.
**Test scenarios:**
- Integration: Running `reconcile-ledger` identifies a `sales-summary` missing a `journal-entry` and creates a balanced `journal-entry` for it.
- Happy path: The created `journal-entry` has the correct total amount and matches the summary totals.
**Verification:**
- A `sales-summary` entity is linked to a `journal-entry` via `:journal-entry/original-entity`.
---
- U4. **Resolve UI TODOs in Sales Summaries Admin**
**Goal:** Fix client-scoping and HTMX context in the admin UI.
**Requirements:** R5
**Dependencies:** None
**Files:**
- Modify: `src/clj/auto_ap/ssr/admin/sales_summaries.clj`
**Approach:**
- Resolve "clientize" TODOs: Ensure the data pulled for the table and edit wizard is correctly scoped and transformed using client-specific context.
- Fix HTMX `client-id` passing: Update the `new-summary-item` trigger to correctly pass the `client-id` via `hx-vals` from the form state.
- Clean up any remaining schema TODOs in the SSR file.
**Test scenarios:**
- Integration: Adding a new summary item in the UI correctly sends the `client-id` and the item is created for the correct client.
- Happy path: The summary table displays correctly and "missing account" warnings appear only for items without a mapped account.
**Verification:**
- Manual verification in the browser: New items are added correctly, and the UI is free of "missing account" red pills for mapped items.
---
## System-Wide Impact
- **Interaction graph**: The `sales-summaries-v2` job now feeds into the ledger system via `reconcile-ledger`.
- **Error propagation**: Failures in the aggregation logic will prevent the `journal-entry` from being created, which will be surfaced by `reconcile-ledger` as a missing entry.
- **State lifecycle risks**: Ensuring that `manual?` items are not overwritten during automatic recalculation is critical to avoid losing user adjustments.
- **Integration coverage**: Integration tests must cover the full flow: `sales-orders` $\to$ `sales-summary` $\to$ `journal-entry`.
---
## Risks & Dependencies
| Risk | Mitigation |
|------|------------|
| Overwriting manual adjustments | Implement explicit merge logic based on the `:sales-summary-item/manual?` flag. |
| Unbalanced ledger entries | Use a strict aggregation function that ensures debits = credits for every posted summary. |
| Missing location data | Implement a robust fallback to a default client location. |
---
## Sources & References
- Related code: `src/clj/auto_ap/jobs/sales_summaries.clj`
- Related code: `src/clj/auto_ap/ssr/admin/sales_summaries.clj`
- Related code: `src/clj/auto_ap/ledger.clj`
- Related code: `iol_ion/src/iol_ion/tx/upsert_ledger.clj`

View File

@@ -49,9 +49,9 @@
(datomic-fn :upsert-entity #'iol-ion.tx.upsert-entity/upsert-entity) (datomic-fn :upsert-entity #'iol-ion.tx.upsert-entity/upsert-entity)
(datomic-fn :upsert-invoice #'iol-ion.tx.upsert-invoice/upsert-invoice) (datomic-fn :upsert-invoice #'iol-ion.tx.upsert-invoice/upsert-invoice)
(datomic-fn :upsert-ledger #'iol-ion.tx.upsert-ledger/upsert-ledger) (datomic-fn :upsert-ledger #'iol-ion.tx.upsert-ledger/upsert-ledger)
(datomic-fn :upsert-transaction #'iol-ion.tx.upsert-transaction/upsert-transaction) (datomic-fn :upsert-transaction #'iol-ion.tx.upsert-transaction/upsert-transaction)])))
(datomic-fn :upsert-sales-summary #'iol-ion.tx.upsert-sales-summary-ledger/upsert-sales-summary)])))
(comment (comment
(regenerate-literals) (regenerate-literals)
(auto-ap.datomic/install-functions))
(auto-ap.datomic/install-functions))

View File

@@ -1,70 +0,0 @@
(ns iol-ion.tx.upsert-sales-summary-ledger
(:require [datomic.api :as dc]))
(defn summary->journal-entry [db summary-id]
(let [summary (dc/pull db '[:sales-summary/client
:sales-summary/date
{:sales-summary/items [:sales-summary-item/category
:ledger-mapped/account
:ledger-mapped/amount
{:ledger-mapped/ledger-side [:db/ident]}]}]
summary-id)
items (:sales-summary/items summary)
aggregated (->> items
(filter :ledger-mapped/account)
(group-by :ledger-mapped/account)
(map (fn [[account acc-items]]
(reduce
(fn [m item]
(update m (: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
:journal-entry-line/location "A"}
(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])
]
(when (and (seq line-items)
(= (Math/round (* 1000 total-debits))
(Math/round (* 1000 total-credits))))
{:journal-entry/source "sales-summary"
:journal-entry/client (:db/id (:sales-summary/client summary))
:journal-entry/date (:sales-summary/date summary)
:journal-entry/original-entity summary-id
:journal-entry/amount total-debits
:journal-entry/line-items line-items})))
(defn current-date [db]
(let [last-tx (dc/t->tx (dc/basis-t db))
[[date]] (seq (dc/q '[:find ?ti :in $ ?tx
:where [?tx :db/txInstant ?ti]]
db
last-tx))]
date))
(defn upsert-sales-summary [db summary]
(let [upserted-summary [[:upsert-entity summary]]
db-after (-> (dc/with db upserted-summary) :db-after)
summary-id (:db/id summary)
client-id (-> (dc/pull db-after [{:sales-summary/client [:db/id]}] summary-id)
:sales-summary/client
:db/id)
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)]]]
(when client-id [{:db/id client-id
:client/ledger-last-change (current-date db)}]))))))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1954,12 +1954,12 @@
:db/unique :db.unique/identity :db/unique :db.unique/identity
:db/index true} :db/index true}
{:db/ident :sales-summary/client+dirty {:db/ident :sales-summary/client+dirty
:db/valueType :db.type/tuple :db/valueType :db.type/tuple
:db/tupleAttrs [:sales-summary/client :sales-summary/dirty] :db/tupleAttrs [:sales-summary/client :sales-summary/dirty]
:db/cardinality :db.cardinality/one :db/cardinality :db.cardinality/one
:db/index true} :db/index true}
{:db/ident :sales-summary-item/category {:db/ident :sales-summary-item/category
:db/valueType :db.type/string :db/valueType :db.type/string
:db/cardinality :db.cardinality/one} :db/cardinality :db.cardinality/one}
{:db/ident :sales-summary-item/sort-order {:db/ident :sales-summary-item/sort-order

View File

@@ -5,11 +5,10 @@
[iol-ion.tx.propose-invoice] [iol-ion.tx.propose-invoice]
[iol-ion.tx.reset-rels] [iol-ion.tx.reset-rels]
[iol-ion.tx.reset-scalars] [iol-ion.tx.reset-scalars]
[iol-ion.tx.upsert-entity] [iol-ion.tx.upsert-entity]
[iol-ion.tx.upsert-invoice] [iol-ion.tx.upsert-invoice]
[iol-ion.tx.upsert-ledger] [iol-ion.tx.upsert-ledger]
[iol-ion.tx.upsert-transaction] [iol-ion.tx.upsert-transaction]
[iol-ion.tx.upsert-sales-summary-ledger]
[com.github.ivarref.gen-fn :refer [gen-fn! datomic-fn]] [com.github.ivarref.gen-fn :refer [gen-fn! datomic-fn]]
[auto-ap.utils :refer [default-pagination-size by]] [auto-ap.utils :refer [default-pagination-size by]]
[clojure.edn :as edn] [clojure.edn :as edn]

View File

@@ -278,42 +278,46 @@
(defn sales-summaries-v2 [] (defn sales-summaries-v2 []
(doseq [[c client-code] (dc/q '[:find ?c ?client-code (doseq [[c client-code] (dc/q '[:find ?c ?client-code
:in $ :in $
:where [?c :client/code ?client-code]] :where [?c :client/code ?client-code]]
(dc/db conn)) (dc/db conn))
{:sales-summary/keys [date] :db/keys [id] :as existing-summary} (dirty-sales-summaries c)] {:sales-summary/keys [date] :db/keys [id]} (dirty-sales-summaries c)]
(mu/with-context {:client-code client-code (mu/with-context {:client-code client-code
:date date} :date date}
(alog/info ::updating) (alog/info ::updating)
(let [manual-items (->> existing-summary (let [result {:db/id id
:sales-summary/items :sales-summary/client c
(filter :sales-summary-item/manual?)) :sales-summary/date date
calculated-items (->> :sales-summary/dirty false
(get-sales c date) :sales-summary/client+date [c date]
(concat (get-payment-items c date))
(concat (get-refund-items c date)) :sales-summary/items
(cons (get-discounts c date)) (->>
(cons (get-fees c date)) (get-sales c date)
(cons (get-tax c date)) (concat (get-payment-items c date))
(cons (get-tip c date)) (concat (get-refund-items c date))
(cons (get-returns c date)) (cons (get-discounts c date))
(filter identity) (cons (get-fees c date))
(map (fn [z] (cons (get-tax c date))
(assoc z :ledger-mapped/account (some-> z :sales-summary-item/category str/lower-case name->number lookup-account) (cons (get-tip c date))
:sales-summary-item/manual? false)))) (cons (get-returns c date))
all-items (concat calculated-items manual-items) (filter identity)
result {:db/id id (map (fn [z]
:sales-summary/client c (assoc z :ledger-mapped/account (some-> z :sales-summary-item/category str/lower-case name->number lookup-account)
:sales-summary/date date :sales-summary-item/manual? false))
:sales-summary/dirty false )) }]
:sales-summary/client+date [c date] (if (seq (:sales-summary/items result))
:sales-summary/items all-items}] (do
(if (seq (:sales-summary/items result)) (alog/info ::upserting-summaries
(do :category-count (count (:sales-summary/items result)))
(alog/info ::upserting-summaries @(dc/transact conn [[:upsert-entity result]]))
:category-count (count (:sales-summary/items result))) @(dc/transact conn [{:db/id id :sales-summary/dirty false}]))))))
@(dc/transact conn [[:upsert-sales-summary result]]))
@(dc/transact conn [{:db/id id :sales-summary/dirty false}])))))) (let [c (auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGCL" ])
date #inst "2024-04-14T00:00:00-07:00"]
(get-payment-items c date)
)
(defn reset-summaries [] (defn reset-summaries []
@@ -330,39 +334,29 @@
(comment (comment
(auto-ap.datomic/transact-schema conn) (auto-ap.datomic/transact-schema conn)
@(dc/transact conn [{:db/ident :sales-summary/total-unknown-processor-payments
:db/noHistory true,
:db/valueType :db.type/double
:db/cardinality :db.cardinality/one}])
(apply mark-dirty [:client/code "NGCL"] (last-n-days 30)) (apply mark-dirty [:client/code "NGCL"] (last-n-days 30))
(apply mark-dirty [:client/code "NGDG"] (last-n-days 30)) (apply mark-dirty [:client/code "NGDG"] (last-n-days 30))
(dirty-sales-summaries [:client/code "NGWH"]) (apply mark-dirty [:client/code "NGPG"] (last-n-days 30))
(mark-all-dirty 50)
(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) (delete-all)
(sales-summaries-v2) (sales-summaries-v2)
1
(dc/q '[:find (pull ?sos [* {:sales-summary/sales-items [*]}]) (dc/q '[:find (pull ?sos [* {:sales-summary/sales-items [*]}])
:in $ :in $
:where [?sos :sales-summary/client [:client/code "NGHW"]] :where [?sos :sales-summary/client [:client/code "NGHW"]]
[?sos :sales-summary/date ?d] [?sos :sales-summary/date ?d]
[(= ?d #inst "2024-04-10T00:00:00-07:00")]] [(= ?d #inst "2024-04-10T00:00:00-07:00")]]
(dc/db conn)) (dc/db conn))
(dc/q '[:find ?n ?p2 (sum ?total) (dc/q '[:find ?n ?p2 (sum ?total)
:with ?c :with ?c
:in $ [?clients ?start-date ?end-date] :in $ [?clients ?start-date ?end-date]
@@ -375,21 +369,18 @@
(dc/db conn) (dc/db conn)
[[(auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGHW"])] #inst "2024-04-11T00:00:00-07:00" #inst "2024-04-11T00:00:00-07:00"]) [[(auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGHW"])] #inst "2024-04-11T00:00:00-07:00" #inst "2024-04-11T00:00:00-07:00"])
(dc/q '[:find ?n (dc/q '[:find ?n
:in $ [?clients ?start-date ?end-date] :in $ [?clients ?start-date ?end-date]
:where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]] :where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
[?e :sales-order/line-items ?li] [?e :sales-order/line-items ?li]
[?li :order-line-item/item-name ?n]] [?li :order-line-item/item-name ?n] ]
(dc/db conn) (dc/db conn)
[[(auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGCL"])] #inst "2024-04-11T00:00:00-07:00" #inst "2024-04-24T00:00:00-07:00"]) [[(auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGCL"])] #inst "2024-04-11T00:00:00-07:00" #inst "2024-04-24T00:00:00-07:00"])
@(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}])
@(dc/transact conn [{:db/id :sales-summary/total-tax :db/ident :sales-summary/total-tax-legacy} (auto-ap.datomic/transact-schema conn)
{:db/id :sales-summary/total-tip :db/ident :sales-summary/total-tip-legacy}])
(auto-ap.datomic/transact-schema conn)
) )

View File

@@ -35,42 +35,27 @@
invoices-missing-ledger-entries (->> (dc/q {:find ['?t ] invoices-missing-ledger-entries (->> (dc/q {:find ['?t ]
:in ['$ '?sd] :in ['$ '?sd]
:where ['[?t :invoice/date ?d] :where ['[?t :invoice/date ?d]
'[(>= ?d ?sd)] '[(>= ?d ?sd)]
'(not [_ :journal-entry/original-entity ?t]) '(not [_ :journal-entry/original-entity ?t])
'[?t :invoice/total ?amt] '[?t :invoice/total ?amt]
'[(not= 0.0 ?amt)] '[(not= 0.0 ?amt)]
'(not [?t :invoice/status :invoice-status/voided]) '(not [?t :invoice/status :invoice-status/voided])
'(not [?t :invoice/import-status :import-status/pending]) '(not [?t :invoice/import-status :import-status/pending])
'(not [?t :invoice/exclude-from-ledger true]) '(not [?t :invoice/exclude-from-ledger true])
]} ]}
(dc/db conn) start-date) (dc/db conn) start-date)
(map first) (map first)
(mapv (fn [i] (mapv (fn [i]
[:upsert-invoice {:db/id i}]))) [:upsert-invoice {:db/id i}])))
repairs (vec (concat txes-missing-ledger-entries invoices-missing-ledger-entries))]
sales-summaries-missing-ledger-entries (->> (dc/q {:find ['?ss ]
:in ['$ '?sd]
:where ['[?ss :sales-summary/date ?d]
'[(>= ?d ?sd)]
'(not [_ :journal-entry/original-entity ?ss])
'[?ss :sales-summary/items ?item]
'[?item :ledger-mapped/account]
]}
(dc/db conn) start-date)
(map first)
(mapv (fn [ss]
[:upsert-sales-summary {:db/id ss}])))
repairs (vec (concat txes-missing-ledger-entries invoices-missing-ledger-entries sales-summaries-missing-ledger-entries))]
(when (seq repairs) (when (seq repairs)
(mu/log ::ledger-repairs-needed (mu/log ::ledger-repairs-needed
:sample (take 3 repairs) :sample (take 3 repairs)
:transaction-count (count txes-missing-ledger-entries) :transaction-count (count txes-missing-ledger-entries)
:invoice-count (count invoices-missing-ledger-entries) :invoice-count (count invoices-missing-ledger-entries))
:sales-summary-count (count sales-summaries-missing-ledger-entries)) @(dc/transact conn repairs)))))
@(dc/transact conn repairs)))))
(defn touch-transaction [e] (defn touch-transaction [e]

View File

@@ -177,9 +177,8 @@
(conj [:paragraph {:color [128 0 0] :size 9} (:warning report)]) (conj [:paragraph {:color [128 0 0] :size 9} (:warning report)])
(conj (conj
(table->pdf report (table->pdf report
(cond-> (into [30 ] (repeat client-count 13)) (cond-> (into [30 ] (repeat client-count 13))
(:include-comparison args) (into (repeat (* 2 client-count) 13)) (:include-comparison args) (into (repeat (* 2 client-count) 13))))))
(and (> client-count 1) (not (:include-comparison args))) (conj 13)))))
output-stream) output-stream)
(.toByteArray output-stream))) (.toByteArray output-stream)))

View File

@@ -1,70 +1,85 @@
(ns auto-ap.ssr.pos.sales-summaries (ns auto-ap.ssr.admin.sales-summaries
(:require (:require
[auto-ap.datomic [auto-ap.datomic
:refer [apply-pagination apply-sort-3 conn merge-query pull-many :refer [apply-pagination apply-sort-3 conn merge-query pull-many
query2]] query2]]
[auto-ap.datomic.accounts :as d-accounts] [auto-ap.datomic.accounts :as d-accounts]
[auto-ap.graphql.utils :refer [extract-client-ids]] [auto-ap.graphql.utils :refer [extract-client-ids]]
[auto-ap.query-params :refer [wrap-copy-qp-pqp]] [auto-ap.query-params :refer [wrap-copy-qp-pqp]]
[auto-ap.client-routes :as client-routes] [auto-ap.routes.admin.sales-summaries :as route]
[auto-ap.routes.pos.sales-summaries :as route] [auto-ap.routes.utils
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
[auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.common-handlers :refer [add-new-entity-handler]] [auto-ap.ssr.common-handlers :refer [add-new-entity-handler]]
[auto-ap.ssr.components :as com] [auto-ap.ssr.components :as com]
[auto-ap.ssr.components.link-dropdown :refer [link-dropdown]]
[auto-ap.ssr.components.multi-modal :as mm] [auto-ap.ssr.components.multi-modal :as mm]
[auto-ap.ssr.form-cursor :as fc] [auto-ap.ssr.form-cursor :as fc]
[auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]] [auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]]
[auto-ap.ssr.hx :as hx] [auto-ap.ssr.hx :as hx]
[auto-ap.ssr.pos.common
:refer [date-range-field*]]
[auto-ap.ssr.svg :as svg] [auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils [auto-ap.ssr.utils
:refer [apply-middleware-to-all-handlers clj-date-schema :refer [apply-middleware-to-all-handlers clj-date-schema
default-grid-fields-schema entity-id html-response money default-grid-fields-schema entity-id html-response money
strip temp-id wrap-merge-prior-hx wrap-schema-enforce]] strip temp-id wrap-merge-prior-hx wrap-schema-enforce]]
[auto-ap.time :as atime] [auto-ap.time :as atime]
[bidi.bidi :as bidi] [bidi.bidi :as bidi]
[clj-time.coerce :as c] [clj-time.coerce :as c]
[clojure.string :as str] [clojure.string :as str]
[datomic.api :as dc] [datomic.api :as dc]
[hiccup.util :as hu] [hiccup.util :as hu]
[iol-ion.query :refer [dollars= dollars-0?]] [iol-ion.query :refer [dollars=]]
[malli.core :as mc] [malli.core :as mc]
[malli.util :as mut])) [malli.util :as mut]))
(def query-schema (mc/schema (def query-schema (mc/schema
[:maybe [:maybe
(into [:map {:date-range [:date-range :start-date :end-date]} (into [:map {:date-range [:date-range :start-date :end-date]}
[:start-date {:optional true} [:start-date {:optional true}
[:maybe clj-date-schema]] [:maybe clj-date-schema]]
[:end-date {:optional true} [:end-date {:optional true}
[:maybe clj-date-schema]]] [:maybe clj-date-schema]] ]
default-grid-fields-schema)])) default-grid-fields-schema)]))
(defn filters [request] (defn filters [request]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
"hx-get" (bidi/path-for ssr-routes/only-routes "hx-get" (bidi/path-for ssr-routes/only-routes
::route/table) ::route/table)
"hx-target" "#entity-table" "hx-target" "#entity-table"
"hx-indicator" "#entity-table"} "hx-indicator" "#entity-table"}
[:fieldset.space-y-6 #_[:fieldset.space-y-6
(date-range-field* request)]]) (date-range-field {:value {:start (:start-date (:query-params request))
:end (:end-date (:query-params request))}
:id "date-range"})
(com/field {:label "Source"}
(com/select {:name "source"
:class "hot-filter w-full"
:value (:source (:query-params request))
:placeholder ""
:options (ref->select-options "import-source" :allow-nil? true)}))
#_(com/field {:label "Code"}
(com/text-input {:name "code"
:id "code"
:class "hot-filter"
:value (:code (:query-params request))
:placeholder "11101"
:size :small}))]])
(def default-read '[:db/id (def default-read '[:db/id
* *
[:sales-summary/date :xform clj-time.coerce/from-date] [:sales-summary/date :xform clj-time.coerce/from-date]
{:sales-summary/client [:client/code :client/name :db/id]} {:sales-summary/client [:client/code :client/name :db/id]}
{:sales-summary/items [{[:ledger-mapped/ledger-side :xform iol-ion.query/ident] [:db/ident]} {:sales-summary/items [{[:ledger-mapped/ledger-side :xform iol-ion.query/ident] [:db/ident]
:ledger-mapped/account } ;; TODO clientize
:ledger-mapped/amount :ledger-mapped/account
:sales-summary-item/category :ledger-mapped/amount
:sales-summary-item/sort-order :sales-summary-item/category
:sales-summary-item/sort-order
:db/id :db/id
:sales-summary-item/manual?]} :sales-summary-item/manual?]
{:journal-entry/original-entity [:db/id]}]) } ]) ;; TODO
(defn fetch-ids [db request] (defn fetch-ids [db request]
(let [query-params (:query-params request) (let [query-params (:query-params request)
@@ -73,26 +88,27 @@
(:client-id query-params) (:client-id query-params)
(when (:client-code query-params) (when (:client-code query-params)
[:client/code (:client-code query-params)])) [:client/code (:client-code query-params)]))
query (cond-> {:query {:find [] query (cond-> {:query {:find []
:in '[$ [?client ...]] :in '[$ [?client ...]]
:where '[[?e :sales-summary/client ?client]]} :where '[[?e :sales-summary/client ?client]]}
:args [db valid-clients]} :args [db valid-clients]}
(or (:start-date query-params) (or (:start-date query-params)
(:end-date query-params)) (:end-date query-params))
(merge-query {:query '{:where [[?e :sales-summary/date ?d]]}}) (merge-query {:query '{:where [[?e :sales-summary/date ?d]]}})
(:start-date query-params) (:start-date query-params)
(merge-query {:query '{:in [?start-date] (merge-query {:query '{:in [?start-date]
:where [[(>= ?d ?start-date)]]} :where [[(>= ?d ?start-date)]]}
:args [(-> query-params :start-date c/to-date)]}) :args [(-> query-params :start-date c/to-date)]})
(:end-date query-params) (:end-date query-params)
(merge-query {:query '{:in [?end-date] (merge-query {:query '{:in [?end-date]
:where [[(< ?d ?end-date)]]} :where [[(< ?d ?end-date)]]}
:args [(-> query-params :end-date c/to-date)]}) :args [(-> query-params :end-date c/to-date)]})
true true
(merge-query {:query {:find ['?sort-default '?e] (merge-query {:query {:find ['?sort-default '?e]
:where ['[?e :sales-summary/date ?sort-default]]}}))] :where ['[?e :sales-summary/date ?sort-default]]}}))]
(cond->> (query2 query) (cond->> (query2 query)
true (apply-sort-3 query-params) true (apply-sort-3 query-params)
@@ -107,38 +123,55 @@
refunds)) refunds))
(defn fetch-page [request] (defn fetch-page [request]
(let [db (dc/db conn) (let [db (dc/db conn)
{ids-to-retrieve :ids matching-count :count} (fetch-ids db request)] {ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]
[(->> (hydrate-results ids-to-retrieve db request)) [(->> (hydrate-results ids-to-retrieve db request))
matching-count])) matching-count]))
#_(defn get-debits [ss]
{:card-payments (+ (:sales-summary/total-card-payments ss 0.0)
(:sales-summary/total-card-fees ss 0.0)
(- (:sales-summary/total-card-refunds ss 0.0)))
:food-app-payments (+ (:sales-summary/total-food-app-payments ss 0.0)
(:sales-summary/total-food-app-fees ss 0.0)
(- (:sales-summary/total-food-app-refunds ss 0.0)))
:gift-card-payments (+ (:sales-summary/total-gift-card-payments ss 0.0)
(:sales-summary/total-gift-card-fees ss 0.0)
(- (:sales-summary/total-gift-card-refunds ss 0.0)))
#_#_:refunds (+ (:sales-summary/total-food-app-refunds ss 0.0)
(:sales-summary/total-card-refunds ss 0.0)
(:sales-summary/total-cash-refunds ss 0.0))
:fees (- (:sales-summary/total-card-fees ss 0.0))
:cash-payments (+ (:sales-summary/total-cash-payments ss 0.0)
(- (:sales-summary/total-cash-refunds ss 0.0)))
:total-unknown-processor-payments (:sales-summary/total-unknown-processor-payments ss 0.0)
:discounts (+ (:sales-summary/discount ss 0.0))
:returns (+ (:sales-summary/total-returns ss 0.0))})
(defn sort-items [ss] (defn sort-items [ss]
(sort-by (juxt :ledger-mapped/ledger-side :sales-summary-item/sort-order :sales-summary-item/category) ss)) (sort-by (juxt :ledger-mapped/ledger-side :sales-summary-item/sort-order :sales-summary-item/category) ss))
(defn total-debits [items] (defn total-debits [items]
(->> items (->> items
(filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %))) (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)))
(map #(:ledger-mapped/amount % 0.0)) (map #(:ledger-mapped/amount % 0.0))
(reduce + 0.0))) (reduce + 0.0)))
(defn total-credits [items] (defn total-credits [items]
(->> items (->> items
(filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %))) (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)))
(map #(:ledger-mapped/amount % 0.0)) (map #(:ledger-mapped/amount % 0.0))
(reduce + 0.0))) (reduce + 0.0)))
(defn truncate [s max-len]
(if (> (count s) max-len)
(str (subs s 0 (- max-len 3)) "...")
s))
(def grid-page (def grid-page
(helper/build {:id "entity-table" (helper/build {:id "entity-table"
:id-fn :db/id :id-fn :db/id
:nav com/main-aside-nav :nav com/admin-aside-nav
:fetch-page fetch-page :fetch-page fetch-page
:page-specific-nav filters :page-specific-nav filters
:query-schema query-schema :query-schema query-schema
:row-buttons (fn [_ entity] :row-buttons (fn [_ entity]
[(com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes [(com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes
@@ -147,144 +180,93 @@
svg/pencil)]) svg/pencil)])
:oob-render :oob-render
(fn [request] (fn [request]
[(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) [#_(assoc-in (date-range-field {:value {:start (:start-date (:query-params request))
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :end (:end-date (:query-params request))}
:company)} :id "date-range"}) [1 :hx-swap-oob] true)]) ;; TODO
"POS"] :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
:admin)}
"Admin"]
[:a {:href (bidi/path-for ssr-routes/only-routes [:a {:href (bidi/path-for ssr-routes/only-routes
::route/page)} ::route/page)}
"Sales Summaries"]] "Sales Summaries"]]
:title "Sales Summaries" :title "Sales Summaries"
:entity-name "Daily Summary" :entity-name "Daily Summary"
:route ::route/table :route ::route/table
:headers [{:key "client" :headers [{:key "client"
:name "Client" :name "Client"
:sort-key "client" :sort-key "client"
:hide? (fn [args] :hide? (fn [args]
(= (count (:clients args)) 1)) (= (count (:clients args)) 1))
:render #(-> % :sales-summary/client :client/code)} :render #(-> % :sales-summary/client :client/code)}
{:key "date"
:name "Date"
:sort-key "date"
:render #(some-> % :sales-summary/date (atime/unparse-local atime/normal-date))}
{:key "debits" {:key "date"
:name "Debits" :name "Date"
:sort-key "debits" :sort-key "date"
:class "w-64 align-top" :render #(some-> % :sales-summary/date (atime/unparse-local atime/normal-date))}
:render (fn [ss]
(let [items (:sales-summary/items ss)
debit-items (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) (sort-items items))
credit-count (count (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) items))
total-debits (total-debits items)
total-credits (total-credits items)
delta (- total-debits total-credits)]
[:div.flex.flex-col.h-full
[:div.font-semibold.text-sm "Debits"]
[:ul.space-y-0.5.flex-grow
(for [si debit-items]
[:li.text-sm.text-gray-700
[:span.text-gray-500 (truncate (:sales-summary-item/category si) 30)]
[:span.ml-2 "→"]
[:span.ml-2.font-mono (format "$%,.2f" (:ledger-mapped/amount si))]
(when-not (:ledger-mapped/account si)
[:span.ml-2 (com/pill {:color :red} "?")])])
(for [_ (range (max 0 (- credit-count (count debit-items))))]
[:li "\u00a0"])]
[:div.border-t.mt-1.pt-1.flex.justify-between.items-center.w-full
[:span.font-semibold "Total"]
[:span.font-mono.font-semibold (format "$%,.2f" total-debits)]]
(when-not (dollars-0? delta)
[:div.text-xs.text-red-600.flex.justify-between.w-full.mt-1
[:span "Delta:"]
[:span.font-mono (format "$%,.2f" delta)]])]))}
{:key "credits"
:name "Credits"
:sort-key "credits"
:class "w-64 align-top"
:render (fn [ss]
(let [items (:sales-summary/items ss)
credit-items (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) (sort-items items))
debit-count (count (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) items))
total-debits (total-debits items)
total-credits (total-credits items)
delta (- total-credits total-debits)]
[:div.flex.flex-col.h-full
[:div.font-semibold.text-sm "Credits"]
[:ul.space-y-0.5.flex-grow
(for [si credit-items]
[:li.text-sm.text-gray-700
[:span.text-gray-500 (truncate (:sales-summary-item/category si) 30)]
[:span.ml-2 "→"]
[:span.ml-2.font-mono (format "$%,.2f" (:ledger-mapped/amount si))]
(when-not (:ledger-mapped/account si)
[:span.ml-2 (com/pill {:color :red} "?")])])
(for [_ (range (max 0 (- debit-count (count credit-items))))]
[:li "\u00a0"])]
[:div.border-t.mt-1.pt-1.flex.justify-between.items-center.w-full
[:span.font-semibold "Total"]
[:span.font-mono.font-semibold (format "$%,.2f" total-credits)]]
(when-not (dollars-0? delta)
[:div.text-xs.text-green-600.flex.justify-between.w-full.mt-1
[:span "Delta:"]
[:span.font-mono (format "$%,.2f" delta)]])]))}
{:key "balance" {:key "debits"
:name "Balance" :name "debits"
:sort-key "balance" :sort-key "debits"
:class "w-24" :render (fn [ss]
:render (fn [ss] (let [total-debits (total-debits (:sales-summary/items ss))
(let [items (:sales-summary/items ss) total-credits (total-credits (:sales-summary/items ss))]
total-debits (total-debits items) [:ul
total-credits (total-credits items) (for [si (sort-items (:sales-summary/items ss))
delta (- total-debits total-credits) :when (= :ledger-side/debit (:ledger-mapped/ledger-side si))]
balanced? (dollars= total-debits total-credits) [:li (:sales-summary-item/category si) ": " (format "$%,.2f" (:ledger-mapped/amount si))
missing-account? (some #(not (:ledger-mapped/account %)) items)] (when-not (:ledger-mapped/account si)
[:div.flex.flex-col.items-center.pt-2 [:span.pl-4 (com/pill {:color :red}
(when missing-account? "missing account")])]
[:span.text-red-600.font-bold.text-xs "Missing acct"]) )
(if balanced? [:li (com/pill {:color (if (dollars= total-debits total-credits)
(when-not missing-account? :primary
[:span.text-green-600.font-bold "✓ Balanced"]) :red)} "Total: " (format "$%,.2f" total-debits))]]))}
(list {:key "credits"
[:span.text-red-600.font-mono (format "$%,.2f" (Math/abs delta))] :name "credits"
[:span.text-xs.text-gray-500.mt-1 :sort-key "credits"
(if (> total-debits total-credits) "Debit over" "Credit over")]))]))} :render (fn [ss]
(let [total-debits (total-debits (:sales-summary/items ss))
total-credits (total-credits (:sales-summary/items ss))]
[:ul
(for [si (sort-items (:sales-summary/items ss))
:when (= :ledger-side/credit (:ledger-mapped/ledger-side si))]
[:li (:sales-summary-item/category si) ": " (format "$%,.2f" (:ledger-mapped/amount si))
(when-not (:ledger-mapped/account si)
[:span.pl-4 (com/pill {:color :red}
"missing account")])])
[:li (com/pill {:color (if (dollars= total-debits total-credits)
:primary
:red)} "Total: " (format "$%,.2f" total-credits))]]))}]}))
{:key "links" ;; TODO schema cleanup
:name "Links" ;; Decide on what should be calculated as generating ledger entries, and what should be calculated
:show-starting "lg" ;; as part of the summary
:class "w-8" ;; default thought here is that the summary has more detail (e.g., line items), fees broken out by type
:render (fn [ss] ;; and aggregated into the final ledger entry
(let [ledger-entry (:journal-entry/original-entity ss)] ;; that allows customization at any level.
(when (seq ledger-entry) ;; TODO rename refunds/returns
(link-dropdown
[{:link (hu/url (bidi/path-for client-routes/routes :ledger)
{:exact-match-id (:db/id (first ledger-entry))})
:color :yellow
:content "Ledger entry"}]))))}]}))
(def row* (partial helper/row* grid-page)) (def row* (partial helper/row* grid-page))
(def table* (partial helper/table* grid-page)) (def table* (partial helper/table* grid-page))
(def edit-schema (def edit-schema
[:map [:map
[:db/id entity-id] [:db/id entity-id]
[:sales-summary/client [:map [:db/id entity-id]]] [:sales-summary/client [:map [:db/id entity-id]]]
[:sales-summary/items [:sales-summary/items
[:vector {:coerce? true} [:vector {:coerce? true}
[:and [:and
[:map [:map
[:db/id [:or entity-id temp-id]] [:db/id [:or entity-id temp-id]]
[:sales-summary-item/category [:string {:decode/string strip}]] [:sales-summary-item/category [:string {:decode/string strip}]]
[:sales-summary-item/manual? {:default false :decode/arbitrary (fn [x] (cond [:sales-summary-item/manual? {:default false :decode/arbitrary (fn [x] (cond
(boolean? x) (boolean? x)
x x
(nil? x) (nil? x)
false false
(str/blank? x) (str/blank? x)
false false
:else :else
@@ -293,10 +275,11 @@
[:credit {:optional true} [:maybe money]] [:credit {:optional true} [:maybe money]]
[:debit {:optional true} [:maybe money]]] [:debit {:optional true} [:maybe money]]]
[:fn {:error/message "Must choose one of credit/debit" [:fn {:error/message "Must choose one of credit/debit"
:error/path [:credit]} :error/path [:credit]}
(fn [x] (fn [x]
(not (and (:credit x) (not (and (:credit x)
(:debit x))))]]]]]) (:debit x))))]]]] ])
(defn summary-total-row* [request] (defn summary-total-row* [request]
(let [total-credits (-> request (let [total-credits (-> request
@@ -355,21 +338,21 @@
(defn- account-typeahead* (defn- account-typeahead*
[{:keys [name value client-id]}] [{:keys [name value client-id]}]
[:div.flex.flex-col [:div.flex.flex-col
(com/typeahead {:name name (com/typeahead {:name name
:placeholder "Search..." :placeholder "Search..."
:url (hu/url (bidi/path-for ssr-routes/only-routes :account-search) :url (hu/url (bidi/path-for ssr-routes/only-routes :account-search)
{:client-id client-id {:client-id client-id
:purpose "invoice"}) :purpose "invoice"})
:value value :value value
:content-fn (fn [value] :content-fn (fn [value]
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value) (:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
client-id)))})]) client-id)))})])
(defn sales-summary-item-row* [{:keys [value client-id]}] (defn sales-summary-item-row* [{:keys [value client-id]}]
(let [manual? (fc/field-value (:sales-summary-item/manual? value))] (let [manual? (fc/field-value (:sales-summary-item/manual? value))]
(com/data-grid-row (cond-> {:x-ref "p" (com/data-grid-row (cond-> {:x-ref "p"
:x-data (hx/json {})} :x-data (hx/json {})}
(fc/field-value (:new? value)) (hx/htmx-transition-appear)) (fc/field-value (:new? value)) (hx/htmx-transition-appear ))
(fc/with-field :db/id (fc/with-field :db/id
(com/hidden {:name (fc/field-name) (com/hidden {:name (fc/field-name)
:value (fc/field-value)})) :value (fc/field-value)}))
@@ -381,9 +364,9 @@
(fc/with-field :sales-summary-item/category (fc/with-field :sales-summary-item/category
(if manual? (if manual?
(com/validated-field {:errors (fc/field-errors)} (com/validated-field {:errors (fc/field-errors)}
(com/text-input {:placeholder "Category/Explanation" (com/text-input {:placeholder "Category/Explanation"
:name (fc/field-name) :name (fc/field-name)
:value (fc/field-value)})) :value (fc/field-value)}))
(list (list
(com/hidden {:name (fc/field-name) (com/hidden {:name (fc/field-name)
@@ -392,9 +375,9 @@
(com/data-grid-cell {} (com/data-grid-cell {}
(fc/with-field :ledger-mapped/account (fc/with-field :ledger-mapped/account
(com/validated-field {:errors (fc/field-errors)} (com/validated-field {:errors (fc/field-errors)}
(account-typeahead* {:value (fc/field-value) (account-typeahead* {:value (fc/field-value)
:client-id client-id :client-id client-id
:name (fc/field-name)})))) :name (fc/field-name)}))))
(com/data-grid-cell {:class "text-right"} (com/data-grid-cell {:class "text-right"}
(if manual? (if manual?
@@ -418,7 +401,7 @@
:ledger-side/credit) :ledger-side/credit)
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value)))))) (format "$%,.2f" (fc/field-value (:ledger-mapped/amount value))))))
(com/data-grid-cell {:class "align-top"} (com/data-grid-cell {:class "align-top"}
(when manual? (when manual?
(com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x)))))) (com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x))))))
(defrecord MainStep [linear-wizard] (defrecord MainStep [linear-wizard]
@@ -432,45 +415,46 @@
[]) [])
(step-schema [_] (step-schema [_]
(mut/select-keys (mm/form-schema linear-wizard) #{:db/id :sales-summary/items})) (mut/select-keys (mm/form-schema linear-wizard) #{:db/id :sales-summary/items}))
(render-step (render-step
[this {:keys [multi-form-state] :as request}] [this {:keys [multi-form-state] :as request}]
(mm/default-render-step (mm/default-render-step
linear-wizard this linear-wizard this
:head [:div.p-2 "New invoice"] :head [:div.p-2 "New invoice"]
:body (mm/default-step-body :body (mm/default-step-body
{} {}
[:div [:div
(fc/with-field :db/id (fc/with-field :db/id
(com/hidden {:name (fc/field-name) (com/hidden {:name (fc/field-name)
:value (fc/field-value)})) :value (fc/field-value)}))
(com/data-grid {:headers (com/data-grid {:headers
[(com/data-grid-header {} "Category") [(com/data-grid-header {} "Category")
(com/data-grid-header {} "Account") (com/data-grid-header {} "Account")
(com/data-grid-header {} "Debits") (com/data-grid-header {} "Debits")
(com/data-grid-header {} "Credits") (com/data-grid-header {} "Credits")
(com/data-grid-header {} "")]} (com/data-grid-header {} "")]}
(fc/with-field :sales-summary/items (fc/with-field :sales-summary/items
(list (list
(fc/cursor-map #(sales-summary-item-row* {:value % (fc/cursor-map #(sales-summary-item-row* {:value %
:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})) :client-id (:db/id (:sales-summary/client (:snapshot multi-form-state))) }))
(com/data-grid-new-row {:colspan 5 ;; TODO
:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item) (com/data-grid-new-row {:colspan 5
:row-offset 0 :hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item)
:index (count (fc/field-value)) :row-offset 0
:tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}} :index (count (fc/field-value))
"New Summary Item"))) :tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}} ;; TODO
(summary-total-row* request) "New Summary Item")))
(unbalanced-row* request))]) (summary-total-row* request)
(unbalanced-row* request)) ])
:footer :footer
(mm/default-step-footer linear-wizard this :validation-route ::route/edit-wizard-navigate) (mm/default-step-footer linear-wizard this :validation-route ::route/edit-wizard-navigate)
:validation-route ::route/edit-wizard-navigate :validation-route ::route/edit-wizard-navigate
:width-height-class "lg:w-[850px] lg:h-[900px]"))) :width-height-class "lg:w-[850px] lg:h-[900px]")))
(defn attach-ledger [i] (defn attach-ledger [i]
(cond-> i (cond-> i
(:credit i) (assoc :ledger-mapped/ledger-side :ledger-side/credit (:credit i) (assoc :ledger-mapped/ledger-side :ledger-side/credit
:ledger-mapped/amount (:credit i)) :ledger-mapped/amount (:credit i))
(:debit i) (assoc :ledger-mapped/ledger-side :ledger-side/debit (:debit i) (assoc :ledger-mapped/ledger-side :ledger-side/debit
@@ -486,8 +470,8 @@
(navigate [this step-key] (navigate [this step-key]
(assoc this :current-step step-key)) (assoc this :current-step step-key))
(get-current-step (get-current-step
[this] [this]
(mm/get-step this :main)) (mm/get-step this :main))
(render-wizard [this {:keys [multi-form-state] :as request}] (render-wizard [this {:keys [multi-form-state] :as request}]
(mm/default-render-wizard (mm/default-render-wizard
this request this request
@@ -499,28 +483,29 @@
(steps [_] (steps [_]
[:main]) [:main])
(get-step [this step-key] (get-step [this step-key]
(let [step-key-result (mc/parse mm/step-key-schema step-key) (let [step-key-result (mc/parse mm/step-key-schema step-key)
[step-key-type step-key] step-key-result] [step-key-type step-key] step-key-result]
(->MainStep this))) (->MainStep this)))
(form-schema [_] (form-schema [_]
edit-schema) edit-schema)
(submit [this {:keys [multi-form-state request-method identity] :as request}] (submit [this {:keys [multi-form-state request-method identity] :as request}]
(let [result (:snapshot multi-form-state) (let [result (:snapshot multi-form-state )
transaction [:upsert-sales-summary {:db/id (:db/id result) transaction [:upsert-entity {:db/id (:db/id result)
:sales-summary/items (map :sales-summary/items (map
(fn [i] (fn [i]
(if (:sales-summary-item/manual? i) (if (:sales-summary-item/manual? i)
(attach-ledger i) (attach-ledger i)
{:db/id (:db/id i) {:db/id (:db/id i)
:ledger-mapped/account (:ledger-mapped/account i)})) :ledger-mapped/account (:ledger-mapped/account i)
(:sales-summary/items result))}]] }))
(:sales-summary/items result))}]]
(clojure.pprint/pprint (:sales-summary/items result)) (clojure.pprint/pprint (:sales-summary/items result))
@(dc/transact conn [transaction]) @(dc/transact conn [ transaction])
(html-response (html-response
(row* identity (dc/pull (dc/db conn) default-read (:db/id result)) (row* identity (dc/pull (dc/db conn) default-read (:db/id result))
{:flash? true {:flash? true
:request request}) :request request})
:headers (cond-> {"hx-trigger" "modalclose" :headers (cond-> {"hx-trigger" "modalclose"
"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id result)) "hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id result))
"hx-reswap" "outerHTML"}))))) "hx-reswap" "outerHTML"})))))
@@ -540,8 +525,8 @@
(def key->handler (def key->handler
(apply-middleware-to-all-handlers (apply-middleware-to-all-handlers
(->> (->>
{::route/page (helper/page-route grid-page) {::route/page (helper/page-route grid-page)
::route/table (helper/table-route grid-page) ::route/table (helper/table-route grid-page)
::route/edit-wizard (-> mm/open-wizard-handler ::route/edit-wizard (-> mm/open-wizard-handler
(mm/wrap-wizard edit-wizard) (mm/wrap-wizard edit-wizard)
(mm/wrap-init-multi-form-state initial-edit-wizard-state) (mm/wrap-init-multi-form-state initial-edit-wizard-state)
@@ -559,13 +544,17 @@
(wrap-schema-enforce :query-schema [:map (wrap-schema-enforce :query-schema [:map
[:client-id {:optional true} [:client-id {:optional true}
[:maybe entity-id]]])) [:maybe entity-id]]]))
::route/edit-wizard-submit (-> mm/submit-handler ::route/edit-wizard-submit (-> mm/submit-handler
(mm/wrap-wizard edit-wizard) (mm/wrap-wizard edit-wizard)
(mm/wrap-decode-multi-form-state))}) (mm/wrap-decode-multi-form-state))})
(fn [h] (fn [h]
(-> h (-> h
(wrap-copy-qp-pqp) (wrap-copy-qp-pqp)
(wrap-apply-sort grid-page) (wrap-apply-sort grid-page)
(wrap-merge-prior-hx) (wrap-merge-prior-hx)
(wrap-schema-enforce :query-schema query-schema) (wrap-schema-enforce :query-schema query-schema)
(wrap-schema-enforce :hx-schema query-schema))))) (wrap-schema-enforce :hx-schema query-schema)
(wrap-admin)
(wrap-client-redirect-unauthenticated)))))

View File

@@ -6,8 +6,7 @@
[auto-ap.routes.admin.excel-invoices :as ei-routes] [auto-ap.routes.admin.excel-invoices :as ei-routes]
[auto-ap.routes.admin.import-batch :as ib-routes] [auto-ap.routes.admin.import-batch :as ib-routes]
[auto-ap.routes.admin.transaction-rules :as transaction-rules] [auto-ap.routes.admin.transaction-rules :as transaction-rules]
[auto-ap.routes.admin.vendors :as v-routes] [auto-ap.routes.admin.vendors :as v-routes]
[auto-ap.routes.pos.sales-summaries :as ss-routes]
[auto-ap.routes.invoice :as invoice-route] [auto-ap.routes.invoice :as invoice-route]
[auto-ap.routes.ledger :as ledger-routes] [auto-ap.routes.ledger :as ledger-routes]
[auto-ap.routes.outgoing-invoice :as oi-routes] [auto-ap.routes.outgoing-invoice :as oi-routes]
@@ -91,8 +90,8 @@
(#{::invoice-route/all-page ::invoice-route/unpaid-page ::invoice-route/voided-page ::invoice-route/paid-page ::oi-routes/new ::invoice-route/import-page :invoice-glimpse :invoice-glimpse-textract-invoice} (:matched-route request)) (#{::invoice-route/all-page ::invoice-route/unpaid-page ::invoice-route/voided-page ::invoice-route/paid-page ::oi-routes/new ::invoice-route/import-page :invoice-glimpse :invoice-glimpse-textract-invoice} (:matched-route request))
"invoices" "invoices"
(#{:pos-sales :pos-expected-deposits :pos-tenders :pos-refunds :pos-cash-drawer-shifts ::ss-routes/page} (:matched-route request)) (#{:pos-sales :pos-expected-deposits :pos-tenders :pos-refunds :pos-cash-drawer-shifts} (:matched-route request))
"sales" "sales"
(#{::payment-routes/all-page ::payment-routes/pending-page ::payment-routes/cleared-page ::payment-routes/voided-page} (:matched-route request)) (#{::payment-routes/all-page ::payment-routes/pending-page ::payment-routes/cleared-page ::payment-routes/voided-page} (:matched-route request))
"payments" "payments"
(#{::ledger-routes/all-page ::ledger-routes/external-page ::ledger-routes/external-import-page ::ledger-routes/balance-sheet ::ledger-routes/cash-flows ::ledger-routes/profit-and-loss} (:matched-route request)) (#{::ledger-routes/all-page ::ledger-routes/external-page ::ledger-routes/external-import-page ::ledger-routes/balance-sheet ::ledger-routes/cash-flows ::ledger-routes/profit-and-loss} (:matched-route request))
@@ -208,18 +207,12 @@
:hx-boost "true"} :hx-boost "true"}
"Refunds") "Refunds")
(menu-button- {:href (str (bidi/path-for ssr-routes/only-routes
:pos-cash-drawer-shifts)
"?date-range=week")
:active? (= :pos-cash-drawer-shifts (:matched-route request))
:hx-boost "true"}
"Cash drawer shifts")
(menu-button- {:href (str (bidi/path-for ssr-routes/only-routes (menu-button- {:href (str (bidi/path-for ssr-routes/only-routes
::ss-routes/page) :pos-cash-drawer-shifts)
"?date-range=week") "?date-range=week")
:active? (= ::ss-routes/page (:matched-route request)) :active? (= :pos-cash-drawer-shifts (:matched-route request))
:hx-boost "true"} :hx-boost "true"}
"Summaries")))) "Cash drawer shifts"))))
(menu-button- {"@click.prevent" "if (selected == 'payments') {selected = null } else { selected = 'payments'} " (menu-button- {"@click.prevent" "if (selected == 'payments') {selected = null } else { selected = 'payments'} "
:icon svg/payments} :icon svg/payments}

View File

@@ -12,7 +12,7 @@
[auto-ap.ssr.admin.excel-invoice :as admin-excel-invoices] [auto-ap.ssr.admin.excel-invoice :as admin-excel-invoices]
[auto-ap.ssr.admin.history :as history] [auto-ap.ssr.admin.history :as history]
[auto-ap.ssr.admin.import-batch :as import-batch] [auto-ap.ssr.admin.import-batch :as import-batch]
[auto-ap.ssr.pos.sales-summaries :as pos-sales-summaries] [auto-ap.ssr.admin.sales-summaries :as admin-sales-summaries]
[auto-ap.ssr.admin.transaction-rules :as admin-rules] [auto-ap.ssr.admin.transaction-rules :as admin-rules]
[auto-ap.ssr.admin.vendors :as admin-vendors] [auto-ap.ssr.admin.vendors :as admin-vendors]
[auto-ap.ssr.auth :as auth] [auto-ap.ssr.auth :as auth]
@@ -85,17 +85,17 @@
(into company-1099/key->handler) (into company-1099/key->handler)
(into invoice/key->handler) (into invoice/key->handler)
(into import-batch/key->handler) (into import-batch/key->handler)
(into pos-sales/key->handler) (into pos-sales/key->handler)
(into pos-expected-deposits/key->handler) (into pos-expected-deposits/key->handler)
(into pos-tenders/key->handler) (into pos-tenders/key->handler)
(into pos-cash-drawer-shifts/key->handler) (into pos-cash-drawer-shifts/key->handler)
(into pos-refunds/key->handler) (into pos-refunds/key->handler)
(into pos-sales-summaries/key->handler) (into users/key->handler)
(into users/key->handler) (into admin-accounts/key->handler)
(into admin-accounts/key->handler) (into admin-excel-invoices/key->handler)
(into admin-excel-invoices/key->handler) (into admin/key->handler)
(into admin/key->handler) (into admin-jobs/key->handler)
(into admin-jobs/key->handler) (into admin-sales-summaries/key->handler)
(into admin-vendors/key->handler) (into admin-vendors/key->handler)
(into admin-clients/key->handler) (into admin-clients/key->handler)
(into admin-rules/key->handler) (into admin-rules/key->handler)

View File

@@ -120,8 +120,7 @@
(list (list
[:div.text-2xl.font-bold.text-gray-600 (str "Balance Sheet - " (str/join ", " (map :client/name client))) ] [:div.text-2xl.font-bold.text-gray-600 (str "Balance Sheet - " (str/join ", " (map :client/name client))) ]
(rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count)) (rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count))
(> (count date) 1) (into (repeat 13 (* 2 client-count (dec (count date))))) (> (count date) 1) (into (repeat 13 (* 2 client-count (dec (count date))))))
(and (> client-count 1) (= (count date) 1)) (conj 13))
:investigate-url (bidi.bidi/path-for ssr-routes/only-routes ::route/investigate) :investigate-url (bidi.bidi/path-for ssr-routes/only-routes ::route/investigate)
:table report :table report
:warning (not-empty (str/join "\n " (filter not-empty [warning (:warning report)])))} ))))]) :warning (not-empty (str/join "\n " (filter not-empty [warning (:warning report)])))} ))))])
@@ -202,9 +201,8 @@
(conj [:paragraph {:color [128 0 0] :size 9} (:warning report)]) (conj [:paragraph {:color [128 0 0] :size 9} (:warning report)])
(conj (conj
(table->pdf report (table->pdf report
(cond-> (into [30 ] (repeat client-count 13)) (cond-> (into [30 ] (repeat client-count 13))
(> (count date) 1) (into (repeat (* 2 client-count (dec (count date))) 13 )) (> (count date) 1) (into (repeat (* 2 client-count (dec (count date))) 13 ))))))
(and (> client-count 1) (= (count date) 1)) (conj 13)))))
output-stream) output-stream)
(.toByteArray output-stream))) (.toByteArray output-stream)))

View File

@@ -798,34 +798,30 @@
(defn balance-sheet-headers [pnl-data] (defn balance-sheet-headers [pnl-data]
(let [period-count (count (:periods (:args pnl-data))) (let [period-count (count (:periods (:args pnl-data)))]
client-ids (set (map :client-id (:data pnl-data)))
client-count (count client-ids)
show-total? (and (> client-count 1) (= 1 period-count))]
(cond-> [] (cond-> []
(> client-count 1) (> (count (set (map :client-id (:data pnl-data)))) 1)
(conj (cond-> (into [{:value "Client"}] (conj (into [{:value "Client"}]
(mapcat identity
(for [client client-ids]
(cond-> [{:value (str (-> pnl-data :client-codes (get client)))}]
(> period-count 1)
(into (apply concat (repeat (dec period-count) ["" ""])))))))
show-total? (conj {:value "Total" :bold true :border [:left]})))
(mapcat identity
(for [client (set (map :client-id (:data pnl-data))) ]
(cond-> [{:value (str (-> pnl-data :client-codes (get client)))}]
(> period-count 1)
(into (apply concat (repeat (dec period-count) ["" ""]))))))))
true true
(conj (cond-> (into [{:value "Period Ending"}] (conj (into [{:value "Period Ending"}]
(for [client client-ids (for [client (set (map :client-id (:data pnl-data)))
[index p] (map vector (range) (:periods (:args pnl-data))) [index p] (map vector (range) (:periods (:args pnl-data)))
:let [is-first? (= 0 index) :let [is-first? (= 0 index)
period-date (date->str p) period-date (date->str p)
period-headers (if (or is-first? period-headers (if (or is-first?
(not (:include-deltas (:args pnl-data)))) (not (:include-deltas (:args pnl-data))))
[{:value period-date}] [{:value period-date}]
[{:value period-date} [{:value period-date}
{:value "+/-"}])] {:value "+/-"}])]
header period-headers] header period-headers]
header)) header))))))
show-total? (conj {:value (date->str (first (:periods (:args pnl-data)))) :border [:left]}))))))
(defn append-deltas [table] (defn append-deltas [table]
(->> table (->> table
@@ -894,33 +890,12 @@
:rows table}))) :rows table})))
) )
(defn add-total-border [rows]
(map (fn [row]
(let [last-idx (dec (count row))]
(map-indexed
(fn [i cell]
(if (= i last-idx)
(let [borders (or (:border cell) [])]
(assoc cell :border (conj borders :left)))
cell))
row)))
rows))
(defn summarize-balance-sheet [pnl-data] (defn summarize-balance-sheet [pnl-data]
(let [client-ids (set (map :client-id (:data pnl-data))) (let [pnl-datas (for [client-id (set (map :client-id (:data pnl-data)))
client-count (count client-ids) p (:periods (:args pnl-data))]
period-count (count (:periods (:args pnl-data))) (-> pnl-data
show-total? (and (> client-count 1) (= 1 period-count)) (filter-client client-id)
pnl-datas (for [client-id client-ids (filter-period p)))]
p (:periods (:args pnl-data))]
(-> pnl-data
(filter-client client-id)
(filter-period p)))
total-data (when show-total?
(-> pnl-data
(filter-period (first (:periods (:args pnl-data))))
(assoc :cell-args {:bold true})))
pnl-datas (concat pnl-datas (when total-data [total-data]))]
(let [table (-> [] (let [table (-> []
(into (detail-rows pnl-datas (into (detail-rows pnl-datas
:assets :assets
@@ -937,11 +912,10 @@
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable})) (negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
pnl-datas) pnl-datas)
"Retained Earnings"))) "Retained Earnings")))
table (if (and (> period-count 1) table (if (and (> (count (:periods (:args pnl-data))) 1)
(:include-deltas (:args pnl-data))) (:include-deltas (:args pnl-data)))
(append-deltas table) (append-deltas table)
table) table)]
table (if show-total? (add-total-border table) table)]
{:warning (warning-message pnl-data) {:warning (warning-message pnl-data)
:header (balance-sheet-headers pnl-data) :header (balance-sheet-headers pnl-data)
:rows table})) :rows table}))

View File

@@ -1,7 +1,9 @@
(ns auto-ap.routes.pos.sales-summaries) (ns auto-ap.routes.admin.sales-summaries)
(def routes {"" {:get ::page (def routes {"" {:get ::page
:put ::edit-wizard-submit} :put ::edit-wizard-submit}
"/table" ::table "/table" ::table
["/" [#"\d+" :db/id]] {:get ::edit-wizard } ["/" [#"\d+" :db/id]] {:get ::edit-wizard }
"/edit/navigate" ::edit-wizard-navigate "/edit/navigate" ::edit-wizard-navigate
"/edit/sales-summary-item" ::new-summary-item}) "/edit/sales-summary-item" ::new-summary-item})

View File

@@ -12,7 +12,7 @@
[auto-ap.routes.transactions :as t-routes] [auto-ap.routes.transactions :as t-routes]
[auto-ap.routes.admin.clients :as ac-routes] [auto-ap.routes.admin.clients :as ac-routes]
[auto-ap.routes.pos.sales-summaries :as ss-routes] [auto-ap.routes.admin.sales-summaries :as ss-routes]
[auto-ap.routes.admin.transaction-rules :as tr-routes])) [auto-ap.routes.admin.transaction-rules :as tr-routes]))
(def routes {"impersonate" :impersonate (def routes {"impersonate" :impersonate

View File

@@ -265,8 +265,7 @@ NOTE: Please review the transactions we may have question for you here: https://
[:div.notification.is-warning.is-light [:div.notification.is-warning.is-light
(:warning report)]) (:warning report)])
[rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count)) [rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count))
(:include-comparison args) (into (repeat 13 (* 2 client-count))) (:include-comparison args) (into (repeat 13 (* 2 client-count))))
(and (> client-count 1) (not (:include-comparison args))) (conj 13))
:click-event ::investigate-clicked :click-event ::investigate-clicked
:table report}]])) :table report}]]))

View File

@@ -1,2 +1,5 @@
#!/bin/bash #!/bin/bash
sudo docker run --rm -ti -v ~/dev/integreat/data/solr:/var/solr --network=bridge -p 8983:8983 679918342773.dkr.ecr.us-east-1.amazonaws.com/integreat-solr sudo docker run --rm -ti -v ~/dev/integreat/data/solr:/var/solr --network=bridge -p 8983:8983 bryce-solr
#sudo podman container run --user 1000 --privileged --volume /home/notid/dev/integreat/data/solr:/var/solr -p 8983:8983 bryce-solr