working on ezcater

ezcater other.

dajusments.

migration for square2
This commit is contained in:
2022-08-19 06:42:40 -07:00
parent dae1cb4a9b
commit bd0f8da16b
21 changed files with 1158 additions and 155 deletions

View File

@@ -292,5 +292,43 @@
:db/index true}]]
:requires [:add-orders]}
:add-ezcater-vendor {:txes [[{:db/ident :vendor/ccp-ezcater
:vendor/name "EZCater CCP"}]]}})
:vendor/name "EZCater CCP"}]]}
:add-charge-client {:txes [[{:db/ident :charge/client
:db/doc "The client for the sale"
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one}
{:db/ident :charge/location
:db/doc "The location of the sale"
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}]]}
:add-charge-date {:txes [[{:db/ident :charge/date
:db/doc "The date for the payment"
:db/valueType :db.type/instant
:db/cardinality :db.cardinality/one}]]}
:add-ezcater-processor {:txes [[{:db/ident :ccp-processor/ezcater
:db/doc "ezcater processor"}]]}
:add-koala-processor {:txes [[{:db/ident :ccp-processor/koala
:db/doc "koala processor"}]]}
:add-square-processor {:txes [[{:db/ident :ccp-processor/square
:db/doc "square processor"}]]}
:add-reference-link3 {:txes [[{:db/ident :sales-order/reference-link
:db/cardinality :db.cardinality/one
:db/valueType :db.type/string
:db/doc "A link someone can go to to look at the order"}
{:db/ident :charge/reference-link
:db/cardinality :db.cardinality/one
:db/valueType :db.type/string
:db/doc "A link someone can go to to look at the charge"}]]}
:add-source-field2 {:txes [[{:db/ident :sales-order/source
:db/cardinality :db.cardinality/one
:db/valueType :db.type/string
:db/doc "Where the POS says the order is from"}
{:db/ident :charge/note
:db/cardinality :db.cardinality/one
:db/valueType :db.type/string
:db/doc "A custom note"}]]}
:add-feature-flags {:txes [[{:db/ident :client/feature-flags
:db/valueType :db.type/string
:db/cardinality :db.cardinality/many}]]}})

View File

@@ -29,6 +29,7 @@
(:sort args) (add-sorter-fields {"client" ['[?e :sales-order/client ?c]
'[?c :client/name ?sort-client]]
"location" ['[?e :sales-order/location ?sort-location]]
"source" ['[?e :sales-order/source ?sort-source]]
"date" ['[?e :sales-order/date ?sort-date]]
"total" ['[?e :sales-order/total ?sort-total]]
"tax" ['[?e :sales-order/tax ?sort-tax]]
@@ -68,6 +69,11 @@
'[?chg :charge/processor ?processor]]}
:args [(keyword "ccp-processor"
(name (:processor args)))]})
(:type-name args)
(merge-query {:query {:in ['?type-name]
:where ['[?e :sales-order/charges ?chg]
'[?chg :charge/type-name ?type-name]]}
:args [(:type-name args)]})
(:total-gte args)
(merge-query {:query {:in ['?total-gte]

View File

@@ -9,7 +9,8 @@
[clojure.tools.logging :as log]
[clj-time.core :as time]
[clojure.set :as set]
[auto-ap.time :as atime]))
[auto-ap.time :as atime]
[cemerick.url :as url]))
(defn query [{:ezcater-integration/keys [api-key]} q]
(-> (client/post "https://api.ezcater.com/graphql/"
@@ -118,118 +119,156 @@
[:ezcater-caterer/uuid caterer-uuid]))
(defn round-carry-cents [f]
(with-precision 2 (double (.setScale (bigdec f) 2 java.math.RoundingMode/HALF_UP))))
(defn commision [order]
(let [commision% (if (= "MARKETPLACE" (:orderSourceType order))
0.15
0.07)]
(* commision%
0.01
(+
(-> order :totals :subTotal :subunits )
(reduce +
0
(map :subunits (:feesAndDiscounts (:catererCart order))))))))
(let [commision% (cond
(= "CLUB_SODA" (:orderSourceType order))
0.25M
(= "MARKETPLACE" (:orderSourceType order))
0.15M
:else
0.07M)]
(round-carry-cents
(* commision%
0.01M
(+
(-> order :totals :subTotal :subunits )
(reduce +
0
(map (comp :subunits :cost) (:feesAndDiscounts (:catererCart order)))))))))
(defn ccp-fee [order]
(* 0.000275
(+
(-> order :totals :subTotal :subunits )
(-> order :totals :salesTax :subunits )
(reduce +
0
(map :subunits (:feesAndDiscounts (:catererCart order)))))))
(round-carry-cents
(* 0.000275M
(+
(-> order :totals :subTotal :subunits )
(-> order :totals :salesTax :subunits )
(reduce +
0
(map (comp :subunits :cost) (:feesAndDiscounts (:catererCart order))))))))
(defn order->sales-order [{{:keys [timestamp]} :event {:keys [orderItems]} :catererCart :keys [client-code client-location uuid] :as order}]
#:sales-order
{:date (atime/localize (coerce/to-date-time timestamp))
:external-id (str "ezcater/order/" client-code "-" client-location "-" uuid)
:client [:client/code client-code]
:location client-location
:line-items (->> orderItems
(map-indexed (fn [i li]
#:order-line-item
{:external-id (str "ezcater/order/" client-code "-" client-location "-" uuid "-" i)
:item-name (:name li)
:category "External Catering"
:total (* 0.01 (:subunits (:totalInSubunits li)))})))
(let [adjustment (round-carry-cents (- (+ (-> order :totals :subTotal :subunits (* 0.01))
(-> order :totals :salesTax :subunits (* 0.01)))
(-> order :catererCart :totals :catererTotalDue )
(commision order)
(ccp-fee order)))
service-charge (+ (commision order) (ccp-fee order))
tax (-> order :totals :salesTax :subunits (* 0.01))
tip (-> order :totals :tip :subunits (* 0.01))]
#:sales-order
{:date (atime/localize (coerce/to-date-time timestamp))
:external-id (str "ezcater/order/" client-code "-" client-location "-" uuid)
:client [:client/code client-code]
:location client-location
:reference-link (str (url/url "https://ezmanage.ezcater.com/orders/" uuid ))
:line-items [#:order-line-item
{:external-id (str "ezcater/order/" client-code "-" client-location "-" uuid "-" 0)
:item-name "EZCater Catering"
:category "EZCater Catering"
:discount adjustment
:tax tax
:total (+ (-> order :totals :subTotal :subunits (* 0.01))
tax
tip)}]
:charges [#:charge
{:type-name "CARD"
:date (atime/localize (coerce/to-date-time timestamp))
:client [:client/code client-code]
:location client-location
:external-id (str "ezcater/charge/" uuid)
:processor :ccp-processor/ezcater
:total (+ (-> order :totals :subTotal :subunits (* 0.01))
tax
tip)
:tip tip}]
:total (-> order :catererCart :totals :catererTotalDue )
:discount (- (+ (-> order :totals :subTotal :subunits (* 0.01))
(-> order :totals :salesTax :subunits (* 0.01)))
(-> order :catererCart :totals :catererTotalDue )
(commision order)
(ccp-fee order))
:service-charge (+ (commision order) (ccp-fee order))
:tax (-> order :totals :salesTax :subunits (* 0.01))
:tip (-> order :totals :tip :subunits (* 0.01))
:vendor :vendor/ccp-ezcater})
:total (+ (-> order :totals :subTotal :subunits (* 0.01))
tax
tip)
:discount adjustment
:service-charge service-charge
:tax tax
:tip tip
:returns 0.0
:vendor :vendor/ccp-ezcater}))
(defn lookup-order [json]
(let [caterer (get-caterer (get json "parent_id"))
integration (:ezcater-integration/_caterers caterer)
client (-> caterer :ezcater-location/_caterer first :client/_ezcater-locations :client/code)
location (-> caterer :ezcater-location/_caterer first :ezcater-location/location)]
(-> (query
integration
{:venia/queries [[:order {:id (get json "entity_id")}
[:uuid
:orderSourceType
[:caterer
[:name
:uuid
[:address [:street]]]]
[:event
[:timestamp
:catererHandoffFoodTime
:orderType]]
[:catererCart [[:orderItems
[:name
:quantity
:posItemId
[:totalInSubunits
[:currency
:subunits]]]]
[:totals
[:catererTotalDue]]
[:feesAndDiscounts
{:type 'DELIVERY_FEE}
[[:cost
[:currency
:subunits]]]]]]
[:totals [[:customerTotalDue
[
:currency
:subunits
]]
[:pointOfSaleIntegrationFee
[
:currency
:subunits
]]
[:tip
[:currency
:subunits]]
[:salesTax
[
:currency
:subunits
]]
[:salesTaxRemittance
[:currency
:subunits
]]
[:subTotal
[:currency
:subunits]]]]]]]})
(:order)
(assoc :client-code client
:client-location location))))
(doto
(-> (query
integration
{:venia/queries [[:order {:id (get json "entity_id")}
[:uuid
:orderSourceType
[:caterer
[:name
:uuid
[:address [:street]]]]
[:event
[:timestamp
:catererHandoffFoodTime
:orderType]]
[:catererCart [[:orderItems
[:name
:quantity
:posItemId
[:totalInSubunits
[:currency
:subunits]]]]
[:totals
[:catererTotalDue]]
[:feesAndDiscounts
{:type 'DELIVERY_FEE}
[[:cost
[:currency
:subunits]]]]]]
[:totals [[:customerTotalDue
[
:currency
:subunits
]]
[:pointOfSaleIntegrationFee
[
:currency
:subunits
]]
[:tip
[:currency
:subunits]]
[:salesTax
[
:currency
:subunits
]]
[:salesTaxRemittance
[:currency
:subunits
]]
[:subTotal
[:currency
:subunits]]]]]]]})
(:order)
(assoc :client-code client
:client-location location))
log/info)))
(defn import-order [json]
;; {"id" "bf3dcf5c-a68f-42d9-9084-049133e03d3d", "parent_type" "Caterer", "parent_id" "91541331-d7ae-4634-9e8b-ccbbcfb2ce70", "entity_type" "Order", "entity_id" "9ab05fee-a9c5-483b-a7f2-14debde4b7a8", "key" "accepted", "occurred_at" "2022-07-21T19:21:07.549Z"}
(clojure.pprint/pprint [(-> json
(lookup-order)
(order->sales-order)
(update :sales-order/date coerce/to-date)
(update-in [:sales-order/charges 0 :charge/date] coerce/to-date))])
@(d/transact conn [(-> json
(lookup-order)
(order->sales-order)
(update :sales-order/date coerce/to-date))])
)
(update :sales-order/date coerce/to-date)
(update-in [:sales-order/charges 0 :charge/date] coerce/to-date))]))

View File

@@ -506,6 +506,9 @@
:enums {
:processor {:values [{:enum-value :na}
{:enum-value :doordash}
{:enum-value :koala}
{:enum-value :ezcater}
{:enum-value :square}
{:enum-value :uber_eats}
{:enum-value :grubhub}]}
:integration_state {:values [{:enum-value :failed}

View File

@@ -31,6 +31,8 @@
{:fields {:id {:type :id}
:location {:type 'String}
:external_id {:type 'String}
:reference_link {:type 'String}
:source {:type 'String}
:total {:type :money}
:tip {:type :money}
:tax {:type :money}
@@ -50,11 +52,13 @@
:category {:type 'String}
:discount {:type :money}}}
:charge
{:fields {:id {:type :id}
:processor {:type :processor}
:type_name {:type 'String}
:total {:type :money}
:tip {:type :money}
{:fields {:id {:type :id}
:processor {:type :processor}
:reference_link {:type 'String}
:note {:type 'String}
:type_name {:type 'String}
:total {:type :money}
:tip {:type :money}
:expected_deposit {:type :expected_deposit}}}})
(def queries
@@ -69,6 +73,7 @@
:date_range {:type :date_range}
:total_lte {:type :money}
:total_gte {:type :money}
:type_name {:type 'String}
:processor {:type :processor}
:start {:type 'Int}
:per_page {:type 'Int}

View File

@@ -0,0 +1,9 @@
(ns auto-ap.jobs.square2
(:gen-class)
(:require
[auto-ap.jobs.core :refer [execute]]
[auto-ap.square.core2 :as square2]))
(defn -main [& _]
(execute "square2-loading" square2/upsert-all))

View File

@@ -17,7 +17,6 @@
(POST "/event" request
(log/info (str "POST EVENT " (body-string request) request))
(e/import-order (:json-params request))
{:status 200
:headers {"Content-Type" "application/json"}
:body "{}"}))

View File

@@ -449,7 +449,8 @@
:client/square-auth-token
{:client/square-locations [:db/id :square-location/name :square-location/square-id :square-location/client-location]}]) ...]
:in $
:where [?c :client/square-auth-token]]
:where [?c :client/square-auth-token]
(not [?c :client/feature-flags "new-square"])]
(d/db conn)))
([ & codes]
(d/q '[:find [(pull ?c [:db/id
@@ -458,6 +459,7 @@
{:client/square-locations [:db/id :square-location/name :square-location/square-id :square-location/client-location]}]) ...]
:in $ [?code ...]
:where [?c :client/square-auth-token]
(not [?c :client/feature-flags "new-square"])
[?c :client/code ?code]]
(d/db conn)
codes)))

View File

@@ -0,0 +1,541 @@
(ns auto-ap.square.core2
(:require
[auto-ap.datomic :refer [conn remove-nils]]
[auto-ap.time :as atime]
[clj-http.client :as client]
[clj-time.coerce :as coerce]
[clj-time.core :as time]
[clj-time.format :as f]
[clj-time.periodic :as periodic]
[clojure.core.async :as async]
[clojure.data.json :as json]
[clojure.set :as set]
[clojure.string :as str]
[clojure.tools.logging :as log]
[cemerick.url :as url]
[datomic.api :as d]
[slingshot.slingshot :refer [try+]]
[unilog.context :as lc]))
(defn client-base-headers [client]
{"Square-Version" "2021-08-18"
"Authorization" (str "Bearer " (:client/square-auth-token client))
"Content-Type" "application/json"})
(defn retry-4 [ex try-count _]
(log/warn "Retrying after failure " ex)
(if (> try-count 4) false true))
(defn lookup-dates []
(->> (periodic/periodic-seq (time/plus (time/now) (time/days -50))
(time/now)
(time/days 5))
(map (fn [d]
[(atime/unparse (time/plus d (time/days 1)) atime/iso-date)
(atime/unparse (time/plus d (time/days 5)) atime/iso-date)]))))
(defn client-locations [client]
(try
(->> (client/get "https://connect.squareup.com/v2/locations"
{:headers (client-base-headers client)
:as :json})
:body
:locations)
(catch Exception e
(log/warn e)
[])))
(defn fetch-catalog [client i]
(if i
(try
(log/trace "looking up catalog for" (str "https://connect.squareup.com/v2/catalog/object/" i))
(->> (client/get (str "https://connect.squareup.com/v2/catalog/object/" i)
{:headers (client-base-headers client)
:query-params {"include_related_items" "true"}
:as :json})
:body
:object)
(catch Exception e
(log/error e)
nil))
(log/warn "Trying to look up non existant ")))
(def fetch-catalog-fast (memoize fetch-catalog))
(defn item-id->category-name [client i]
(let [item (fetch-catalog-fast client i)]
(cond (:item_variation_data item)
(item-id->category-name client (:item_id (:item_variation_data item)))
(:category_id (:item_data item))
(:name (:category_data (fetch-catalog-fast client (:category_id (:item_data item)))))
(:item_data item)
"Uncategorized"
:else
(do
(log/warn "couldn't look up" i)
"Uncategorized"))))
(defn pc [start end]
{"query" {"filter" {"date_time_filter"
{
"created_at" {
"start_at" (f/unparse (f/formatter "YYYY-MM-dd'T'HH:mm:ssZZ") start)
"end_at" (f/unparse (f/formatter "YYYY-MM-dd'T'HH:mm:ssZZ") end)
}
}}
"sort" {
"sort_field" "CREATED_AT"
"sort_order" "DESC"
}}})
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn get-order
([client location order-id]
(log/info "Searching for" (:square-location/client-location location))
(let [result (->> (client/get (str "https://connect.squareup.com/v2/orders/" order-id)
{:headers (client-base-headers client)
:as :json})
:body
)]
result)))
(defn continue-search [client location start end cursor]
(log/info "Continuing search for" cursor)
(let [result (->> (client/post "https://connect.squareup.com/v2/orders/search"
{:headers (client-base-headers client)
:body (json/write-str (cond-> {"location_ids" [(:square-location/square-id location)]
"limit" 10000
"cursor" cursor}
start (merge (pc start end))))
:as :json})
:body)]
(log/info "found " (count (:orders result)))
(if (not-empty (:cursor result))
(concat (:orders result) (continue-search client location start end (:cursor result)))
(:orders result))))
(defn search
([client location start end]
(log/info "Searching for" (:square-location/client-location location))
(let [result (->> (client/post "https://connect.squareup.com/v2/orders/search"
{:headers (client-base-headers client)
:body (json/write-str (cond-> {"location_ids" [(:square-location/square-id location)] "limit" 10000}
start (merge (pc start end))))
:as :json})
:body)]
(log/info "found " (count (:orders result)))
(if (not-empty (:cursor result))
(concat (:orders result) (continue-search client location start end (:cursor result)))
(:orders result)))))
(defn amount->money [amt]
(* 0.01 (or (:amount amt) 0.0)))
;; to get totals:
(comment
(reduce
(fn [total i]
(+ total (+ (- (:sales-order/total i)
(:sales-order/tax i)
(:sales-order/tip i)
(:sales-order/service-charge i))
(:sales-order/returns i)
(:sales-order/discount i)
)))
0.0
[]))
(defn tender->charge [order client location t]
(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) ))
:external-id (when (:id t)
(str "square/charge/" (:id t)))
:processor (condp = (:type t)
"OTHER"
(condp = (some-> (:note t) str/lower-case)
"doordash" :ccp-processor/doordash
"dd" :ccp-processor/doordash
"ubereats" :ccp-processor/uber-eats
"ue" :ccp-processor/uber-eats
"grubhub" :ccp-processor/grubhub
"grub" :ccp-processor/grubhub
"gh" :ccp-processor/grubhub
(condp = (:name (:source order))
"GRUBHUB" :ccp-processor/grubhub
"UBEREATS" :ccp-processor/uber-eats
"DOORDASH" :ccp-processor/doordash
"Koala" :ccp-processor/koala
"koala-production" :ccp-processor/koala
:ccp-processor/na))
"CARD"
:ccp-processor/square
"SQUARE_GIFT_CARD"
:ccp-processor/square
"CASH"
:ccp-processor/na
:ccp-processor/na)
:total (amount->money (:amount_money t))
:tip (amount->money (:tip_money t))}))
(defn order->sales-order [client location order]
(let [is-order-only-for-charge?
(= ["CUSTOM_AMOUNT"]
(mapv :item_type (:line_items order )))]
(if is-order-only-for-charge?
(->> (:tenders order)
(map #(tender->charge order client location %)))
[(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 order)
(map-indexed (fn [i 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"
(item-id->category-name client (:catalog_object_id li)))
:total (amount->money (:total_money li))
:tax (amount->money (:total_tax_money li))
:discount (amount->money (:total_discount_money li))}))))})])))
(defn daily-results
([client location]
(daily-results client location (time/plus (time/now) (time/days -45)) (time/now)))
([client location start end]
(let [search-results (search client location start end)]
(->> search-results
(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"}))))))
(mapcat #(order->sales-order client location %))))))
(defn retry
([f] (retry f 0))
([f i]
(if (< i 5)
(try
(f)
(catch Exception e
(log/warn "error pulling http " e)
(retry f (inc i))))
(log/warn "Too many failures"))))
(defn get-payment [client p]
(:payment (:body (retry #(client/get (str "https://connect.squareup.com/v2/payments/" p)
{:headers (client-base-headers client)
:as :json
:retry-handler retry-4})))))
(defn halt-if-error [x]
(if (instance? Throwable x)
(throw x)
x))
(defn get-settlement-sales-date [client settlement]
(let [concurrent 4
output-chan (async/chan)]
(async/pipeline-blocking concurrent
output-chan
(map (fn [p]
(lc/with-context {:source "Square settlements loading "}
(log/trace "looking up payment " p " for settlement " (:id settlement))
(or
(-> (get-payment client p)
:created_at
coerce/to-date)
(coerce/to-date (time/now))))))
(async/to-chan! (->> settlement
:entries
(filter #(= "CHARGE" (:type %)))
(map :payment_id)
(filter identity)
(set)
(take 20)
))
true
(fn [e]
(lc/with-context {:source "Square settlements loading "}
(log/warn "Error loading sales date details" e)
e)))
(->> (async/<!! (async/into [] output-chan))
(map halt-if-error)
sort
(drop 2)
first)))
(defn get-settlement-details [client location settlements] ;; pairs of [location settlement]
(log/info "getting settlement details for " settlements)
(let [concurrent 4
output-chan (async/chan)]
(async/pipeline-blocking concurrent
output-chan
(map (fn [s]
(lc/with-context {:source "Square settlements loading "}
(log/info "Looking up settlement " s " for location " (:square-location/client-location location))
(let [settlement (:body (retry #(client/get (str "https://connect.squareup.com/v1/" (:square-location/square-id location) "/settlements/" s)
{:headers (client-base-headers client)
:as :json
:retry-handler retry-4})))
sales-date (get-settlement-sales-date client settlement)]
(log/info "sales date for " s " is " sales-date)
(assoc settlement :sales-date sales-date)))))
(async/to-chan! settlements)
true
(fn [e]
(lc/with-context {:source "Square settlements loading "}
(log/warn "Error loading settlements details" e)
e)))
(->> (async/<!! (async/into [] output-chan))
(map halt-if-error))))
(defn settlements
([client location] (settlements client location (lookup-dates)))
([client location lookup-dates]
(log/info "Searching for" (:square-location/client-location location))
(->> lookup-dates
(mapcat (fn [[start-date end-date]]
(log/info "looking up settlements for " (:square-location/client-location location) " on dates " start-date " to " end-date)
(let [settlements (->> (retry #(client/get (str "https://connect.squareup.com/v1/" (:square-location/square-id location) "/settlements")
{:headers (client-base-headers client)
:query-params {"begin_time" start-date
"end_time" end-date}
:as :json
:retry-handler retry-4}))
:body
(map :id))]
settlements)))
set
seq
(get-settlement-details client location))))
(defn daily-settlements
([client location]
(->> (for [settlement (settlements client location)]
#:expected-deposit {:external-id (str "square/settlement/" (:id settlement))
:vendor :vendor/ccp-square
:status :expected-deposit-status/pending
:total (amount->money (:total_money settlement))
:client (:db/id client)
:location (:square-location/client-location location)
:fee (- (reduce + 0.0 (map (fn [entry]
(if (= (:type entry) "REFUND")
(- (amount->money (:fee_money entry)))
(amount->money (:fee_money entry))))
(:entries settlement))))
:date (-> (:initiated_at settlement)
(coerce/to-date))
:sales-date (or (:sales-date settlement)
(-> (:initiated_at settlement)
(coerce/to-date)))
:charges (->> (:entries settlement)
(filter :payment_id)
(map (fn [p] {:charge/external-id (str "square/charge/" (:payment_id p))})))})
(filter :expected-deposit/date))))
(defn refunds
([client l]
(let [refunds (:refunds (:body (client/get (str "https://connect.squareup.com/v2/refunds?location_id=" (:square-location/square-id l))
{:headers (client-base-headers client)
:as :json
:retry-handler retry-4})))]
(->> refunds
(filter (fn [r] (= "COMPLETED" (:status r))))
(map (fn [r]
#:sales-refund {:external-id (str "square/refund/" (:id r))
:vendor :vendor/ccp-square
:total (amount->money (:amount_money r))
:fee (transduce
(comp (filter #(= "ADJUSTMENT" (:type %)))
(map :amount_money)
(map amount->money))
+
0.0
(:processing_fee r))
:client (:db/id client)
:location (:square-location/client-location l)
:date (coerce/to-date (:created_at r))
:type (:source_type (get-payment client (:payment_id r)))}))))))
(defn upsert
([client ]
(doseq [square-location (:client/square-locations client)
:when (:square-location/client-location square-location)]
(upsert client square-location (time/plus (time/now) (time/days -45)) (time/now))))
([client location start end]
(lc/with-context {:source "Square loading"}
(doseq [x (partition-all 20 (daily-results client location start end))]
(log/info "Loading " (count x))
@(d/transact conn x)))))
(defn upsert-settlements
([client]
(doseq [square-location (:client/square-locations client)
:when (:square-location/client-location square-location)]
(upsert-settlements client square-location)))
([client location]
(lc/with-context {:source "Square settlements loading"
:client (:client/code client)}
(doseq [x (partition-all 20 (daily-settlements client location))]
(log/info "Loading expected deposit" (count x))
@(d/transact conn x))
(log/info "Done loading settlements"))))
(defn upsert-refunds
([client]
(doseq [square-location (:client/square-locations client)
:when (:square-location/client-location square-location)]
(upsert-refunds client square-location)))
([client location]
(lc/with-context {:source "Loading Square Settlements"
:client (:client/code client)
:location (:square-location/client-location client)}
(doseq [x (partition-all 20 (refunds client location))]
(log/info "Loading refund" (count x))
@(d/transact conn x))
(log/info "Done loading refunds"))))
(def square-read [:db/id
:client/code
:client/square-auth-token
{:client/square-locations [:db/id :square-location/name :square-location/square-id :square-location/client-location]}])
(defn get-square-clients
([]
(d/q '[:find [(pull ?c [:db/id
:client/square-integration-status
:client/code
:client/square-auth-token
{:client/square-locations [:db/id :square-location/name :square-location/square-id :square-location/client-location]}]) ...]
:in $
:where [?c :client/square-auth-token]
[?c :client/feature-flags "new-square"]]
(d/db conn)))
([ & codes]
(d/q '[:find [(pull ?c [:db/id
:client/code
:client/square-auth-token
{:client/square-locations [:db/id :square-location/name :square-location/square-id :square-location/client-location]}]) ...]
:in $ [?code ...]
:where [?c :client/square-auth-token]
[?c :client/feature-flags "new-square"]
[?c :client/code ?code]]
(d/db conn)
codes)))
(defn upsert-locations
([] (doseq [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)))]
(->> (for [square-location (client-locations client)]
{:db/id (or (square-id->id (:id square-location)) (d/tempid :db.part/user))
:client/_square-locations (:db/id client)
:square-location/name (:name square-location)
:square-location/square-id (:id square-location)})
(d/transact conn)
deref))))
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn reset []
(->>
(d/query {:query {:find ['?e]
:in ['$]
:where ['(or [?e :sales-order/date]
[?e :expected-deposit/date])]}
:args [(d/db conn)]})
(map first)
(map (fn [x] [:db/retractEntity x]))))
(defn mark-integration-status [client integration-status]
@(d/transact conn
[{:db/id (:db/id client)
:client/square-integration-status (assoc integration-status
:db/id (or (-> client :client/square-integration-status :db/id)
#db/id [:db.part/user]))}]))
(defn upsert-all [ & clients]
(doseq [client (apply get-square-clients clients)
:when (seq (filter :square-location/client-location (:client/square-locations client)))]
(lc/with-context {:client (:client/code client)}
(mark-integration-status client {:integration-status/last-attempt (coerce/to-date (time/now))})
(try+
(upsert-locations client)
(upsert client)
(upsert-settlements client)
(upsert-refunds client)
(mark-integration-status client {:integration-status/state :integration-state/success
:integration-status/last-updated (coerce/to-date (time/now))})
(catch [:status 401] data
(mark-integration-status client {:integration-status/state :integration-state/unauthorized
:integration-status/message (-> data :body str)}))
(catch [:status 503] data
(mark-integration-status client {:integration-status/state :integration-state/failed
:integration-status/message (-> data :body str)}))
(catch Object _
(log/warn &throw-context)
(mark-integration-status client {:integration-status/state :integration-state/failed
:integration-status/message (or (some-> (:wrapper &throw-context) (.getMessage ))
(some-> (:object &throw-context) str)
"Unknown error")}))))))

View File

@@ -498,8 +498,8 @@
(println "orders")
(lc/with-context {:source "Historical loading data"}
(doseq [d (per/periodic-seq (t/plus (t/today) (t/days (- days)))
(t/today)
(t/days 1))]
(t/today)
(t/days 1))]
(println d)
(square/upsert client square-location (c/to-date-time d) (c/to-date-time (t/plus d (t/days 1))))))

View File

@@ -24,6 +24,14 @@
[form-builder/builder {:submit-event [::saving ]
:id ::form}
[form-builder/section {:title "Sales Order"}
[:div
"Order " (:id data)
(when (:reference-link data)
[:a {:href (:reference-link data)
:target "_new"}
[:span.icon
[:i.fa.fa-external-link
]]])]
(when-not @(re-frame/subscribe [::subs/client])
[form-builder/field-v2 {:field :client}
"Client"
@@ -62,7 +70,12 @@
[:ul
(for [charge (:charges data)]
^{:key (:id charge)}
[:li (:type-name charge) ": " (:total charge)])]]
[:li [:span (:type-name charge) ": " (:total charge)
(when (:reference-link charge)
[:a {:href (:reference-link charge) :target "_new"}
[:span.icon
[:i.fa.fa-external-link
]]])]])]]
[form-builder/section {:title "Line Items"}
[:ul

View File

@@ -25,13 +25,14 @@
{:start (:start params 0)
:sort (:sort params)
:per-page (:per-page params)
:type-name (:type-name params)
:total-gte (:amount-gte (:total-range params))
:total-lte (:amount-lte (:total-range params))
:date-range (:date-range params)
:processor (some-> (:processor params) keyword)
:client-id (:id @(re-frame/subscribe [::subs/client]))}
[[:sales-orders [:id :total :tax :tip :discount :service-charge :returns :date
[:charges [:type-name :total :processor :id [:expected-deposit [:id]] ]]
[[:sales-orders [:id :source :total :tax :tip :reference-link :discount :service-charge :returns :date
[:charges [:type-name :note :reference-link :total :processor :id [:expected-deposit [:id]] ]]
[:line-items [:item-name :total :category]]
[:client [:name :id]]]]
:total

View File

@@ -39,9 +39,40 @@
(when (= :sales-orders ap)
[:<>
[:p.menu-label "Payment Method"]
[:div
[:nav.panel
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :type-name nil])}
[:span.panel-icon]
"All"]
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :type-name "CASH"])}
[:span.panel-icon
[:span {:class "icon-accounting-bill" :style {:font-weight "400"}}]]
"Cash"]
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :type-name "CARD"])}
[:span.panel-icon
[:span {:class "icon-credit-card-1" :style {:font-weight "400"}}]]
"Card"]
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :type-name "SQUARE_GIFT_CARD"])}
[:span.panel-icon
[:span {:class "icon-gift-box" :style {:font-weight "400"}}]]
"Gift Card"]
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :type-name "OTHER"])}
[:span.panel-icon ]
"Other"]]]
[:p.menu-label "Processor"]
[:div
[:nav.panel
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :processor nil])}
[:span.panel-icon]
"All"]
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :processor "square"])}
[:span.panel-icon [:img.level-item {:src "/img/square.png"}]]
"Square"]
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :processor "doordash"])}
[:span.panel-icon [:img.level-item {:src "/img/doordash.png"}]]
"Doordash"]
@@ -51,7 +82,17 @@
"Uber Eats"]
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :processor "grubhub"])}
[:span.panel-icon [:img.level-item {:src "/img/grubhub.png"}]]
"Grubhub"]]]])
"Grubhub"]
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :processor "koala"])}
[:span.panel-icon [:img.level-item {:src "/img/koala.png"}]]
"Koala"]
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :processor "ezcater"])}
[:span.panel-icon [:img.level-item {:src "/img/ezcater.png"}]]
"EZCater"]
[:a.panel-block {:on-click (dispatch-event [::data-page/filter-changed data-page :processor "na"])}
[:span.panel-icon #_[:img.level-item {:src "/img/grubhub.png"}]]
"No Processor"]
]]])
(when-let [exact-match-id @(re-frame/subscribe [::data-page/filter data-page :exact-match-id])]
[:div

View File

@@ -17,15 +17,36 @@
(defn row [{sales-order :sales-order
selected-client :selected-client}]
(let [{:keys [client date total tax tip charges line-items id]} sales-order
(let [{:keys [client date total tax tip charges source line-items id]} sales-order
expected-deposits (->> charges (filter :expected-deposit) (map :expected-deposit))]
[grid/row {:class (:class sales-order) :id id}
(when-not selected-client
[grid/cell {} (:name client)])
[grid/cell {} (date->str date)]
[grid/cell {} source]
[grid/cell {:class "has-text-right"} (nf total)]
[grid/cell {:class "has-text-right"} (nf tax)]
[grid/cell {:class "has-text-right"} (nf tip)]
[grid/cell {}
[:div.level-left
(for [charge charges]
(with-meta
(condp = (:type-name charge)
"CASH"
[:span.icon.level-item {:style {:font-size "24px"}} [:span {:class "icon-accounting-bill" :style {:font-weight "400"}}]]
"CARD"
[:span.icon.level-item {:style {:font-size "24px"}} [:span {:class "icon-credit-card-1" :style {:font-weight "400"}}]]
"SQUARE_GIFT_CARD"
[:span.icon.level-item {:style {:font-size "24px"}} [:span {:class "icon-gift-box" :style {:font-weight "400"}}]]
[:span.level-item [:span (:type-name charge) (when-let [note (:note charge)]
[:span
[:i.has-text-grey " (" note ")"]])] ])
{:key (:id charge)}))]]
[grid/cell {}
[:div.level
[:div.level-left
@@ -42,17 +63,15 @@
:uber-eats
[:img.level-item {:src "/img/ubereats.png" :style {:width "24px" :height "24px"}}]
(condp = (:type-name charge)
"CASH"
[:span.icon.level-item {:style {:font-size "24px"}} [:span {:class "icon-accounting-bill" :style {:font-weight "400"}}]]
:square
[:img.level-item {:src "/img/square.png" :style {:width "24px" :height "24px"}}]
"CARD"
[:span.icon.level-item {:style {:font-size "24px"}} [:span {:class "icon-credit-card-1" :style {:font-weight "400"}}]]
:koala
[:img.level-item {:src "/img/koala.png" :style {:width "24px" :height "24px"}}]
"SQUARE_GIFT_CARD"
[:span.icon.level-item {:style {:font-size "24px"}} [:span {:class "icon-gift-box" :style {:font-weight "400"}}]]
[:span.level-item "Other (" (:type-name charge) ")"]))
:ezcater
[:img.level-item {:src "/img/ezcater.png" :style {:width "24px" :height "24px"}}]
nil)
{:key (:id charge)}))]]]
@@ -105,10 +124,12 @@
(when-not selected-client
[grid/sortable-header-cell {:sort-key "client" :sort-name "Client"} "Client"])
[grid/sortable-header-cell {:sort-key "date" :sort-name "Date" :style {:width "8em"}} "Date"]
[grid/sortable-header-cell {:sort-key "source" :sort-name "Source"} "Source"]
[grid/sortable-header-cell {:sort-key "total" :sort-name "Total" :class "has-text-right" :style {:width "8em"}} "Total"]
[grid/sortable-header-cell {:sort-key "tax" :sort-name "Tax" :class "has-text-right" :style {:width "7em"}} "Tax"]
[grid/sortable-header-cell {:sort-key "tip" :sort-name "Tip" :class "has-text-right" :style {:width "7em"}} "Tip"]
[grid/header-cell {} "Payment Methods"]
[grid/header-cell {} "Processor"]
[grid/header-cell {} "Line Items"]
[grid/header-cell {:style {:width "8em"}}]]]
[grid/body