3 Commits

Author SHA1 Message Date
63eb5b5954 refactor: remove dead calc-aggregate-totals and unused schema attributes
The 13 sales-summary/total-* attributes were computed and stored but never
read — the only consumer (get-debits) was commented out. Active display code
computes totals on-the-fly from the items list instead.
2026-05-01 15:40:42 -07:00
af66049f39 consolidate sales summary ledger entry creation into upsert-sales-summary tx
Move journal entry calculation and creation from the reconcile-ledger
background job into the upsert-sales-summary tx function. Now any save
of a sales summary (job recalculation, admin edit wizard, or manual
touch) automatically creates the journal entry if balanced with all
accounts mapped, or retracts it if conditions no longer hold. Eliminates
the need for a separate upsert-sales-summary-ledger call and the
reconcile ledger pass for sales summaries.
2026-05-01 14:12:05 -07:00
2bcebc4424 feat: complete automatic sales summary calculations and ledger posting 2026-04-25 08:44:19 -07:00
8 changed files with 391 additions and 131 deletions

View File

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

@@ -38,18 +38,19 @@
(defn regenerate-literals [] (defn regenerate-literals []
(require 'com.github.ivarref.gen-fn) (require 'com.github.ivarref.gen-fn)
(spit (spit
"resources/functions.edn" "resources/functions.edn"
(let [datomic-fn @(resolve 'com.github.ivarref.gen-fn/datomic-fn)] (let [datomic-fn @(resolve 'com.github.ivarref.gen-fn/datomic-fn)]
[(datomic-fn :pay #'iol-ion.tx.pay/pay) [(datomic-fn :pay #'iol-ion.tx.pay/pay)
(datomic-fn :plus #'iol-ion.tx.plus/plus) (datomic-fn :plus #'iol-ion.tx.plus/plus)
(datomic-fn :propose-invoice #'iol-ion.tx.propose-invoice/propose-invoice) (datomic-fn :propose-invoice #'iol-ion.tx.propose-invoice/propose-invoice)
(datomic-fn :reset-rels #'iol-ion.tx.reset-rels/reset-rels) (datomic-fn :reset-rels #'iol-ion.tx.reset-rels/reset-rels)
(datomic-fn :reset-scalars #'iol-ion.tx.reset-scalars/reset-scalars) (datomic-fn :reset-scalars #'iol-ion.tx.reset-scalars/reset-scalars)
(datomic-fn :upsert-entity #'iol-ion.tx.upsert-entity/upsert-entity) (datomic-fn :upsert-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)

View File

@@ -0,0 +1,60 @@
(ns iol-ion.tx.upsert-sales-summary-ledger
(:require [datomic.api :as dc]
[iol-ion.tx.upsert-entity :as upsert-entity]
[iol-ion.tx.upsert-ledger :as upsert-ledger]))
(defn summary->journal-entry [db summary-id]
(let [summary (dc/pull db '[:sales-summary/client
:sales-summary/date
{:sales-summary/items [:sales-summary-item/category
:ledger-mapped/account
:ledger-mapped/amount
:ledger-mapped/ledger-side]}]
summary-id)
items (:sales-summary/items summary)
aggregated (->> items
(filter :ledger-mapped/account)
(group-by :ledger-mapped/account)
(map (fn [[account acc-items]]
(reduce
(fn [m item]
(update m (:ledger-mapped/ledger-side item) (fnil + 0.0) (:ledger-mapped/amount item 0.0)))
{:account account}
acc-items))))
line-items (mapv (fn [{:keys [account] :as m}]
(cond-> {:db/id (str (java.util.UUID/randomUUID))
:journal-entry-line/account account
: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))]
(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 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)]
(into upserted-summary
(if journal-entry
[[:upsert-ledger journal-entry]]
(let [existing-je-id (ffirst (dc/q '[:find ?je . :in $ ?ss
:where [?je :journal-entry/original-entity ?ss]]
db-after summary-id))]
(concat
(when existing-je-id [[:db/retractEntity existing-je-id]])
(when client-id [{:db/id client-id
:client/ledger-last-change (upsert-ledger/current-date db)}])))))))

View File

@@ -1949,12 +1949,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,10 +5,11 @@
[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,46 +278,42 @@
(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]} (dirty-sales-summaries c)] {:sales-summary/keys [date] :db/keys [id] :as existing-summary} (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 [result {:db/id id (let [manual-items (->> existing-summary
:sales-summary/client c :sales-summary/items
:sales-summary/date date (filter :sales-summary-item/manual?))
:sales-summary/dirty false calculated-items (->>
:sales-summary/client+date [c date] (get-sales c date)
(concat (get-payment-items c date))
:sales-summary/items (concat (get-refund-items c date))
(->> (cons (get-discounts c date))
(get-sales c date) (cons (get-fees c date))
(concat (get-payment-items c date)) (cons (get-tax c date))
(concat (get-refund-items c date)) (cons (get-tip c date))
(cons (get-discounts c date)) (cons (get-returns c date))
(cons (get-fees c date)) (filter identity)
(cons (get-tax c date)) (map (fn [z]
(cons (get-tip c date)) (assoc z :ledger-mapped/account (some-> z :sales-summary-item/category str/lower-case name->number lookup-account)
(cons (get-returns c date)) :sales-summary-item/manual? false))))
(filter identity) all-items (concat calculated-items manual-items)
(map (fn [z] result {:db/id id
(assoc z :ledger-mapped/account (some-> z :sales-summary-item/category str/lower-case name->number lookup-account) :sales-summary/client c
:sales-summary-item/manual? false)) :sales-summary/date date
)) }] :sales-summary/dirty false
(if (seq (:sales-summary/items result)) :sales-summary/client+date [c date]
(do :sales-summary/items all-items}]
(alog/info ::upserting-summaries (if (seq (:sales-summary/items result))
:category-count (count (:sales-summary/items result))) (do
@(dc/transact conn [[:upsert-entity result]])) (alog/info ::upserting-summaries
@(dc/transact conn [{:db/id id :sales-summary/dirty false}])))))) :category-count (count (:sales-summary/items result)))
@(dc/transact conn [[:upsert-sales-summary result]]))
(let [c (auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGCL" ]) @(dc/transact conn [{:db/id id :sales-summary/dirty false}]))))))
date #inst "2024-04-14T00:00:00-07:00"]
(get-payment-items c date)
)
(defn reset-summaries [] (defn reset-summaries []
@@ -334,11 +330,6 @@
(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))

View File

@@ -35,27 +35,42 @@
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)
@(dc/transact conn repairs))))) :sales-summary-count (count sales-summaries-missing-ledger-entries))
@(dc/transact conn repairs)))))
(defn touch-transaction [e] (defn touch-transaction [e]

View File

@@ -72,14 +72,14 @@
[: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]
} ;; TODO clientize }
:ledger-mapped/account :ledger-mapped/account
:ledger-mapped/amount :ledger-mapped/amount
:sales-summary-item/category :sales-summary-item/category
:sales-summary-item/sort-order :sales-summary-item/sort-order
:db/id :db/id
:sales-summary-item/manual?] :sales-summary-item/manual?]
} ]) ;; TODO } ])
(defn fetch-ids [db request] (defn fetch-ids [db request]
(let [query-params (:query-params request) (let [query-params (:query-params request)
@@ -129,26 +129,6 @@
[(->> (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))
@@ -179,10 +159,8 @@
:db/id (:db/id entity))} :db/id (:db/id entity))}
svg/pencil)]) svg/pencil)])
:oob-render :oob-render
(fn [request] (fn [_request]
[#_(assoc-in (date-range-field {:value {:start (:start-date (:query-params request)) [])
:end (:end-date (:query-params request))}
:id "date-range"}) [1 :hx-swap-oob] true)]) ;; TODO
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
:admin)} :admin)}
"Admin"] "Admin"]
@@ -241,13 +219,9 @@
:primary :primary
:red)} "Total: " (format "$%,.2f" total-credits))]]))}]})) :red)} "Total: " (format "$%,.2f" total-credits))]]))}]}))
;; TODO schema cleanup ;; Architecture: Sales summary maintains granular detail (line items, fee types)
;; Decide on what should be calculated as generating ledger entries, and what should be calculated ;; and is aggregated into ledger entries by account/location. Manual adjustments
;; as part of the summary ;; are preserved during automatic recalculation.
;; default thought here is that the summary has more detail (e.g., line items), fees broken out by type
;; and aggregated into the final ledger entry
;; that allows customization at any level.
;; TODO rename refunds/returns
(def row* (partial helper/row* grid-page)) (def row* (partial helper/row* grid-page))
(def table* (partial helper/table* grid-page)) (def table* (partial helper/table* grid-page))
@@ -436,15 +410,14 @@
(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))) }))
;; TODO (com/data-grid-new-row {:colspan 5
(com/data-grid-new-row {:colspan 5 :hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item)
:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item) :row-offset 0
:row-offset 0 :index (count (fc/field-value))
:index (count (fc/field-value)) :tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}}
:tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}} ;; TODO "New Summary Item")))
"New Summary Item")))
(summary-total-row* request) (summary-total-row* request)
(unbalanced-row* request)) ]) (unbalanced-row* request)) ])
@@ -490,7 +463,7 @@
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-entity {:db/id (:db/id result) transaction [:upsert-sales-summary {: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)