Makes simple sales summaries

This commit is contained in:
2024-03-31 21:47:40 -07:00
parent d1a660c5c1
commit ded5371e77
3 changed files with 314 additions and 90 deletions

View File

@@ -1902,6 +1902,18 @@
:db/noHistory true,
:db/valueType :db.type/double
:db/cardinality :db.cardinality/one}
{:db/ident :sales-summary/total-tax
:db/noHistory true,
:db/valueType :db.type/double
:db/cardinality :db.cardinality/one}
{:db/ident :sales-summary/total-returns
:db/noHistory true,
:db/valueType :db.type/double
:db/cardinality :db.cardinality/one}
{:db/ident :sales-summary/total-tip
:db/noHistory true,
:db/valueType :db.type/double
:db/cardinality :db.cardinality/one}
{:db/ident :sales-summary/total-card-fees
:db/noHistory true,
:db/valueType :db.type/double
@@ -1922,9 +1934,9 @@
:db/noHistory true,
:db/valueType :db.type/double
:db/cardinality :db.cardinality/one}
{:db/ident :sales-summary/total-food-app-refunds
{:db/ident :sales-summary/total-food-app-refunds
:db/noHistory true,
:db/valueType :db.type/double
:db/cardinality :db.cardinality/one} ]
:db/cardinality :db.cardinality/one}]

View File

@@ -25,22 +25,20 @@
(.toDateMidnight (atime/localize (time/now)))])
(defn mark-all-dirty [days]
(doseq [c (dc/q '[:find ?c
:in $
:where [_ :sales-order/client ?c]]
(dc/db conn))]
(apply mark-dirty [:client/code "NGOP"] (last-n-days days))))
(doseq [[c] (dc/q '[:find ?c
:in $
:where [_ :sales-order/client ?c]]
(dc/db conn))]
(apply mark-dirty c (last-n-days days))))
(comment
(mark-all-dirty 365))
(str (c/to-local-date (atime/localize (time/now))))
(defn dirty-sales-summaries [c]
(let [client-id (dc/entid (dc/db conn) c)]
(->> (dc/index-pull (dc/db conn)
{:index :avet
:selector '[:sales-summary/date :sales-summary/client]
:selector '[:sales-summary/date :sales-summary/client :db/id]
:start [:sales-summary/client+dirty [client-id true]]})
(filter (fn [sales-summary]
(= client-id (:db/id (:sales-summary/client sales-summary))))))))
@@ -48,9 +46,10 @@
(defn sales-summaries []
(doseq [[c client-code] (dc/q '[:find ?c ?client-code
:in $
:where [?c :client/code ?client-code]]
:where [?c :client/code ?client-code]
[(= ?client-code "NGCL")]]
(dc/db conn))
{:sales-summary/keys [date]} (dirty-sales-summaries c)]
{:sales-summary/keys [date] :db/keys [id]} (dirty-sales-summaries c)]
(mu/with-context {:client-code client-code
:date date}
(alog/info ::updating)
@@ -63,25 +62,157 @@
[?li :order-line-item/category ?category]
[?li :order-line-item/total ?total]
[?li :order-line-item/tax ?tax]
[?li :order-line-item/discount ?discount] ]
[?li :order-line-item/discount ?discount]]
(dc/db conn)
[[c] date date]))
result {:sales-summary/client c
:sales-summary/date (c/to-date (atime/parse date atime/normal-date))
result {:db/id id
:sales-summary/client c
:sales-summary/date date
:sales-summary/dirty false
:sales-summary/client+date [c (c/to-date (atime/parse date atime/normal-date))]
:sales-summary/client+date [c date]
:sales-summary/discount (or (ffirst (dc/q '[:find (sum ?discount)
:with ?e
:in $ [?clients ?start-date ?end-date]
:where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
[?e :sales-order/discount ?discount]]
(dc/db conn)
[[c] date date]))
0.0)
:sales-summary/total-returns (or (let [[t f] (first (dc/q '[:find (sum ?t) (sum ?f)
:with ?e
:in $ [?clients ?start-date ?end-date]
:where [(iol-ion.query/scan-sales-refunds $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
[?e :sales-refund/total ?t]
[?e :sales-refund/fee ?f]]
(dc/db conn)
[[c] date date]))]
(when (and t f)
(- t f)))
0.0)
:sales-summary/sales-items
(for [[item-name category total tax discount] sales]
{:sales-summary-item/item-name item-name
{:db/id (str (java.util.UUID/randomUUID))
:sales-summary-item/item-name item-name
:sales-summary-item/category category
:sales-summary-item/total total
:sales-summary-item/tax tax
:sales-summary-item/discount discount})}]
:sales-summary-item/discount discount})
:sales-summary/total-tax
(or (ffirst (dc/q '[:find (sum ?tax)
:with ?e
:in $ [?clients ?start-date ?end-date]
:where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
[?e :sales-order/tax ?tax]
#_[?e :sales-order/charges ?c]
#_[?c :charge/tax ?tax]]
(dc/db conn)
[[c] date date]))
0.0)
:sales-summary/total-tip
(or (ffirst (dc/q '[:find (sum ?tip)
:with ?c
:in $ [?clients ?start-date ?end-date]
:where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
[?e :sales-order/charges ?c]
[?c :charge/tip ?tip]]
(dc/db conn)
[[c] date date]))
0.0)
:sales-summary/total-card-payments
(or (ffirst (dc/q '[:find (sum ?total)
:with ?c
:in $ [?clients ?start-date ?end-date]
:where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
[?e :sales-order/charges ?c]
[?c :charge/type-name "CARD"]
[?c :charge/total ?total]]
(dc/db conn)
[[c] date date]))
0.0)
:sales-summary/total-card-fees
(or (ffirst (dc/q '[:find ?f
:in $ ?client ?d
:where
[?e :expected-deposit/client ?client]
[?e :expected-deposit/sales-date ?d]
[?e :expected-deposit/fee ?f]]
(dc/db conn)
c
date))
0.0)
:sales-summary/total-card-refunds
(or (ffirst (dc/q '[:find (sum ?t)
:in $ [?clients ?start-date ?end-date]
:where
:where [(iol-ion.query/scan-sales-refunds $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
[?e :sales-refund/type "CARD"]
[?e :sales-refund/total ?t]]
(dc/db conn)
[[c] date date]))
0.0)
:sales-summary/total-cash-payments
(or (ffirst (dc/q '[:find (sum ?total)
:with ?c
:in $ [?clients ?start-date ?end-date]
:where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
[?e :sales-order/charges ?c]
[?c :charge/total ?total]
[?c :charge/type-name "CASH"]]
(dc/db conn)
[[c] date date]))
0.0)
:sales-summary/total-cash-refunds
(or (ffirst (dc/q '[:find (sum ?t)
:in $ [?clients ?start-date ?end-date]
:where
:where [(iol-ion.query/scan-sales-refunds $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
[?e :sales-refund/type "CASH"]
[?e :sales-refund/total ?t]]
(dc/db conn)
[[c] date date]))
0.0)
:sales-summary/total-food-app-payments
(or (ffirst (dc/q '[:find (sum ?total)
:with ?c
:in $ [?clients ?start-date ?end-date] [?processor ...]
:where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
[?e :sales-order/charges ?c]
[?c :charge/processor ?processor]
[?c :charge/total ?total]]
(dc/db conn)
[[c] date date]
#{:ccp-processor/toast
#_:ccp-processor/ezcater
#_:ccp-processor/koala
:ccp-processor/doordash
:ccp-processor/grubhub
:ccp-processor/uber-eats}))
0.0)
:sales-summary/total-food-app-refunds
(or (ffirst (dc/q '[:find (sum ?t)
:in $ [?clients ?start-date ?end-date]
:where
:where [(iol-ion.query/scan-sales-refunds $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
(not [?e :sales-refund/type "CASH"])
(not [?e :sales-refund/type "CARD"])
[?e :sales-refund/total ?t]]
(dc/db conn)
[[c] date date]))
0.0)}]
(when (seq (:sales-summary/sales-items result))
(alog/info ::upserting-summaries
:category-count (count (:sales-summary/sales-items result)))
@(dc/transact conn [result]))))))
@(dc/transact conn [ [:upsert-entity result]]))))))
(defn reset-summaries []
@(dc/transact conn (->> (dc/q '[:find ?sos
@@ -91,6 +222,36 @@
(map (fn [[sos]]
[:db/retractEntity sos])))))
(comment
(auto-ap.datomic/transact-schema conn)
(apply mark-dirty [:client/code "NGCL"] (last-n-days 12))
#_(mark-all-dirty 700)
(sales-summaries)
(dc/q '[:find (pull ?sos [* {:sales-summary/sales-items [*]}])
:in $
:where [?sos :sales-summary/client [:client/code "NGCL"]]
[?sos :sales-summary/date ?d]
[(= ?d #inst "2024-03-25T00:00:00-07:00")]]
(dc/db conn))
)
(defn -main [& _]
(execute "sales-summaries" sales-summaries))

View File

@@ -1,52 +1,50 @@
(ns auto-ap.ssr.admin.sales-summaries
(:require [auto-ap.client-routes :as client-routes]
[auto-ap.datomic
:refer [apply-pagination apply-sort-3 conn merge-query pull-many query2]]
(:require [auto-ap.datomic
:refer [apply-pagination apply-sort-3 conn merge-query pull-many
query2]]
[auto-ap.graphql.utils :refer [extract-client-ids]]
[auto-ap.routes.admin.sales-summaries :as route]
[auto-ap.routes.utils
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components :as com]
[auto-ap.ssr.components.date-range :refer [date-range-field]]
[auto-ap.ssr.grid-page-helper :as helper]
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils
:refer [apply-middleware-to-all-handlers]]
:refer [apply-middleware-to-all-handlers]]
[auto-ap.time :as atime]
[bidi.bidi :as bidi]
[clj-time.coerce :as c]
[datomic.api :as dc]
[hiccup.util :as hu]))
[iol-ion.query :refer [dollars=]]))
(defn filters [request]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
"hx-get" (bidi/path-for ssr-routes/only-routes
::route/table)
::route/table)
"hx-target" "#entity-table"
"hx-indicator" "#entity-table"}
#_[:fieldset.space-y-6
(date-range-field {:value {:start (:start-date (:parsed-query-params request))
:end (:end-date (:parsed-query-params request))}
:id "date-range"})
(com/field {:label "Source"}
(com/select {:name "source"
:class "hot-filter w-full"
:value (:source (:parsed-query-params request))
:placeholder ""
:options (ref->select-options "import-source" :allow-nil? true)}))
#_[:fieldset.space-y-6
(date-range-field {:value {:start (:start-date (:parsed-query-params request))
:end (:end-date (:parsed-query-params request))}
:id "date-range"})
(com/field {:label "Source"}
(com/select {:name "source"
:class "hot-filter w-full"
:value (:source (:parsed-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 (:parsed-query-params request))
:placeholder "11101"
:size :small}))]])
#_(com/field {:label "Code"}
(com/text-input {:name "code"
:id "code"
:class "hot-filter"
:value (:code (:parsed-query-params request))
:placeholder "11101"
:size :small}))]])
(def default-read '[:db/id
[ :sales-summary/date :xform clj-time.coerce/from-date]
[:sales-summary/date :xform clj-time.coerce/from-date]
*]) ;; TODO
(defn fetch-ids [db request]
@@ -58,7 +56,7 @@
[:client/code (:client-code query-params)]))
query (cond-> {:query {:find []
:in '[$ [?client ...]]
:where '[ [?e :sales-summary/client ?client]]}
:where '[[?e :sales-summary/client ?client]]}
:args [db valid-clients]}
(or (:start-date query-params)
(:end-date query-params))
@@ -74,7 +72,7 @@
:where [[(< ?d ?end-date)]]}
:args [(-> query-params :end-date c/to-date)]})
true
(merge-query {:query {:find ['?sort-default '?e]
:where ['[?e :sales-summary/date ?sort-default]]}}))]
@@ -97,6 +95,19 @@
[(->> (hydrate-results ids-to-retrieve db request))
matching-count]))
(defn get-credits [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)))
: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)))
:discounts (+ (:sales-summary/discount ss 0.0))
:returns (+ (:sales-summary/total-returns ss 0.0))})
(def grid-page
(helper/build {:id "entity-table"
:id-fn :db/id
@@ -108,10 +119,10 @@
:oob-render
(fn [request]
[#_(assoc-in (date-range-field {:value {:start (:start-date (:parsed-query-params request))
:end (:end-date (:parsed-query-params request))}
:id "date-range"}) [1 :hx-swap-oob] true)]) ;; TODO
:end (:end-date (:parsed-query-params request))}
:id "date-range"}) [1 :hx-swap-oob] true)]) ;; TODO
:parse-query-params (comp
(helper/default-parse-query-params grid-page))
(helper/default-parse-query-params grid-page))
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
:admin)}
"Admin"]
@@ -126,51 +137,91 @@
:name "Date"
:sort-key "date"
:render #(some-> % :sales-summary/date (atime/unparse-local atime/normal-date))}
{:key "items"
:name "items"
:sort-key "source"
{:key "credits"
:name "Credits"
:sort-key "credits"
:render (fn [ss]
[:ul
(for [[ n x] (group-by :sales-summary-item/category (:sales-summary/sales-items ss))]
[:li n ": " (format "$%,.2f" (reduce + 0.0 (map :sales-summary-item/total x)))])]
#_(count ))}
{:key "payments"
:name "payments"
:sort-key "payments"
(let [total-credits (reduce + 0.0 (vals (get-credits ss)))
total-debits (+ (- (+ (reduce + 0.0 (map :sales-summary-item/total (:sales-summary/sales-items ss)))
(reduce + 0.0 (map :sales-summary-item/discount (:sales-summary/sales-items ss))))
(reduce + 0.0 (map :sales-summary-item/tax (:sales-summary/sales-items ss))))
(:sales-summary/total-tax ss 0.0)
(:sales-summary/total-tip ss 0.0))]
[:ul
(for [[n x] (group-by :sales-summary-item/category (:sales-summary/sales-items ss))]
[:li n ": " (format "$%,.2f" (- (+ (reduce + 0.0 (map :sales-summary-item/total x))
(reduce + 0.0 (map :sales-summary-item/discount x)))
(reduce + 0.0 (map :sales-summary-item/tax x))))])
[:li "Sales subtotal: " (- (+ (reduce + 0.0 (map :sales-summary-item/total (:sales-summary/sales-items ss)))
(reduce + 0.0 (map :sales-summary-item/discount (:sales-summary/sales-items ss))))
(reduce + 0.0 (map :sales-summary-item/tax (:sales-summary/sales-items ss))))]
[:li "Tax: " (format "$%,.2f" (:sales-summary/total-tax ss))]
[:li "Tips: " (format "$%,.2f" (:sales-summary/total-tip ss))]
[:li (com/pill {:color (if (dollars= total-credits total-debits)
:primary
:red)} "Total: " (format "$%,.2f" total-debits))]])
#_(count))}
{:key "debits"
:name "debits"
:sort-key "debits"
:render (fn [ss]
[:ul
[:li "Card Payments: "
(format "$%,.2f" (:sales-summary/total-card-payments ss 0.0))
(let [{:keys [card-payments food-app-payments
cash-payments discounts fees
returns] :as credits} (get-credits ss)
total-credits (reduce + 0.0 (vals credits))
total-debits (+ (- (+ (reduce + 0.0 (map :sales-summary-item/total (:sales-summary/sales-items ss)))
", fees: "
(format "$%,.2f" (:sales-summary/total-card-fees ss 0.0))
(reduce + 0.0 (map :sales-summary-item/discount (:sales-summary/sales-items ss))))
(reduce + 0.0 (map :sales-summary-item/tax (:sales-summary/sales-items ss))))
", refunds: "
(format "$%,.2f" (:sales-summary/total-card-refunds ss 0.0))]
[:li "Cash Payments: "
(format "$%,.2f" (:sales-summary/total-cash-payments ss 0.0))
(:sales-summary/total-tax ss 0.0)
(:sales-summary/total-tip ss 0.0))]
[:ul
[:li "Card Payments: "
(format "$%,.2f" card-payments)]
", refunds: "
(format "$%,.2f" (:sales-summary/total-cash-refunds ss 0.0))]
[:li "Food App Payments: "
(format "$%,.2f" (:sales-summary/total-food-app-payments ss 0.0))
[:li "Food App Payments: "
(format "$%,.2f" food-app-payments)]
[:li "Cash Payments: "
", refunds: "
(format "$%,.2f" (:sales-summary/total-food-app-refunds ss 0.0)) ]]
#_(count ))}]}))
(format "$%,.2f" cash-payments)]
[:li "Discounts: "
(format "$%,.2f" discounts)]
[:li "Fees: "
(format "$%,.2f" fees)]
[:li "Returns: "
(format "$%,.2f" returns)]
[:li (com/pill {:color (if (dollars= total-credits total-debits)
:primary
:red)} "Total: " (format "$%,.2f" total-credits))]])
#_(count))}]}))
;; TODO schema cleanup
;; Decide on what should be calculated as generating ledger entries, and what should be calculated
;; as part of the summary
;; default thought here is that the summary has more detail (e.g., line items), fees broken out by type
;; and aggregated into the final ledger entry
;; that allows customization at any level.
;; TODO rename refunds/returns
(def row* (partial helper/row* grid-page))
(def table* (partial helper/table* grid-page))
(def key->handler
(apply-middleware-to-all-handlers
(->>
{::route/page (helper/page-route grid-page)
::route/table (helper/table-route grid-page)})
(fn [h]
(-> h
(wrap-admin)
(wrap-client-redirect-unauthenticated)))))
(apply-middleware-to-all-handlers
(->>
{::route/page (helper/page-route grid-page)
::route/table (helper/table-route grid-page)})
(fn [h]
(-> h
(wrap-admin)
(wrap-client-redirect-unauthenticated)))))