Merge branch 'master' into ssr-invoices
This commit is contained in:
@@ -1847,6 +1847,97 @@
|
||||
:db/valueType :db.type/tuple
|
||||
:db/tupleAttrs [:expected-deposit/client :expected-deposit/date]
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true}]
|
||||
:db/index true}
|
||||
|
||||
{:db/ident :sales-summary/client
|
||||
:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true}
|
||||
{:db/ident :sales-summary/date
|
||||
:db/valueType :db.type/instant
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true}
|
||||
{:db/ident :sales-summary/sales-items
|
||||
:db/valueType :db.type/ref
|
||||
:db/isComponent true,
|
||||
:db/cardinality :db.cardinality/many}
|
||||
{:db/ident :sales-summary-item/category
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary-item/item-name
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary-item/total
|
||||
:db/noHistory true,
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary-item/tax
|
||||
:db/noHistory true,
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary-item/discount
|
||||
:db/noHistory true,
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/dirty
|
||||
:db/noHistory true,
|
||||
:db/valueType :db.type/boolean
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/client+date
|
||||
:db/valueType :db.type/tuple
|
||||
:db/tupleAttrs [:sales-summary/client :sales-summary/date]
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/unique :db.unique/identity
|
||||
:db/index true}
|
||||
{:db/ident :sales-summary/client+dirty
|
||||
:db/valueType :db.type/tuple
|
||||
:db/tupleAttrs [:sales-summary/client :sales-summary/dirty]
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true}
|
||||
|
||||
{:db/ident :sales-summary/discount
|
||||
:db/noHistory true,
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-card-payments
|
||||
: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
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-card-refunds
|
||||
:db/noHistory true,
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-cash-payments
|
||||
:db/noHistory true,
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-cash-refunds
|
||||
:db/noHistory true,
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-food-app-payments
|
||||
:db/noHistory true,
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :sales-summary/total-food-app-refunds
|
||||
:db/noHistory true,
|
||||
:db/valueType :db.type/double
|
||||
:db/cardinality :db.cardinality/one}]
|
||||
|
||||
|
||||
|
||||
256
src/clj/auto_ap/jobs/sales_summaries.clj
Normal file
256
src/clj/auto_ap/jobs/sales_summaries.clj
Normal file
@@ -0,0 +1,256 @@
|
||||
(ns auto-ap.jobs.sales-summaries
|
||||
(:require [auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.jobs.core :refer [execute]]
|
||||
[auto-ap.logging :as alog]
|
||||
[auto-ap.time :as atime]
|
||||
[clj-time.coerce :as c]
|
||||
[clj-time.core :as time]
|
||||
[clj-time.periodic :as per]
|
||||
[com.brunobonacci.mulog :as mu]
|
||||
[datomic.api :as dc]))
|
||||
|
||||
(defn mark-dirty [client start end]
|
||||
(let [client (dc/entid (dc/db conn) client)]
|
||||
@(dc/transact conn
|
||||
(for [s (per/periodic-seq start
|
||||
end
|
||||
(time/days 1))]
|
||||
{:sales-summary/client client
|
||||
:sales-summary/date (c/to-date s)
|
||||
:sales-summary/dirty true
|
||||
:sales-summary/client+date [client (c/to-date s)]}))))
|
||||
|
||||
(defn last-n-days [n]
|
||||
[(.toDateMidnight (atime/localize (time/plus (time/now) (time/days (- n)))))
|
||||
(.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 c (last-n-days days))))
|
||||
|
||||
|
||||
|
||||
|
||||
(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 :db/id]
|
||||
:start [:sales-summary/client+dirty [client-id true]]})
|
||||
(filter (fn [sales-summary]
|
||||
(= client-id (:db/id (:sales-summary/client sales-summary))))))))
|
||||
|
||||
(defn sales-summaries []
|
||||
(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)]
|
||||
(mu/with-context {:client-code client-code
|
||||
:date date}
|
||||
(alog/info ::updating)
|
||||
(let [sales (->> (dc/q '[:find ?item-name ?category (sum ?total) (sum ?tax) (sum ?discount)
|
||||
:with ?e ?li
|
||||
:in $ [?clients ?start-date ?end-date]
|
||||
:where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
|
||||
[?e :sales-order/line-items ?li]
|
||||
[?li :order-line-item/item-name ?item-name]
|
||||
[?li :order-line-item/category ?category]
|
||||
[?li :order-line-item/total ?total]
|
||||
[?li :order-line-item/tax ?tax]
|
||||
[?li :order-line-item/discount ?discount]]
|
||||
(dc/db conn)
|
||||
[[c] date date]))
|
||||
result {:db/id id
|
||||
:sales-summary/client c
|
||||
:sales-summary/date date
|
||||
:sales-summary/dirty false
|
||||
: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 (ffirst (dc/q '[:find (sum ?r)
|
||||
: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/returns ?r]
|
||||
|
||||
#_[?e :sales-order/charges ?c]
|
||||
#_[?c :charge/tax ?tax]]
|
||||
(dc/db conn)
|
||||
[[c] date date]))
|
||||
0.0)
|
||||
|
||||
:sales-summary/sales-items
|
||||
|
||||
|
||||
|
||||
(for [[item-name category total tax discount] sales]
|
||||
{: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/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 [ [:upsert-entity result]]))))))
|
||||
|
||||
(defn reset-summaries []
|
||||
@(dc/transact conn (->> (dc/q '[:find ?sos
|
||||
:in $
|
||||
:where [?sos :sales-summary/client]]
|
||||
(dc/db conn))
|
||||
(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 30)
|
||||
|
||||
(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))
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
{:vendor "Southern Glazers"
|
||||
:keywords [#"Southern Glazer's"]
|
||||
:extract {:date #"INVOICE DATE(?s:.*)(?= (?:[0-9]+/[0-9]+/[0-9]+)\s+([0-9]+/[0-9]+/[0-9]+)) "
|
||||
:invoice-number #"INVOICE\n(?:.*?)(?=\d{4,})(\d+)"
|
||||
:invoice-number #"(?s)INVOICE\n(?:.*?)(?=\d{4,})(\d+)"
|
||||
:total #"PAY THIS AMOUNT(?s:.*)(?= ([0-9,]+\.[0-9]{2}))"
|
||||
:account-number #"ACCOUNT #.*\n.*?[\n]?\s+(\d+)"}
|
||||
:parser {:date [:clj-time "MM/dd/yy"]
|
||||
@@ -317,6 +317,17 @@
|
||||
:parser {:date [:clj-time "MM/dd/yyyy"]
|
||||
:total [:trim-commas nil]}}
|
||||
|
||||
|
||||
;; Breakthru Bev
|
||||
{:vendor "Wine Warehouse"
|
||||
:keywords [#"BREAKTHRU BEVERAGE"]
|
||||
:extract {:date #"INVOICE DATE\s+([0-9]+/[0-9]+/[0-9]+)"
|
||||
:account-number #"CUSTOMER NUMBER\s+(\d+)"
|
||||
:invoice-number #"INV #\s+(.*?)\n"
|
||||
:total #"INVOICE AMOUNT\s+\$\s*([\d,\.\-]+)"}
|
||||
:parser {:date [:clj-time "MM/dd/yyyy"]
|
||||
:total [:trim-commas nil]}}
|
||||
|
||||
;; THE WATER PROS
|
||||
{:vendor "The Water Pros"
|
||||
:keywords [#"The Water Pros, Inc"]
|
||||
|
||||
@@ -31,49 +31,49 @@
|
||||
(f/unparse (f/formatter "YYYY-MM-dd'T'HH:mm:ssZZ") d))
|
||||
|
||||
|
||||
(def manifold-api-stream
|
||||
(def manifold-api-stream
|
||||
(let [stream (s/stream 100)]
|
||||
(->> stream
|
||||
(s/throttle 50)
|
||||
(s/map (fn [[request attempt response-deferred]]
|
||||
(de/catch
|
||||
(de/chain
|
||||
(de/loop [attempt 0]
|
||||
(-> (de/chain (de/future-with (ex/execute-pool)
|
||||
(log/info ::request-started
|
||||
:url (:url request)
|
||||
:attempt attempt
|
||||
:source "Square 3"
|
||||
:background-job "Square 3")
|
||||
(try
|
||||
(client/request (assoc request
|
||||
:socket-timeout 10000
|
||||
:connection-timeout 10000
|
||||
#_#_:connection-request-timeout 5000
|
||||
:as :json))
|
||||
(catch Throwable e
|
||||
(log/warn ::raw-request-failed
|
||||
:exception e)
|
||||
(throw e)))))
|
||||
(de/catch
|
||||
(fn [e]
|
||||
(if (>= attempt 5)
|
||||
(throw e)
|
||||
(de/chain
|
||||
(mt/in 10000 (fn [] 1))
|
||||
(fn [_] (de/recur (inc attempt)))))))
|
||||
(de/chain identity)))
|
||||
(fn [result]
|
||||
(de/success! response-deferred result)))
|
||||
(fn [error]
|
||||
(de/error! response-deferred error)))))
|
||||
(de/chain
|
||||
(de/loop [attempt 0]
|
||||
(-> (de/chain (de/future-with (ex/execute-pool)
|
||||
(log/info ::request-started
|
||||
:url (:url request)
|
||||
:attempt attempt
|
||||
:source "Square 3"
|
||||
:background-job "Square 3")
|
||||
(try
|
||||
(client/request (assoc request
|
||||
:socket-timeout 10000
|
||||
:connection-timeout 10000
|
||||
#_#_:connection-request-timeout 5000
|
||||
:as :json))
|
||||
(catch Throwable e
|
||||
(log/warn ::raw-request-failed
|
||||
:exception e)
|
||||
(throw e)))))
|
||||
(de/catch
|
||||
(fn [e]
|
||||
(if (>= attempt 5)
|
||||
(throw e)
|
||||
(de/chain
|
||||
(mt/in 10000 (fn [] 1))
|
||||
(fn [_] (de/recur (inc attempt)))))))
|
||||
(de/chain identity)))
|
||||
(fn [result]
|
||||
(de/success! response-deferred result)))
|
||||
(fn [error]
|
||||
(de/error! response-deferred error)))))
|
||||
|
||||
(s/buffer 50)
|
||||
(s/realize-each)
|
||||
(s/consume (fn [_]
|
||||
#_(log/info ::request-completed
|
||||
:source "Square 3"
|
||||
:background-job "Square 3")
|
||||
:source "Square 3"
|
||||
:background-job "Square 3")
|
||||
nil)))
|
||||
stream))
|
||||
|
||||
@@ -89,18 +89,18 @@
|
||||
|
||||
(defn client-locations [client]
|
||||
(capture-context->lc
|
||||
(de/catch
|
||||
(de/chain (manifold-api-call
|
||||
{:url "https://connect.squareup.com/v2/locations"
|
||||
:method :get
|
||||
:headers (client-base-headers client)})
|
||||
:body
|
||||
:locations)
|
||||
(fn [error]
|
||||
(mu/with-context lc
|
||||
(log/error ::no-locations-found
|
||||
:exception error))
|
||||
[]))))
|
||||
(de/catch
|
||||
(de/chain (manifold-api-call
|
||||
{:url "https://connect.squareup.com/v2/locations"
|
||||
:method :get
|
||||
:headers (client-base-headers client)})
|
||||
:body
|
||||
:locations)
|
||||
(fn [error]
|
||||
(mu/with-context lc
|
||||
(log/error ::no-locations-found
|
||||
:exception error))
|
||||
[]))))
|
||||
|
||||
|
||||
(def item-cache (atom {}))
|
||||
@@ -136,25 +136,25 @@
|
||||
(fn [item]
|
||||
(mu/with-context lc
|
||||
(item->category-name-impl client item version))))
|
||||
(fn [e]
|
||||
(log/warn ::couldnt-fetch-variation
|
||||
:exception e)
|
||||
"Uncategorized"))
|
||||
(fn [e]
|
||||
(log/warn ::couldnt-fetch-variation
|
||||
:exception e)
|
||||
"Uncategorized"))
|
||||
|
||||
(:category_id (:item_data item))
|
||||
(de/catch (de/chain (fetch-catalog-cache client (:category_id (:item_data item)) version)
|
||||
:category_data
|
||||
:name)
|
||||
(fn [e]
|
||||
(log/warn ::couldnt-fetch-category
|
||||
:exception e)
|
||||
"Uncategorized"))
|
||||
(fn [e]
|
||||
(log/warn ::couldnt-fetch-category
|
||||
:exception e)
|
||||
"Uncategorized"))
|
||||
|
||||
(:item_data item)
|
||||
"Uncategorized"
|
||||
|
||||
:else
|
||||
(do
|
||||
(do
|
||||
(log/warn ::no-look-up-item
|
||||
:item item)
|
||||
"Uncategorized"))))
|
||||
@@ -163,29 +163,25 @@
|
||||
(defn item-id->category-name [client i version]
|
||||
(capture-context->lc
|
||||
(-> [client i]
|
||||
(de/chain
|
||||
(de/chain
|
||||
(fn [[client i]]
|
||||
(if (str/blank? i)
|
||||
"Uncategorized"
|
||||
(de/catch (de/chain (fetch-catalog-cache client i version)
|
||||
#(mu/with-context lc
|
||||
(item->category-name-impl client % version)))
|
||||
(fn [error]
|
||||
(log/warn ::couldnt-fetch-item
|
||||
:exception error)
|
||||
"Uncategorized"
|
||||
(throw error)))))))))
|
||||
(fn [error]
|
||||
(log/warn ::couldnt-fetch-item
|
||||
:exception error)
|
||||
"Uncategorized"
|
||||
(throw error)))))))))
|
||||
|
||||
(defn pc [start end]
|
||||
{"query" {"filter" {"date_time_filter"
|
||||
{
|
||||
"created_at" {
|
||||
"start_at" (->square-date start)
|
||||
"end_at" (->square-date end)
|
||||
}}}
|
||||
{"created_at" {"start_at" (->square-date start)
|
||||
"end_at" (->square-date end)}}}
|
||||
|
||||
"sort" {
|
||||
"sort_field" "CREATED_AT"
|
||||
"sort" {"sort_field" "CREATED_AT"
|
||||
"sort_order" "DESC"}}})
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
@@ -195,18 +191,17 @@
|
||||
:location location
|
||||
:order-id order-id)
|
||||
(let [result (->> (client/get (str "https://connect.squareup.com/v2/orders/" order-id)
|
||||
{:headers (client-base-headers client)
|
||||
:as :json})
|
||||
:body
|
||||
)]
|
||||
{:headers (client-base-headers client)
|
||||
:as :json})
|
||||
:body)]
|
||||
result)))
|
||||
|
||||
(defn continue-search [client location start end cursor]
|
||||
(log/info ::continue-order-search
|
||||
:cursor cursor)
|
||||
|
||||
|
||||
(capture-context->lc
|
||||
(de/chain (manifold-api-call
|
||||
(de/chain (manifold-api-call
|
||||
{:url "https://connect.squareup.com/v2/orders/search"
|
||||
:method :post
|
||||
:headers (client-base-headers client)
|
||||
@@ -218,23 +213,23 @@
|
||||
:body
|
||||
(fn [result]
|
||||
(mu/with-context
|
||||
lc
|
||||
lc
|
||||
(log/info ::orders-found
|
||||
:count (count (:orders result)))
|
||||
(if (not-empty (:cursor result))
|
||||
(de/chain (continue-search client location start end (:cursor result))
|
||||
(fn [continued-results]
|
||||
(mu/with-context
|
||||
lc
|
||||
lc
|
||||
(concat (:orders result) continued-results))))
|
||||
(:orders result)))))))
|
||||
|
||||
|
||||
(defn search
|
||||
([client location start end]
|
||||
(capture-context->lc
|
||||
(capture-context->lc
|
||||
(log/info ::searching
|
||||
:location (:square-location/client-location location))
|
||||
:location (:square-location/client-location location))
|
||||
(de/chain (manifold-api-call {:url "https://connect.squareup.com/v2/orders/search"
|
||||
:method :post
|
||||
:headers (client-base-headers client)
|
||||
@@ -268,20 +263,19 @@
|
||||
(:sales-order/service-charge i))
|
||||
(:sales-order/returns i)
|
||||
|
||||
(:sales-order/discount i)
|
||||
)))
|
||||
(:sales-order/discount i))))
|
||||
0.0
|
||||
[]))
|
||||
|
||||
(defn tender->charge [order client location t]
|
||||
(remove-nils
|
||||
#:charge
|
||||
(remove-nils
|
||||
#:charge
|
||||
{:type-name (:type t)
|
||||
:date (coerce/to-date (time/to-time-zone (coerce/to-date-time (:created_at order)) (time/time-zone-for-id "America/Los_Angeles")))
|
||||
:client (:db/id client)
|
||||
:note (:note t)
|
||||
:location (:square-location/client-location location)
|
||||
:reference-link (str (url/url "https://squareup.com/receipt/preview" (:id t) ))
|
||||
:reference-link (str (url/url "https://squareup.com/receipt/preview" (:id t)))
|
||||
:external-id (when (:id t)
|
||||
(str "square/charge/" (:id t)))
|
||||
:processor (condp = (:type t)
|
||||
@@ -317,98 +311,97 @@
|
||||
(defn order->sales-order [client location order]
|
||||
(capture-context->lc
|
||||
(let [is-order-only-for-charge? (= ["CUSTOM_AMOUNT"]
|
||||
(mapv :item_type (:line_items order )))]
|
||||
(mapv :item_type (:line_items order)))]
|
||||
(if is-order-only-for-charge?
|
||||
(de/success-deferred
|
||||
(de/success-deferred
|
||||
(->> (:tenders order)
|
||||
(map #(tender->charge order client location %))))
|
||||
(de/catch
|
||||
(de/let-flow [line-items
|
||||
(->>
|
||||
(or (:line_items order) [])
|
||||
(s/->source)
|
||||
(s/transform
|
||||
(map-indexed (fn [i li]
|
||||
(mu/with-context lc
|
||||
(->
|
||||
(de/let-flow [category (item-id->category-name client (:catalog_object_id li) (:catalog_version li))]
|
||||
(remove-nils
|
||||
#:order-line-item
|
||||
{:external-id (str "square/order/" (:client/code client) "-" (:square-location/client-location location) "-" (:id order) "-" i)
|
||||
:item-name (:name li)
|
||||
:category (if (= "GIFT_CARD" (:item_type li))
|
||||
"Gift Card"
|
||||
category)
|
||||
:total (amount->money (:total_money li))
|
||||
:tax (amount->money (:total_tax_money li))
|
||||
:discount (amount->money (:total_discount_money li))}))
|
||||
(de/catch (fn [e]
|
||||
(log/error ::cant-transform
|
||||
:exception e
|
||||
:line-item li)))))))
|
||||
)
|
||||
(s/buffer 5)
|
||||
(s/realize-each)
|
||||
(s/reduce conj []))]
|
||||
[(remove-nils
|
||||
#:sales-order
|
||||
{:date (coerce/to-date (time/to-time-zone (coerce/to-date-time (:created_at order)) (time/time-zone-for-id "America/Los_Angeles")))
|
||||
:client (:db/id client)
|
||||
:location (:square-location/client-location location)
|
||||
:external-id (str "square/order/" (:client/code client) "-" (:square-location/client-location location) "-" (:id order))
|
||||
:source (or (:name (:source order))
|
||||
"Square")
|
||||
:vendor :vendor/ccp-square
|
||||
(de/catch
|
||||
(de/let-flow [line-items
|
||||
(->>
|
||||
(or (:line_items order) [])
|
||||
(s/->source)
|
||||
(s/transform
|
||||
(map-indexed (fn [i li]
|
||||
(mu/with-context lc
|
||||
(->
|
||||
(de/let-flow [category (item-id->category-name client (:catalog_object_id li) (:catalog_version li))]
|
||||
(remove-nils
|
||||
#:order-line-item
|
||||
{:external-id (str "square/order/" (:client/code client) "-" (:square-location/client-location location) "-" (:id order) "-" i)
|
||||
:item-name (:name li)
|
||||
:category (if (= "GIFT_CARD" (:item_type li))
|
||||
"Gift Card"
|
||||
category)
|
||||
:total (amount->money (:total_money li))
|
||||
:tax (amount->money (:total_tax_money li))
|
||||
:discount (amount->money (:total_discount_money li))}))
|
||||
(de/catch (fn [e]
|
||||
(log/error ::cant-transform
|
||||
:exception e
|
||||
:line-item li))))))))
|
||||
(s/buffer 5)
|
||||
(s/realize-each)
|
||||
(s/reduce conj []))]
|
||||
[(remove-nils
|
||||
#:sales-order
|
||||
{:date (coerce/to-date (time/to-time-zone (coerce/to-date-time (:created_at order)) (time/time-zone-for-id "America/Los_Angeles")))
|
||||
:client (:db/id client)
|
||||
:location (:square-location/client-location location)
|
||||
:external-id (str "square/order/" (:client/code client) "-" (:square-location/client-location location) "-" (:id order))
|
||||
:source (or (:name (:source order))
|
||||
"Square")
|
||||
:vendor :vendor/ccp-square
|
||||
|
||||
:reference-link (str (url/url "https://squareup.com/dashboard/sales/transactions" (:id order) "by-unit" (:square-location/square-id location)))
|
||||
:total (-> order :net_amounts :total_money amount->money)
|
||||
:tax (-> order :net_amounts :tax_money amount->money)
|
||||
:tip (-> order :net_amounts :tip_money amount->money)
|
||||
:discount (-> order :net_amounts :discount_money amount->money)
|
||||
:service-charge (-> order :net_amounts :service_charge_money amount->money)
|
||||
:returns (+ (- (-> order :return_amounts :total_money amount->money)
|
||||
(-> order :return_amounts :tax_money amount->money)
|
||||
(-> order :return_amounts :tip_money amount->money)
|
||||
(-> order :return_amounts :service_charge_money amount->money))
|
||||
(-> order :return_amounts :discount_money amount->money))
|
||||
:charges (->> (:tenders order)
|
||||
(map #(tender->charge order client location %)))
|
||||
:line-items line-items})])
|
||||
(fn [e]
|
||||
(log/error ::failed-to-transform-order
|
||||
:exception e)))))))
|
||||
:reference-link (str (url/url "https://squareup.com/dashboard/sales/transactions" (:id order) "by-unit" (:square-location/square-id location)))
|
||||
:total (-> order :net_amounts :total_money amount->money)
|
||||
:tax (-> order :net_amounts :tax_money amount->money)
|
||||
:tip (-> order :net_amounts :tip_money amount->money)
|
||||
:discount (-> order :net_amounts :discount_money amount->money)
|
||||
:service-charge (-> order :net_amounts :service_charge_money amount->money)
|
||||
:returns (+ (- (-> order :return_amounts :total_money amount->money)
|
||||
(-> order :return_amounts :tax_money amount->money)
|
||||
(-> order :return_amounts :tip_money amount->money)
|
||||
(-> order :return_amounts :service_charge_money amount->money))
|
||||
(-> order :return_amounts :discount_money amount->money))
|
||||
:charges (->> (:tenders order)
|
||||
(map #(tender->charge order client location %)))
|
||||
:line-items line-items})])
|
||||
(fn [e]
|
||||
(log/error ::failed-to-transform-order
|
||||
:exception e)))))))
|
||||
|
||||
(defn daily-results
|
||||
([client location]
|
||||
(daily-results client location (time/plus (time/now) (time/days -7)) (time/now)))
|
||||
([client location start end]
|
||||
(capture-context->lc
|
||||
(->
|
||||
(de/chain (search client location start end)
|
||||
(fn [search-results]
|
||||
(->> (or search-results [])
|
||||
(s/->source)
|
||||
(s/filter (fn [order]
|
||||
(->
|
||||
(de/chain (search client location start end)
|
||||
(fn [search-results]
|
||||
(->> (or search-results [])
|
||||
(s/->source)
|
||||
(s/filter (fn [order]
|
||||
;; sometimes orders stay open in square. At least one payment
|
||||
;; is needed to import, in order to avoid importing orders in-progress.
|
||||
(and
|
||||
(or (> (count (:tenders order)) 0)
|
||||
(seq (:returns order)))
|
||||
(or (= #{} (set (map #(:status (:card_details %)) (:tenders order))))
|
||||
(not= #{} (set/difference
|
||||
(set (map #(:status (:card_details %)) (:tenders order)))
|
||||
#{"FAILED" "VOIDED"}))))))
|
||||
(s/map #(mu/with-context lc (order->sales-order client location %)))
|
||||
(s/buffer 10)
|
||||
(s/realize-each)
|
||||
(s/reduce into []))))
|
||||
(de/catch (fn [e]
|
||||
(log/error ::cant-create-results
|
||||
:exception e)))))))
|
||||
(and
|
||||
(or (> (count (:tenders order)) 0)
|
||||
(seq (:returns order)))
|
||||
(or (= #{} (set (map #(:status (:card_details %)) (:tenders order))))
|
||||
(not= #{} (set/difference
|
||||
(set (map #(:status (:card_details %)) (:tenders order)))
|
||||
#{"FAILED" "VOIDED"}))))))
|
||||
(s/map #(mu/with-context lc (order->sales-order client location %)))
|
||||
(s/buffer 10)
|
||||
(s/realize-each)
|
||||
(s/reduce into []))))
|
||||
(de/catch (fn [e]
|
||||
(log/error ::cant-create-results
|
||||
:exception e)))))))
|
||||
|
||||
|
||||
(defn get-payment [client p]
|
||||
(de/chain (manifold-api-call
|
||||
(de/chain (manifold-api-call
|
||||
{:url (str "https://connect.squareup.com/v2/payments/" p)
|
||||
:method :get
|
||||
:headers (client-base-headers client)})
|
||||
@@ -418,74 +411,73 @@
|
||||
|
||||
(defn continue-payout-entry-list [c l poi cursor]
|
||||
(capture-context->lc lc
|
||||
(de/chain
|
||||
(manifold-api-call
|
||||
{:url (str "https://connect.squareup.com/v2/payouts/" poi "/payout-entries" "?cursor=" cursor )
|
||||
:method :get
|
||||
:headers (client-base-headers c "2023-04-19")
|
||||
:as :json})
|
||||
:body
|
||||
(fn [result]
|
||||
(mu/with-context lc
|
||||
(log/info ::payout-list-found
|
||||
:count (count (:payout_entries result)))
|
||||
(if (not-empty (:cursor result))
|
||||
(de/chain (continue-payout-entry-list c l poi (:cursor result))
|
||||
(fn [continued-results]
|
||||
(mu/with-context lc
|
||||
(concat (:payout_entries result) continued-results))))
|
||||
(:payout_entries result)))))))
|
||||
(de/chain
|
||||
(manifold-api-call
|
||||
{:url (str "https://connect.squareup.com/v2/payouts/" poi "/payout-entries" "?cursor=" cursor)
|
||||
:method :get
|
||||
:headers (client-base-headers c "2023-04-19")
|
||||
:as :json})
|
||||
:body
|
||||
(fn [result]
|
||||
(mu/with-context lc
|
||||
(log/info ::payout-list-found
|
||||
:count (count (:payout_entries result)))
|
||||
(if (not-empty (:cursor result))
|
||||
(de/chain (continue-payout-entry-list c l poi (:cursor result))
|
||||
(fn [continued-results]
|
||||
(mu/with-context lc
|
||||
(concat (:payout_entries result) continued-results))))
|
||||
(:payout_entries result)))))))
|
||||
|
||||
(defn get-payout-entry-list [c l poi]
|
||||
(capture-context->lc lc
|
||||
(de/chain
|
||||
(manifold-api-call
|
||||
{:url (str "https://connect.squareup.com/v2/payouts/" poi "/payout-entries")
|
||||
:method :get
|
||||
:headers (client-base-headers c "2023-04-19")
|
||||
:as :json})
|
||||
:body
|
||||
(fn [result]
|
||||
(mu/with-context lc
|
||||
(log/info ::payout-list-found
|
||||
:count (count (:payout_entries result)))
|
||||
(if (not-empty (:cursor result))
|
||||
(de/chain (continue-payout-entry-list c l poi (:cursor result))
|
||||
(fn [continued-results]
|
||||
(mu/with-context lc
|
||||
(concat (:payout_entries result) continued-results))))
|
||||
(:payout_entries result)))))))
|
||||
(de/chain
|
||||
(manifold-api-call
|
||||
{:url (str "https://connect.squareup.com/v2/payouts/" poi "/payout-entries")
|
||||
:method :get
|
||||
:headers (client-base-headers c "2023-04-19")
|
||||
:as :json})
|
||||
:body
|
||||
(fn [result]
|
||||
(mu/with-context lc
|
||||
(log/info ::payout-list-found
|
||||
:count (count (:payout_entries result)))
|
||||
(if (not-empty (:cursor result))
|
||||
(de/chain (continue-payout-entry-list c l poi (:cursor result))
|
||||
(fn [continued-results]
|
||||
(mu/with-context lc
|
||||
(concat (:payout_entries result) continued-results))))
|
||||
(:payout_entries result)))))))
|
||||
|
||||
(defn payouts
|
||||
([client location] (payouts client location (time/plus (time/now) (time/days -7)) (time/now)))
|
||||
([client location start end]
|
||||
(with-context-as {:location (:square-location/client-location location)} lc
|
||||
(de/chain (manifold-api-call
|
||||
{:url (str "https://connect.squareup.com/v2/payouts/?"
|
||||
(url/map->query
|
||||
{:location_id (:square-location/square-id location)
|
||||
:begin_time (->square-date start)
|
||||
:end_time (->square-date end)}) )
|
||||
:method :get
|
||||
:headers (client-base-headers client "2023-04-19")
|
||||
})
|
||||
:body
|
||||
:payouts
|
||||
(fn [payouts]
|
||||
(if (seq payouts)
|
||||
(->> payouts
|
||||
(s/->source)
|
||||
(s/map (fn [payout]
|
||||
(mu/with-context lc
|
||||
(log/info ::looking-up-payout
|
||||
:payout-id (:id payout))
|
||||
(de/chain (get-payout-entry-list client location (:id payout))
|
||||
(fn [payout-entries]
|
||||
(assoc payout :payout_entries payout-entries ))))))
|
||||
(s/buffer 10)
|
||||
(s/realize-each)
|
||||
(s/reduce conj []))
|
||||
[]))))))
|
||||
(de/chain (manifold-api-call
|
||||
{:url (str "https://connect.squareup.com/v2/payouts/?"
|
||||
(url/map->query
|
||||
{:location_id (:square-location/square-id location)
|
||||
:begin_time (->square-date start)
|
||||
:end_time (->square-date end)}))
|
||||
:method :get
|
||||
:headers (client-base-headers client "2023-04-19")})
|
||||
:body
|
||||
:payouts
|
||||
(fn [payouts]
|
||||
(if (seq payouts)
|
||||
(->> payouts
|
||||
(s/->source)
|
||||
(s/map (fn [payout]
|
||||
(mu/with-context lc
|
||||
(log/info ::looking-up-payout
|
||||
:payout-id (:id payout))
|
||||
(de/chain (get-payout-entry-list client location (:id payout))
|
||||
(fn [payout-entries]
|
||||
(assoc payout :payout_entries payout-entries))))))
|
||||
(s/buffer 10)
|
||||
(s/realize-each)
|
||||
(s/reduce conj []))
|
||||
[]))))))
|
||||
|
||||
(defn transformed-payouts
|
||||
([client location]
|
||||
@@ -493,12 +485,12 @@
|
||||
([client location start end]
|
||||
(transformed-payouts client location (payouts client location start end)))
|
||||
([client location payouts]
|
||||
(with-context-as {:location (:square-location/client-location location)} lc
|
||||
(with-context-as {:location (:square-location/client-location location)} lc
|
||||
(de/chain payouts
|
||||
(fn [payouts]
|
||||
(mu/with-context lc
|
||||
(log/info ::transforming-payouts)
|
||||
(try
|
||||
(try
|
||||
(->> (for [payout payouts
|
||||
:let [best-sales-date (some->> (dc/q '[:find ?s4 (count ?s)
|
||||
:in $ ?payout-id
|
||||
@@ -518,7 +510,7 @@
|
||||
coerce/to-date-time
|
||||
atime/as-local-time
|
||||
coerce/to-date)
|
||||
|
||||
|
||||
;; TODO delete this - this is only needed during the short transformation time
|
||||
equivalent-already-exists? (seq (dc/q '[:find ?s
|
||||
:in $ ?c ?a
|
||||
@@ -530,12 +522,10 @@
|
||||
[?s :expected-deposit/external-id ?eid]
|
||||
[(clojure.string/includes? ?eid "settlement")]
|
||||
[?s :expected-deposit/total ?t]
|
||||
[(iol-ion.query/dollars= ?t ?a)]
|
||||
]
|
||||
[(iol-ion.query/dollars= ?t ?a)]]
|
||||
(dc/db conn)
|
||||
(:db/id client)
|
||||
(amount->money (:amount_money payout))
|
||||
))]
|
||||
(amount->money (:amount_money payout))))]
|
||||
:when (not equivalent-already-exists?)]
|
||||
#:expected-deposit {:external-id (str "square/payout/" (:id payout))
|
||||
:vendor :vendor/ccp-square
|
||||
@@ -574,7 +564,7 @@
|
||||
(fn [refunds]
|
||||
(->> refunds
|
||||
(filter (fn [r] (= "COMPLETED" (:status r))))
|
||||
(s/->source )
|
||||
(s/->source)
|
||||
(s/map (fn [r]
|
||||
(de/chain
|
||||
(get-payment client (:payment_id r))
|
||||
@@ -597,20 +587,20 @@
|
||||
(s/realize-each)
|
||||
(s/reduce conj []))))))
|
||||
(defn upsert
|
||||
([client ]
|
||||
([client]
|
||||
(apply de/zip
|
||||
(for [square-location (:client/square-locations client)
|
||||
:when (:square-location/client-location square-location)]
|
||||
(upsert client square-location (time/plus (time/now) (time/days -14)) (time/now)))))
|
||||
([client location start end]
|
||||
(capture-context->lc
|
||||
(de/chain (daily-results client location start end)
|
||||
(fn [results ]
|
||||
(mu/with-context lc
|
||||
(doseq [x (partition-all 100 results)]
|
||||
(log/info ::loading-orders
|
||||
:count (count x))
|
||||
@(dc/transact-async conn x))))))))
|
||||
(de/chain (daily-results client location start end)
|
||||
(fn [results]
|
||||
(mu/with-context lc
|
||||
(doseq [x (partition-all 100 results)]
|
||||
(log/info ::loading-orders
|
||||
:count (count x))
|
||||
@(dc/transact-async conn x))))))))
|
||||
|
||||
|
||||
(defn upsert-payouts
|
||||
@@ -624,7 +614,7 @@
|
||||
([client location start end]
|
||||
(with-context-as {:source "Square payout loading"
|
||||
:client (:client/code client)} lc
|
||||
|
||||
|
||||
(de/chain (transformed-payouts client location start end)
|
||||
(fn [payouts]
|
||||
(mu/with-context lc
|
||||
@@ -637,35 +627,33 @@
|
||||
|
||||
(defn upsert-refunds
|
||||
([client]
|
||||
(apply de/zip
|
||||
(apply de/zip
|
||||
(for [square-location (:client/square-locations client)
|
||||
:when (:square-location/client-location square-location)]
|
||||
(upsert-refunds client square-location))))
|
||||
([client location]
|
||||
(with-context-as {:source "Square refunds loading"
|
||||
:client (:client/code client)} lc
|
||||
|
||||
|
||||
(de/chain (refunds client location)
|
||||
(fn [refunds]
|
||||
(mu/with-context lc
|
||||
(try
|
||||
(try
|
||||
(doseq [x (partition-all 100 refunds)]
|
||||
(log/info ::loading-refunds
|
||||
:count (count x)
|
||||
:sample (first x))
|
||||
@(dc/transact-async conn x))
|
||||
|
||||
|
||||
(catch Throwable e
|
||||
(log/error ::upsert-refunds-failed
|
||||
:exception e)))
|
||||
|
||||
|
||||
(log/info ::done-loading-refunds)))))))
|
||||
|
||||
|
||||
(defn get-cash-shift [client id]
|
||||
(de/chain (manifold-api-call {:url (str (url/url "https://connect.squareup.com/v2/cash-drawers/shifts" id
|
||||
|
||||
))
|
||||
(de/chain (manifold-api-call {:url (str (url/url "https://connect.squareup.com/v2/cash-drawers/shifts" id))
|
||||
:method :get
|
||||
|
||||
:headers (client-base-headers client "2023-04-19")
|
||||
@@ -680,11 +668,11 @@
|
||||
(de/chain (manifold-api-call {:url (str "https://connect.squareup.com/v2/cash-drawers/shifts"
|
||||
"?"
|
||||
|
||||
(url/map->query
|
||||
{:location_id (:square-location/square-id l)
|
||||
:begin_time (->square-date start)
|
||||
:end_time (->square-date end)
|
||||
:limit 1000}))
|
||||
(url/map->query
|
||||
{:location_id (:square-location/square-id l)
|
||||
:begin_time (->square-date start)
|
||||
:end_time (->square-date end)
|
||||
:limit 1000}))
|
||||
:method :get
|
||||
|
||||
:headers (client-base-headers client "2023-04-19")
|
||||
@@ -694,49 +682,48 @@
|
||||
(fn [shifts]
|
||||
(->> shifts
|
||||
(filter (fn [r] (= "ENDED" (:state r))))
|
||||
(s/->source )
|
||||
(s/->source)
|
||||
(s/map (fn [s]
|
||||
(de/chain
|
||||
(get-cash-shift client (:id s))
|
||||
(fn [cash-drawer-shift]
|
||||
#:cash-drawer-shift {:external-id (str "square/cash-drawer-shift/" (:id cash-drawer-shift))
|
||||
:vendor :vendor/ccp-square
|
||||
:paid-in (amount->money (:cash_paid_in_money cash-drawer-shift))
|
||||
:paid-out (amount->money (:cash_paid_out_money cash-drawer-shift))
|
||||
:expected-cash (amount->money (:expected_cash_money cash-drawer-shift))
|
||||
:opened-cash (amount->money (:opened_cash_money cash-drawer-shift))
|
||||
:date (coerce/to-date (:opened_at cash-drawer-shift))
|
||||
:client (:db/id client)
|
||||
:location (:square-location/client-location l)
|
||||
}))))
|
||||
(get-cash-shift client (:id s))
|
||||
(fn [cash-drawer-shift]
|
||||
#:cash-drawer-shift {:external-id (str "square/cash-drawer-shift/" (:id cash-drawer-shift))
|
||||
:vendor :vendor/ccp-square
|
||||
:paid-in (amount->money (:cash_paid_in_money cash-drawer-shift))
|
||||
:paid-out (amount->money (:cash_paid_out_money cash-drawer-shift))
|
||||
:expected-cash (amount->money (:expected_cash_money cash-drawer-shift))
|
||||
:opened-cash (amount->money (:opened_cash_money cash-drawer-shift))
|
||||
:date (coerce/to-date (:opened_at cash-drawer-shift))
|
||||
:client (:db/id client)
|
||||
:location (:square-location/client-location l)}))))
|
||||
(s/buffer 5)
|
||||
(s/realize-each)
|
||||
(s/reduce conj []))))))
|
||||
|
||||
(defn upsert-cash-shifts
|
||||
([client]
|
||||
(apply de/zip
|
||||
(apply de/zip
|
||||
(for [square-location (:client/square-locations client)
|
||||
:when (:square-location/client-location square-location)]
|
||||
(upsert-cash-shifts client square-location))))
|
||||
([client location]
|
||||
(with-context-as {:source "Square cash shift loading"
|
||||
:client (:client/code client)} lc
|
||||
|
||||
|
||||
(de/chain (cash-drawer-shifts client location)
|
||||
(fn [cash-shifts]
|
||||
(mu/with-context lc
|
||||
(try
|
||||
(try
|
||||
(doseq [x (partition-all 100 cash-shifts)]
|
||||
(log/info ::loading-cash-shifts
|
||||
:count (count x)
|
||||
:sample (first x))
|
||||
@(dc/transact-async conn x))
|
||||
|
||||
|
||||
(catch Throwable e
|
||||
(log/error ::upsert-cash-shifts-failed
|
||||
:exception e)))
|
||||
|
||||
|
||||
(log/info ::done-loading-cash-shifts)))))))
|
||||
|
||||
(def square-read [:db/id
|
||||
@@ -754,7 +741,7 @@
|
||||
:in $
|
||||
:where [?c :client/square-auth-token]]
|
||||
(dc/db conn))))
|
||||
([ & codes]
|
||||
([& codes]
|
||||
(map first (dc/q '[:find (pull ?c [:db/id
|
||||
:client/code
|
||||
:client/square-auth-token
|
||||
@@ -768,110 +755,111 @@
|
||||
(defn get-square-client-and-location [code]
|
||||
(let [[client] (get-square-clients code)]
|
||||
(some->> client
|
||||
:client/square-locations
|
||||
(filter :square-location/client-location)
|
||||
seq
|
||||
(conj [client]))))
|
||||
:client/square-locations
|
||||
(filter :square-location/client-location)
|
||||
seq
|
||||
(conj [client]))))
|
||||
|
||||
(defn upsert-locations
|
||||
([]
|
||||
(apply de/zip
|
||||
(apply de/zip
|
||||
(for [client (get-square-clients)]
|
||||
(upsert-locations client))))
|
||||
([client]
|
||||
(let [square-id->id (into {}
|
||||
(map
|
||||
(fn [sl]
|
||||
[(:square-location/square-id sl)
|
||||
(:db/id sl)])
|
||||
(:client/square-locations client)))]
|
||||
(fn [sl]
|
||||
[(:square-location/square-id sl)
|
||||
(:db/id sl)])
|
||||
(:client/square-locations client)))]
|
||||
(de/chain (client-locations client)
|
||||
(fn [client-locations]
|
||||
@(dc/transact-async conn
|
||||
(for [square-location client-locations]
|
||||
{:db/id (or (square-id->id (:id square-location)) (str (java.util.UUID/randomUUID)))
|
||||
:client/_square-locations (:db/id client)
|
||||
:square-location/name (:name square-location)
|
||||
:square-location/square-id (:id square-location)})))))))
|
||||
(for [square-location client-locations]
|
||||
{:db/id (or (square-id->id (:id square-location)) (str (java.util.UUID/randomUUID)))
|
||||
:client/_square-locations (:db/id client)
|
||||
:square-location/name (:name square-location)
|
||||
:square-location/square-id (:id square-location)})))))))
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(defn reset []
|
||||
(->>
|
||||
(dc/q {:find ['?e]
|
||||
:in ['$]
|
||||
:where ['(or [?e :sales-order/date]
|
||||
[?e :expected-deposit/date])]}
|
||||
(dc/db conn))
|
||||
(->>
|
||||
(dc/q {:find ['?e]
|
||||
:in ['$]
|
||||
:where ['(or [?e :sales-order/date]
|
||||
[?e :expected-deposit/date])]}
|
||||
(dc/db conn))
|
||||
(map first)
|
||||
(map (fn [x] [:db/retractEntity x]))))
|
||||
|
||||
(defn mark-integration-status [client integration-status]
|
||||
@(dc/transact-async conn
|
||||
[{:db/id (:db/id client)
|
||||
:client/square-integration-status (assoc integration-status
|
||||
:db/id (or (-> client :client/square-integration-status :db/id)
|
||||
(str (java.util.UUID/randomUUID))))}]))
|
||||
[{:db/id (:db/id client)
|
||||
:client/square-integration-status (assoc integration-status
|
||||
:db/id (or (-> client :client/square-integration-status :db/id)
|
||||
(str (java.util.UUID/randomUUID))))}]))
|
||||
|
||||
(defn upsert-all [ & clients]
|
||||
(defn upsert-all [& clients]
|
||||
(capture-context->lc
|
||||
(log/info ::starting-upsert)
|
||||
(->> (apply get-square-clients clients)
|
||||
(s/->source)
|
||||
(s/filter (fn [client]
|
||||
(seq (filter :square-location/client-location (:client/square-locations client)))))
|
||||
(s/map (fn [client]
|
||||
(with-context-as (merge lc {:client (:client/code client)}) lc
|
||||
(log/info ::import-started)
|
||||
(mark-integration-status client {:integration-status/last-attempt (coerce/to-date (time/now))})
|
||||
(log/info ::starting-upsert)
|
||||
(->> (apply get-square-clients clients)
|
||||
(s/->source)
|
||||
(s/filter (fn [client]
|
||||
(seq (filter :square-location/client-location (:client/square-locations client)))))
|
||||
(s/map (fn [client]
|
||||
(with-context-as (merge lc {:client (:client/code client)}) lc
|
||||
(log/info ::import-started)
|
||||
(mark-integration-status client {:integration-status/last-attempt (coerce/to-date (time/now))})
|
||||
|
||||
(->
|
||||
(de/chain (upsert-locations client)
|
||||
(fn [_]
|
||||
(mu/with-context lc
|
||||
(log/info ::upsert-orders-started)
|
||||
(upsert client)))
|
||||
(fn [_]
|
||||
(mu/with-context lc
|
||||
(log/info ::upsert-payouts-started)
|
||||
(upsert-payouts client)))
|
||||
(fn [_]
|
||||
(mu/with-context lc
|
||||
(log/info ::upsert-refunds-started)
|
||||
(upsert-refunds client)))
|
||||
(->
|
||||
(de/chain (upsert-locations client)
|
||||
(fn [_]
|
||||
(mu/with-context lc
|
||||
(log/info ::upsert-orders-started)
|
||||
(upsert client)))
|
||||
(fn [_]
|
||||
(mu/with-context lc
|
||||
(log/info ::upsert-payouts-started)
|
||||
(upsert-payouts client)))
|
||||
(fn [_]
|
||||
(mu/with-context lc
|
||||
(log/info ::upsert-refunds-started)
|
||||
(upsert-refunds client)))
|
||||
|
||||
(fn [_]
|
||||
(mu/with-context lc
|
||||
(log/info ::upsert-cash-shifts)
|
||||
(upsert-cash-shifts client)))
|
||||
(fn [_]
|
||||
(mu/with-context lc
|
||||
(log/info ::upsert-done))
|
||||
(mark-integration-status client {:integration-status/state :integration-state/success
|
||||
:integration-status/last-updated (coerce/to-date (time/now))})))
|
||||
(de/catch (fn [e]
|
||||
(mu/with-context lc
|
||||
(let [data (ex-data e)]
|
||||
(log/info ::upsert-all-failed
|
||||
:severity :error
|
||||
:exception e)
|
||||
(cond (= (:status data) 401)
|
||||
(mark-integration-status client {:integration-status/state :integration-state/unauthorized
|
||||
:integration-status/message (-> data :body str)})
|
||||
(fn [_]
|
||||
(mu/with-context lc
|
||||
(log/info ::upsert-cash-shifts)
|
||||
(upsert-cash-shifts client)))
|
||||
(fn [_]
|
||||
(mu/with-context lc
|
||||
(log/info ::upsert-done))
|
||||
(mark-integration-status client {:integration-status/state :integration-state/success
|
||||
:integration-status/last-updated (coerce/to-date (time/now))})))
|
||||
(de/catch (fn [e]
|
||||
(mu/with-context lc
|
||||
(let [data (ex-data e)]
|
||||
(log/info ::upsert-all-failed
|
||||
:severity :error
|
||||
:exception e)
|
||||
(cond (= (:status data) 401)
|
||||
(mark-integration-status client {:integration-status/state :integration-state/unauthorized
|
||||
:integration-status/message (-> data :body str)})
|
||||
|
||||
(= (:status data) 503)
|
||||
(mark-integration-status client {:integration-status/state :integration-state/failed
|
||||
:integration-status/message (-> data :body str)})
|
||||
:else
|
||||
(mark-integration-status client {:integration-status/state :integration-state/failed
|
||||
:integration-status/message (or (ex-message e)
|
||||
(str e))}))))))))))
|
||||
(s/buffer 5)
|
||||
(s/realize-each)
|
||||
(s/reduce conj []))))
|
||||
(= (:status data) 503)
|
||||
(mark-integration-status client {:integration-status/state :integration-state/failed
|
||||
:integration-status/message (-> data :body str)})
|
||||
:else
|
||||
(mark-integration-status client {:integration-status/state :integration-state/failed
|
||||
:integration-status/message (or (ex-message e)
|
||||
(str e))}))))))))))
|
||||
(s/buffer 5)
|
||||
(s/realize-each)
|
||||
(s/reduce conj []))))
|
||||
|
||||
(defn do-upsert-all [& clients]
|
||||
(mu/trace
|
||||
::upsert-all
|
||||
[:clients clients]
|
||||
@(apply upsert-all clients)))
|
||||
::upsert-all
|
||||
[:clients clients]
|
||||
@(apply upsert-all clients)))
|
||||
|
||||
|
||||
|
||||
227
src/clj/auto_ap/ssr/admin/sales_summaries.clj
Normal file
227
src/clj/auto_ap/ssr/admin/sales_summaries.clj
Normal file
@@ -0,0 +1,227 @@
|
||||
(ns auto-ap.ssr.admin.sales-summaries
|
||||
(: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]]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.grid-page-helper :as helper]
|
||||
[auto-ap.ssr.utils
|
||||
:refer [apply-middleware-to-all-handlers]]
|
||||
[auto-ap.time :as atime]
|
||||
[bidi.bidi :as bidi]
|
||||
[clj-time.coerce :as c]
|
||||
[datomic.api :as dc]
|
||||
[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)
|
||||
"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)}))
|
||||
|
||||
#_(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]
|
||||
*]) ;; TODO
|
||||
|
||||
(defn fetch-ids [db request]
|
||||
(let [query-params (:parsed-query-params request)
|
||||
valid-clients (extract-client-ids (:clients request)
|
||||
(:client request)
|
||||
(:client-id query-params)
|
||||
(when (:client-code query-params)
|
||||
[:client/code (:client-code query-params)]))
|
||||
query (cond-> {:query {:find []
|
||||
:in '[$ [?client ...]]
|
||||
:where '[[?e :sales-summary/client ?client]]}
|
||||
:args [db valid-clients]}
|
||||
(or (:start-date query-params)
|
||||
(:end-date query-params))
|
||||
(merge-query {:query '{:where [[?e :sales-summary/date ?d]]}})
|
||||
|
||||
(:start-date query-params)
|
||||
(merge-query {:query '{:in [?start-date]
|
||||
:where [[(>= ?d ?start-date)]]}
|
||||
:args [(-> query-params :start-date c/to-date)]})
|
||||
|
||||
(:end-date query-params)
|
||||
(merge-query {:query '{:in [?end-date]
|
||||
: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]]}}))]
|
||||
(cond->> (query2 query)
|
||||
true (apply-sort-3 query-params)
|
||||
true (apply-pagination query-params))))
|
||||
|
||||
(defn hydrate-results [ids db _]
|
||||
(let [results (->> (pull-many db default-read ids)
|
||||
(group-by :db/id))
|
||||
refunds (->> ids
|
||||
(map results)
|
||||
(map first))]
|
||||
refunds))
|
||||
|
||||
(defn fetch-page [request]
|
||||
(let [db (dc/db conn)
|
||||
{ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]
|
||||
|
||||
[(->> (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
|
||||
:nav (com/admin-aside-nav)
|
||||
:fetch-page fetch-page
|
||||
:page-specific-nav filters
|
||||
:row-buttons (fn [_ entity]
|
||||
[])
|
||||
: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
|
||||
:parse-query-params (comp
|
||||
(helper/default-parse-query-params grid-page))
|
||||
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:admin)}
|
||||
"Admin"]
|
||||
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
::route/page)}
|
||||
"Sales Summaries"]]
|
||||
:title "Sales Summaries"
|
||||
:entity-name "Daily Summary"
|
||||
:route ::route/table
|
||||
:headers [{:key "date"
|
||||
:name "Date"
|
||||
:sort-key "date"
|
||||
:render #(some-> % :sales-summary/date (atime/unparse-local atime/normal-date))}
|
||||
{:key "credits"
|
||||
:name "Credits"
|
||||
:sort-key "credits"
|
||||
:render (fn [ss]
|
||||
(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: " (format "$%,.2f" (- (+ (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]
|
||||
(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)))
|
||||
|
||||
(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
|
||||
[:li "Card Payments: "
|
||||
(format "$%,.2f" card-payments)]
|
||||
|
||||
[:li "Food App Payments: "
|
||||
(format "$%,.2f" food-app-payments)]
|
||||
[:li "Cash Payments: "
|
||||
|
||||
(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)))))
|
||||
@@ -14,6 +14,7 @@
|
||||
[auto-ap.ssr.admin.transaction-rules :as admin-rules]
|
||||
[auto-ap.ssr.admin.vendors :as admin-vendors]
|
||||
[auto-ap.ssr.admin.clients :as admin-clients]
|
||||
[auto-ap.ssr.admin.sales-summaries :as admin-sales-summaries]
|
||||
[auto-ap.ssr.auth :as auth]
|
||||
[auto-ap.ssr.indicators :as indicators]
|
||||
[auto-ap.ssr.company :as company]
|
||||
@@ -94,6 +95,7 @@
|
||||
(into admin-excel-invoices/key->handler)
|
||||
(into admin/key->handler)
|
||||
(into admin-jobs/key->handler)
|
||||
(into admin-sales-summaries/key->handler)
|
||||
(into admin-vendors/key->handler)
|
||||
(into admin-clients/key->handler)
|
||||
(into admin-rules/key->handler)
|
||||
|
||||
3
src/cljc/auto_ap/routes/admin/sales_summaries.cljc
Normal file
3
src/cljc/auto_ap/routes/admin/sales_summaries.cljc
Normal file
@@ -0,0 +1,3 @@
|
||||
(ns auto-ap.routes.admin.sales-summaries)
|
||||
(def routes {"" {:get ::page}
|
||||
"/table" ::table})
|
||||
@@ -7,6 +7,7 @@
|
||||
[auto-ap.routes.payments :as p-routes]
|
||||
[auto-ap.routes.invoice :as i-routes]
|
||||
[auto-ap.routes.admin.clients :as ac-routes]
|
||||
[auto-ap.routes.admin.sales-summaries :as ss-routes]
|
||||
[auto-ap.routes.admin.transaction-rules :as tr-routes]))
|
||||
|
||||
(def routes {"impersonate" :impersonate
|
||||
@@ -51,7 +52,8 @@
|
||||
["/disapprove/" [#"\d+" :transaction-id]] {:delete :transaction-insight-disapprove}
|
||||
["/rows/" [#"\d+" :after]] {:get :transaction-insight-rows}
|
||||
["/explain/" [#"\d+" :transaction-id]] {:get :transaction-insight-explain}}}
|
||||
"pos" {"/sales" {"" {:get :pos-sales}
|
||||
"pos" {"/summaries" ss-routes/routes
|
||||
"/sales" {"" {:get :pos-sales}
|
||||
"/table" {:get :pos-sales-table}}
|
||||
"/expected-deposit" {"" {:get :pos-expected-deposits}
|
||||
"/table" {:get :pos-expected-deposit-table}}
|
||||
|
||||
Reference in New Issue
Block a user