feat: complete automatic sales summary calculations and ledger posting
This commit is contained in:
@@ -5,10 +5,11 @@
|
||||
[iol-ion.tx.propose-invoice]
|
||||
[iol-ion.tx.reset-rels]
|
||||
[iol-ion.tx.reset-scalars]
|
||||
[iol-ion.tx.upsert-entity]
|
||||
[iol-ion.tx.upsert-invoice]
|
||||
[iol-ion.tx.upsert-ledger]
|
||||
[iol-ion.tx.upsert-transaction]
|
||||
[iol-ion.tx.upsert-entity]
|
||||
[iol-ion.tx.upsert-invoice]
|
||||
[iol-ion.tx.upsert-ledger]
|
||||
[iol-ion.tx.upsert-transaction]
|
||||
[iol-ion.tx.upsert-sales-summary-ledger]
|
||||
[com.github.ivarref.gen-fn :refer [gen-fn! datomic-fn]]
|
||||
[auto-ap.utils :refer [default-pagination-size by]]
|
||||
[clojure.edn :as edn]
|
||||
|
||||
@@ -276,48 +276,108 @@
|
||||
:ledger-mapped/amount amount
|
||||
:ledger-mapped/ledger-side :ledger-side/debit}))
|
||||
|
||||
(defn- summary->journal-entry [db summary-id]
|
||||
(let [summary (dc/pull db '[:sales-summary/client
|
||||
:sales-summary/date
|
||||
{:sales-summary/items [:sales-summary-item/category
|
||||
:ledger-mapped/account
|
||||
:ledger-mapped/amount
|
||||
:ledger-mapped/ledger-side]}]
|
||||
summary-id)
|
||||
items (:sales-summary/items summary)
|
||||
;; Aggregate items by account and ledger side
|
||||
aggregated (->> items
|
||||
(filter :ledger-mapped/account)
|
||||
(group-by :ledger-mapped/account)
|
||||
(map (fn [[account acc-items]]
|
||||
(reduce
|
||||
(fn [m item]
|
||||
(update m (:ledger-mapped/ledger-side item) (fnil + 0.0) (:ledger-mapped/amount item 0.0)))
|
||||
{:account account}
|
||||
acc-items))))
|
||||
line-items (mapv (fn [{:keys [account] :as m}]
|
||||
(cond-> {:db/id (str (java.util.UUID/randomUUID))
|
||||
:journal-entry-line/account account}
|
||||
(get m :ledger-side/debit) (assoc :journal-entry-line/debit (get m :ledger-side/debit))
|
||||
(get m :ledger-side/credit) (assoc :journal-entry-line/credit (get m :ledger-side/credit))))
|
||||
aggregated)
|
||||
total-amount (reduce + 0.0 (map #(get % :ledger-side/debit 0.0) aggregated))]
|
||||
(when (seq line-items)
|
||||
{:journal-entry/source "sales-summary"
|
||||
:journal-entry/client (:db/id (:sales-summary/client summary))
|
||||
:journal-entry/date (:sales-summary/date summary)
|
||||
:journal-entry/original-entity summary-id
|
||||
:journal-entry/amount total-amount
|
||||
:journal-entry/line-items line-items})))
|
||||
|
||||
(defn- calc-aggregate-totals [items]
|
||||
(reduce
|
||||
(fn [acc item]
|
||||
(let [cat (:sales-summary-item/category item)
|
||||
amt (:ledger-mapped/amount item 0.0)
|
||||
side (:ledger-mapped/ledger-side item)]
|
||||
(cond-> acc
|
||||
;; Payments (debits)
|
||||
(= cat "Card Payments") (update :sales-summary/total-card-payments (fnil + 0.0) amt)
|
||||
(= cat "Cash Payments") (update :sales-summary/total-cash-payments (fnil + 0.0) amt)
|
||||
(= cat "Food App Payments") (update :sales-summary/total-food-app-payments (fnil + 0.0) amt)
|
||||
(= cat "Gift Card Payments") (update :sales-summary/total-gift-card-payments (fnil + 0.0) amt)
|
||||
(= cat "Unknown") (update :sales-summary/total-unknown-payments (fnil + 0.0) amt)
|
||||
;; Refunds (credits)
|
||||
(= cat "Card Refunds") (update :sales-summary/total-card-refunds (fnil + 0.0) amt)
|
||||
(= cat "Cash Refunds") (update :sales-summary/total-cash-refunds (fnil + 0.0) amt)
|
||||
(= cat "Food App Refunds") (update :sales-summary/total-food-app-refunds (fnil + 0.0) amt)
|
||||
;; Other
|
||||
(= cat "Fees") (update :sales-summary/total-fees (fnil + 0.0) amt)
|
||||
(= cat "Discounts") (update :sales-summary/total-discounts (fnil + 0.0) amt)
|
||||
(= cat "Tax") (update :sales-summary/total-tax (fnil + 0.0) amt)
|
||||
(= cat "Tip") (update :sales-summary/total-tip (fnil + 0.0) amt)
|
||||
(= cat "Returns") (update :sales-summary/total-returns (fnil + 0.0) amt)
|
||||
;; Net from sales line items
|
||||
:else (update :sales-summary/total-net (fnil + 0.0) amt))))
|
||||
{}
|
||||
items))
|
||||
|
||||
(defn sales-summaries-v2 []
|
||||
(doseq [[c client-code] (dc/q '[:find ?c ?client-code
|
||||
:in $
|
||||
:where [?c :client/code ?client-code]]
|
||||
(dc/db conn))
|
||||
{:sales-summary/keys [date] :db/keys [id]} (dirty-sales-summaries c)]
|
||||
:in $
|
||||
:where [?c :client/code ?client-code]]
|
||||
(dc/db conn))
|
||||
{:sales-summary/keys [date] :db/keys [id] :as existing-summary} (dirty-sales-summaries c)]
|
||||
(mu/with-context {:client-code client-code
|
||||
:date date}
|
||||
(alog/info ::updating)
|
||||
(let [result {:db/id id
|
||||
:sales-summary/client c
|
||||
:sales-summary/date date
|
||||
:sales-summary/dirty false
|
||||
:sales-summary/client+date [c date]
|
||||
|
||||
:sales-summary/items
|
||||
(->>
|
||||
(get-sales c date)
|
||||
(concat (get-payment-items c date))
|
||||
(concat (get-refund-items c date))
|
||||
(cons (get-discounts c date))
|
||||
(cons (get-fees c date))
|
||||
(cons (get-tax c date))
|
||||
(cons (get-tip c date))
|
||||
(cons (get-returns c date))
|
||||
(filter identity)
|
||||
(map (fn [z]
|
||||
(assoc z :ledger-mapped/account (some-> z :sales-summary-item/category str/lower-case name->number lookup-account)
|
||||
:sales-summary-item/manual? false))
|
||||
)) }]
|
||||
(if (seq (:sales-summary/items result))
|
||||
(do
|
||||
(alog/info ::upserting-summaries
|
||||
:category-count (count (:sales-summary/items result)))
|
||||
@(dc/transact conn [[:upsert-entity result]]))
|
||||
@(dc/transact conn [{:db/id id :sales-summary/dirty false}]))))))
|
||||
|
||||
(let [c (auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGCL" ])
|
||||
date #inst "2024-04-14T00:00:00-07:00"]
|
||||
(get-payment-items c date)
|
||||
|
||||
)
|
||||
:date date}
|
||||
(alog/info ::updating)
|
||||
(let [manual-items (->> existing-summary
|
||||
:sales-summary/items
|
||||
(filter :sales-summary-item/manual?))
|
||||
calculated-items (->>
|
||||
(get-sales c date)
|
||||
(concat (get-payment-items c date))
|
||||
(concat (get-refund-items c date))
|
||||
(cons (get-discounts c date))
|
||||
(cons (get-fees c date))
|
||||
(cons (get-tax c date))
|
||||
(cons (get-tip c date))
|
||||
(cons (get-returns c date))
|
||||
(filter identity)
|
||||
(map (fn [z]
|
||||
(assoc z :ledger-mapped/account (some-> z :sales-summary-item/category str/lower-case name->number lookup-account)
|
||||
:sales-summary-item/manual? false))))
|
||||
all-items (concat calculated-items manual-items)
|
||||
aggregates (calc-aggregate-totals all-items)
|
||||
result (into {:db/id id
|
||||
:sales-summary/client c
|
||||
:sales-summary/date date
|
||||
:sales-summary/dirty false
|
||||
:sales-summary/client+date [c date]
|
||||
:sales-summary/items all-items}
|
||||
aggregates)]
|
||||
(if (seq (:sales-summary/items result))
|
||||
(do
|
||||
(alog/info ::upserting-summaries
|
||||
:category-count (count (:sales-summary/items result)))
|
||||
@(dc/transact conn [[:upsert-entity result]]))
|
||||
@(dc/transact conn [{:db/id id :sales-summary/dirty false}]))))))
|
||||
|
||||
|
||||
(defn reset-summaries []
|
||||
|
||||
@@ -35,27 +35,42 @@
|
||||
|
||||
|
||||
invoices-missing-ledger-entries (->> (dc/q {:find ['?t ]
|
||||
:in ['$ '?sd]
|
||||
:where ['[?t :invoice/date ?d]
|
||||
'[(>= ?d ?sd)]
|
||||
'(not [_ :journal-entry/original-entity ?t])
|
||||
'[?t :invoice/total ?amt]
|
||||
'[(not= 0.0 ?amt)]
|
||||
'(not [?t :invoice/status :invoice-status/voided])
|
||||
'(not [?t :invoice/import-status :import-status/pending])
|
||||
'(not [?t :invoice/exclude-from-ledger true])
|
||||
]}
|
||||
(dc/db conn) start-date)
|
||||
:in ['$ '?sd]
|
||||
:where ['[?t :invoice/date ?d]
|
||||
'[(>= ?d ?sd)]
|
||||
'(not [_ :journal-entry/original-entity ?t])
|
||||
'[?t :invoice/total ?amt]
|
||||
'[(not= 0.0 ?amt)]
|
||||
'(not [?t :invoice/status :invoice-status/voided])
|
||||
'(not [?t :invoice/import-status :import-status/pending])
|
||||
'(not [?t :invoice/exclude-from-ledger true])
|
||||
]}
|
||||
(dc/db conn) start-date)
|
||||
(map first)
|
||||
(mapv (fn [i]
|
||||
[:upsert-invoice {:db/id i}])))
|
||||
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-ledger {:db/id ss}])))
|
||||
|
||||
repairs (vec (concat txes-missing-ledger-entries invoices-missing-ledger-entries sales-summaries-missing-ledger-entries))]
|
||||
(when (seq repairs)
|
||||
(mu/log ::ledger-repairs-needed
|
||||
:sample (take 3 repairs)
|
||||
:transaction-count (count txes-missing-ledger-entries)
|
||||
:invoice-count (count invoices-missing-ledger-entries))
|
||||
@(dc/transact conn repairs)))))
|
||||
(mu/log ::ledger-repairs-needed
|
||||
:sample (take 3 repairs)
|
||||
:transaction-count (count txes-missing-ledger-entries)
|
||||
:invoice-count (count invoices-missing-ledger-entries)
|
||||
:sales-summary-count (count sales-summaries-missing-ledger-entries))
|
||||
@(dc/transact conn repairs)))))
|
||||
|
||||
|
||||
(defn touch-transaction [e]
|
||||
|
||||
@@ -72,14 +72,14 @@
|
||||
[:sales-summary/date :xform clj-time.coerce/from-date]
|
||||
{:sales-summary/client [:client/code :client/name :db/id]}
|
||||
{:sales-summary/items [{[:ledger-mapped/ledger-side :xform iol-ion.query/ident] [:db/ident]
|
||||
} ;; TODO clientize
|
||||
:ledger-mapped/account
|
||||
:ledger-mapped/amount
|
||||
:sales-summary-item/category
|
||||
:sales-summary-item/sort-order
|
||||
:db/id
|
||||
:sales-summary-item/manual?]
|
||||
} ]) ;; TODO
|
||||
}
|
||||
:ledger-mapped/account
|
||||
:ledger-mapped/amount
|
||||
:sales-summary-item/category
|
||||
:sales-summary-item/sort-order
|
||||
:db/id
|
||||
:sales-summary-item/manual?]
|
||||
} ])
|
||||
|
||||
(defn fetch-ids [db request]
|
||||
(let [query-params (:query-params request)
|
||||
@@ -179,10 +179,8 @@
|
||||
:db/id (:db/id entity))}
|
||||
svg/pencil)])
|
||||
:oob-render
|
||||
(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
|
||||
(fn [_request]
|
||||
[])
|
||||
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:admin)}
|
||||
"Admin"]
|
||||
@@ -241,13 +239,9 @@
|
||||
:primary
|
||||
:red)} "Total: " (format "$%,.2f" total-credits))]]))}]}))
|
||||
|
||||
;; TODO schema cleanup
|
||||
;; Decide on what should be calculated as generating ledger entries, and what should be calculated
|
||||
;; as part of the summary
|
||||
;; default thought here is that the summary has more detail (e.g., line items), fees broken out by type
|
||||
;; and aggregated into the final ledger entry
|
||||
;; that allows customization at any level.
|
||||
;; TODO rename refunds/returns
|
||||
;; Architecture: Sales summary maintains granular detail (line items, fee types)
|
||||
;; and is aggregated into ledger entries by account/location. Manual adjustments
|
||||
;; are preserved during automatic recalculation.
|
||||
|
||||
(def row* (partial helper/row* grid-page))
|
||||
(def table* (partial helper/table* grid-page))
|
||||
@@ -436,15 +430,14 @@
|
||||
(com/data-grid-header {} "")]}
|
||||
(fc/with-field :sales-summary/items
|
||||
(list
|
||||
(fc/cursor-map #(sales-summary-item-row* {:value %
|
||||
:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state))) }))
|
||||
;; TODO
|
||||
(com/data-grid-new-row {:colspan 5
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item)
|
||||
:row-offset 0
|
||||
:index (count (fc/field-value))
|
||||
:tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}} ;; TODO
|
||||
"New Summary Item")))
|
||||
(fc/cursor-map #(sales-summary-item-row* {:value %
|
||||
:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state))) }))
|
||||
(com/data-grid-new-row {:colspan 5
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item)
|
||||
:row-offset 0
|
||||
:index (count (fc/field-value))
|
||||
:tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}}
|
||||
"New Summary Item")))
|
||||
(summary-total-row* request)
|
||||
(unbalanced-row* request)) ])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user