Files
integreat/src/clj/auto_ap/ezcater/core.clj

347 lines
19 KiB
Clojure

(ns auto-ap.ezcater.core
(:require
[auto-ap.datomic :refer [conn]]
[datomic.api :as d]
[clj-http.client :as client]
[venia.core :as v]
[clojure.string :as str]
[clojure.data.json :as json]
[clj-time.coerce :as coerce]
[clojure.tools.logging :as log]
[clj-time.core :as time]
[clojure.set :as set]
[auto-ap.time :as atime]
[auto-ap.logging :as alog]
[cemerick.url :as url]))
(defn query [{:ezcater-integration/keys [api-key]} q]
(-> (client/post "https://api.ezcater.com/graphql/"
{:headers {"Authorization" api-key
"Content-Type" "application/json"}
:body (json/write-str {"query" (v/graphql-query q)})
:as :json})
:body
:data
))
(defn get-caterers [integration]
(:caterers (query integration {:venia/queries [{:query/data
[:caterers [:name :uuid [:address [:name :street]]]]}]} )))
(defn get-subscriptions [integration]
(->> (query integration {:venia/queries [{:query/data
[:subscribers [:id [:subscriptions [:parentId :parentEntity :eventEntity :eventKey]] ]]}]} )
:subscribers
first
:subscriptions))
(defn get-integrations []
(d/q '[:find [(pull ?i [:ezcater-integration/api-key
:ezcater-integration/subscriber-uuid
:db/id
:ezcater-integration/integration-status [:db/id]]) ...]
:in $
:where [?i :ezcater-integration/api-key]]
(d/db conn)))
(defn mark-integration-status [integration integration-status]
@(d/transact conn
[{:db/id (:db/id integration)
:ezcater-integration/integration-status (assoc integration-status
:db/id (or (-> integration :ezcater-integration/integration-status :db/id)
#db/id [:db.part/user]))}]))
(defn upsert-caterers
([integration]
@(d/transact
conn
(for [caterer (get-caterers integration)]
{:db/id (:db/id integration)
:ezcater-integration/caterers [{:ezcater-caterer/name (str (:name caterer) " (" (:street (:address caterer)) ")")
:ezcater-caterer/search-terms (str (:name caterer) " " (:street (:address caterer)))
:ezcater-caterer/uuid (:uuid caterer)}]}))))
(defn upsert-used-subscriptions
([integration]
(let [extant (get-subscriptions integration)
to-ensure (set (d/q '[:find [?cu ...]
:in $
:where [_ :client/ezcater-locations ?el]
[?el :ezcater-location/caterer ?c]
[?c :ezcater-caterer/uuid ?cu]]
(d/db conn)))
to-create (set/difference
to-ensure
(set (map :parentId extant)))]
(doseq [parentId to-create]
(query integration
{:venia/operation {:operation/type :mutation
:operation/name "createSubscription"}
:venia/queries [[:createSubscription
{:subscriptionParams {:subscriberId (:ezcater-integration/subscriber-uuid integration)
:parentEntity 'Caterer
:parentId parentId
:eventEntity 'Order
:eventKey 'accepted}}
[[:subscription [:parentId :parentEntity :eventEntity :eventKey]]]]]})
(query integration
{:venia/operation {:operation/type :mutation
:operation/name "createSubscription"}
:venia/queries [[:createSubscription
{:subscriptionParams {:subscriberId (:ezcater-integration/subscriber-uuid integration)
:parentEntity 'Caterer
:parentId parentId
:eventEntity 'Order
:eventKey 'cancelled}}
[[:subscription [:parentId :parentEntity :eventEntity :eventKey]]]]]})))))
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn upsert-ezcater
([] (upsert-ezcater (get-integrations)))
([integrations]
(doseq [integration integrations]
(mark-integration-status integration {:integration-status/last-attempt (coerce/to-date (time/now))})
(try
(upsert-caterers integration)
(upsert-used-subscriptions integration)
(mark-integration-status integration {:integration-status/state :integration-state/success
:integration-status/last-updated (coerce/to-date (time/now))})
(catch Exception e
(log/warn e)
(mark-integration-status integration {:integration-status/state :integration-state/failed
:integration-status/message (.getMessage e)}))))))
(defn get-caterer [caterer-uuid]
(d/pull (d/db conn)
'[:ezcater-caterer/name
{:ezcater-integration/_caterers [:ezcater-integration/api-key]}
{:ezcater-location/_caterer [:ezcater-location/location
{:client/_ezcater-locations [:client/code]}]}]
[: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% (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]
(round-carry-cents
(* 0.000299M
(+
(-> 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}]
(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 :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)]
(if (and client 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))
(#(alog/info ::order-details :detail %)))
(alog/warn ::caterer-no-longer-has-location :json json))))
(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"}
(alog/info
::try-import-order
:json json)
@(d/transact conn [(some-> json
(lookup-order)
(order->sales-order)
(update :sales-order/date coerce/to-date)
(update-in [:sales-order/charges 0 :charge/date] coerce/to-date))]))
(defn upsert-recent []
(let [last-sunday (coerce/to-date (time/plus (second (->> (time/today)
(iterate #(time/plus % (time/days -1)))
(filter #(= 7 (time/day-of-week %)))))
(time/days 1)))
orders-to-update (doall (for [[order uuid] (d/q '[:find ?eid ?uuid
:in $ ?start
:where [?e :sales-order/vendor :vendor/ccp-ezcater]
[?e :sales-order/date ?d]
[(>= ?d ?start)]
[?e :sales-order/external-id ?eid]
[?e :sales-order/client ?c]
[?c :client/ezcater-locations ?l]
[?l :ezcater-location/caterer ?c2]
[?c2 :ezcater-caterer/uuid ?uuid]]
(d/db conn)
last-sunday)
:let [_ (log/info "Considering updating " order)
id (last (str/split order #"/"))
id (str/join "-" (drop 2 (str/split order #"-")))
lookup-map {"id" "bf3dcf5c-a68f-42d9-9084-049133e03d3d",
"parent_type" "Caterer",
"parent_id" uuid,
"entity_type" "Order",
"entity_id" id,
"key" "accepted",
"occurred_at" "2022-07-21T19:21:07.549Z"}
ezcater-order (lookup-order lookup-map)
extant-order (d/pull (d/db conn) '[:sales-order/total
:sales-order/tax
:sales-order/tip
:sales-order/discount
:sales-order/external-id
{:sales-order/charges [:charge/tax
:charge/tip
:charge/total
:charge/external-id]
:sales-order/line-items [:order-line-item/external-id
:order-line-item/total
:order-line-item/tax
:order-line-item/discount]}]
[:sales-order/external-id order])
updated-order (-> (order->sales-order ezcater-order)
(select-keys
#{:sales-order/total
:sales-order/tax
:sales-order/tip
:sales-order/discount
:sales-order/charges
:sales-order/external-id
:sales-order/line-items})
(update :sales-order/line-items
(fn [c]
(map #(select-keys % #{:order-line-item/external-id
:order-line-item/total
:order-line-item/tax
:order-line-item/discount}) c)))
(update :sales-order/charges (fn [c]
(map #(select-keys % #{:charge/tax :charge/tip :charge/total
:charge/external-id}) c))))]
:when (not= updated-order extant-order)]
updated-order))]
(log/info "Found these orders to update:" orders-to-update)
@(d/transact conn orders-to-update)))