From 3a49ba971cba435a2a32d213b5e2442cfc670893 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Wed, 6 Apr 2022 08:22:52 -0700 Subject: [PATCH] Allows users to self set up square --- config/dev.edn | 4 +- src/clj/auto_ap/datomic/clients.clj | 4 + src/clj/auto_ap/datomic/migrate/sales.clj | 23 +- src/clj/auto_ap/graphql/clients.clj | 30 +- src/clj/auto_ap/square/core.clj | 279 +++++++++--------- src/clj/user.clj | 46 +-- src/cljs/auto_ap/events.cljs | 3 +- .../views/pages/admin/clients/form.cljs | 25 ++ src/cljs/auto_ap/views/utils.cljs | 128 ++++---- 9 files changed, 303 insertions(+), 239 deletions(-) diff --git a/config/dev.edn b/config/dev.edn index 07956c89..cc799234 100644 --- a/config/dev.edn +++ b/config/dev.edn @@ -115,6 +115,4 @@ :intuit {:client-id "ABBAQI0qeck149vEC1e8tV6b3YJNujOCdwsUMkJ1ZoptzumyYu" :client-secret "7DriIEend1K9RHlzhupIxPFQozXHELLfeFW2GfTR" - :redirect-uri "https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl"} - - } + :redirect-uri "https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl"}} diff --git a/src/clj/auto_ap/datomic/clients.clj b/src/clj/auto_ap/datomic/clients.clj index 1715effa..4be5c952 100644 --- a/src/clj/auto_ap/datomic/clients.clj +++ b/src/clj/auto_ap/datomic/clients.clj @@ -23,6 +23,10 @@ (defn get-all [] (->> (d/q '[:find (pull ?e [* {:client/address [*]} + {:client/square-locations [:square-location/square-id + :square-location/name + :square-location/client-location + :db/id]} {:client/bank-accounts [* {:bank-account/type [*] :bank-account/yodlee-account [:yodlee-account/name :yodlee-account/id :yodlee-account/number] :bank-account/plaid-account [:plaid-account/name :db/id :plaid-account/number :plaid-account/balance] diff --git a/src/clj/auto_ap/datomic/migrate/sales.clj b/src/clj/auto_ap/datomic/migrate/sales.clj index 195e9c6e..80ff4c87 100644 --- a/src/clj/auto_ap/datomic/migrate/sales.clj +++ b/src/clj/auto_ap/datomic/migrate/sales.clj @@ -250,5 +250,26 @@ :db/doc "The type of refund" :db/valueType :db.type/string :db/cardinality :db.cardinality/one}]] - :requires [:add-refunds]}}) + :requires [:add-refunds]} + :add-square-locations-3 {:txes [[{:db/ident :client/square-auth-token + :db/doc "A token that can be used to authenticate with square" + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one} + {:db/ident :client/square-locations + :db/doc "Locations in square" + :db/valueType :db.type/ref + :db/isComponent true + :db/cardinality :db.cardinality/many} + {:db/ident :square-location/square-id + :db/doc "An id for a location in square" + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one} + {:db/ident :square-location/name + :db/doc "Name of the location in square e.g. Woodland Hills" + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one} + {:db/ident :square-location/client-location + :db/doc "The client location that this location should match to. e.g. WH" + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one}]]}}) diff --git a/src/clj/auto_ap/graphql/clients.clj b/src/clj/auto_ap/graphql/clients.clj index 2ef7c5eb..c604ab5e 100644 --- a/src/clj/auto_ap/graphql/clients.clj +++ b/src/clj/auto_ap/graphql/clients.clj @@ -4,6 +4,7 @@ [auto-ap.graphql.utils :refer [->graphql assert-admin can-see-client? is-admin?]] [auto-ap.utils :refer [by]] [auto-ap.yodlee.core :refer [in-memory-cache]] + [auto-ap.square.core :as square] [com.walmartlabs.lacinia.util :refer [attach-resolvers]] [clj-time.coerce :as coerce] [config.core :refer [env]] @@ -14,7 +15,8 @@ [clojure.java.io :as io] [amazonica.aws.s3 :as s3] [yang.scheduler :as scheduler] - [mount.core :as mount]) + [mount.core :as mount] + [auto-ap.square.core :as square]) (:import [org.apache.commons.codec.binary Base64] java.util.UUID)) @@ -54,7 +56,7 @@ (> cnt 1))))) (throw (ex-info "Cannot reuse yodlee/plaid/intuit account" {:validation-error (str "Cannot reuse yodlee/plaid/intuit account")}))))) -(defn edit-client [context {:keys [edit_client new_bank_accounts] :as args} value] +(defn edit-client [context {:keys [edit_client]} _] (assert-admin (:id context)) (when-not (:id edit_client) (assert-client-code-is-unique (:code edit_client))) @@ -100,6 +102,13 @@ :client/week-a-debits (:week_a_debits edit_client) :client/week-a-credits (:week_a_credits edit_client) :client/week-b-debits (:week_b_debits edit_client) + :client/square-auth-token (:square_auth_token edit_client) + :client/square-locations (map + (fn [sl] + (remove-nils + {:db/id (:id sl) + :square-location/client-location (:client_location sl)})) + (:square_locations edit_client)) :client/week-b-credits (:week_b_credits edit_client) :client/location-matches (->> (:location_matches edit_client) (filter (fn [lm] (and (:location lm) (:match lm)))) @@ -153,7 +162,9 @@ _ (assert-no-shared-transaction-sources client-code transactions) _ (log/info "upserting client" transactions) result (audit-transact transactions (:id context))] - (-> result :tempids (get id) (or id) d-clients/get-by-id + (when (:square_auth_token edit_client) + (square/upsert-locations [(-> result :tempids (get id) (or id) d-clients/get-by-id)])) + (-> (-> result :tempids (get id) (or id) d-clients/get-by-id) (update :client/location-matches (fn [lms] (mapcat (fn [lm] @@ -273,6 +284,12 @@ :match {:type 'String} :id {:type :id}}} + :square_location + {:fields {:client_location {:type 'String} + :name {:type 'String} + :square_id {:type 'String} + :id {:type :id}}} + :email_contact {:fields {:id {:type :id} :email {:type 'String} :description {:type 'String}}} @@ -282,6 +299,7 @@ :name {:type 'String} :locked_until {:type :iso_date} :code {:type 'String} + :square_auth_token {:type 'String} :signature_file {:type 'String} :week_a_debits {:type :money} :week_a_credits {:type :money} @@ -294,6 +312,7 @@ :locations {:type '(list String)} :matches {:type '(list String)} :bank_accounts {:type '(list :bank_account)} + :square_locations {:type '(list :square_location)} :forecasted_transactions {:type '(list :forecasted_transaction)} :yodlee_provider_accounts {:type '(list :yodlee_provider_account)} :plaid_items {:type '(list :plaid_item)}}} @@ -342,6 +361,9 @@ :match {:type 'String} :id {:type :id}}} + :edit_square_location {:fields {:client_location {:type 'String} + :id {:type :id}}} + :edit_forecasted_transaction {:fields {:identifier {:type 'String} :id {:type :id} :day_of_month {:type 'Int} @@ -353,6 +375,7 @@ :name {:type 'String} :locked_until {:type :iso_date} :code {:type 'String} + :square_auth_token {:type 'String} :signature_data {:type 'String} :email {:type 'String} :emails {:type '(list :edit_email_contact)} @@ -364,6 +387,7 @@ :locations {:type '(list String)} :matches {:type '(list String)} :location_matches {:type '(list :edit_location_match)} + :square_locations {:type '(list :edit_square_location)} :bank_accounts {:type '(list :edit_bank_account)} :forecasted_transactions {:type '(list :edit_forecasted_transaction)}}} diff --git a/src/clj/auto_ap/square/core.clj b/src/clj/auto_ap/square/core.clj index 9e7144e3..ca630da3 100644 --- a/src/clj/auto_ap/square/core.clj +++ b/src/clj/auto_ap/square/core.clj @@ -7,7 +7,6 @@ [clj-time.core :as time] [clj-time.periodic :as periodic] [clj-time.format :as f] - [config.core :refer [env] :as cfg ] [clojure.string :as str] [clojure.data.json :as json] [clojure.tools.logging :as log] @@ -17,10 +16,14 @@ [yang.scheduler :as scheduler] [clojure.core.async :as async])) -(defn base-headers [client] +(defn client-base-headers [client] {"Square-Version" "2021-08-18" - "Authorization" (str "Bearer " (get-in env [:square-config client :token])) - "Content-Type" "application/json"}) + "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 -15)) @@ -38,12 +41,23 @@ :body :locations)) +(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/info "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 (base-headers client) + {:headers (client-base-headers client) :query-params {"include_related_items" "true"} :as :json}) :body @@ -71,28 +85,6 @@ (log/warn "couldn't look up" i) "Uncategorized")))) -(defn categories [] - (by :id (comp :name :category_data) )) - -(def potential-query - {"query" {"filter" {"date_time_filter" - { - "created_at" { - "start_at" "2020-08-28T00:00:00-07:00", - "end_at" "2020-08-28T23:59:59-07:00" - } - - - - } - "state_filter" {"states" ["COMPLETED"]}} - - "sort" { - "sort_field" "CREATED_AT" - "sort_order" "DESC" - }}}) - - (defn pc [d] {"query" {"filter" {"date_time_filter" { "created_at" { @@ -111,17 +103,14 @@ (defn search - ([client] - (search client (get-in env [:square-config client :square-location]))) - ([client l] - (search client l nil)) - ([client l d] - (log/info "Searching for" l) + ([client location d] + (log/info "Searching for" (:square-location/client-location location)) (let [result (->> (client/post "https://connect.squareup.com/v2/orders/search" - {:headers (base-headers client) - :body (json/write-str (cond-> {"location_ids" [l] "limit" 10000} - d (merge (pc d)))) - :as :json}) + (doto {:headers (client-base-headers client) + :body (json/write-str (cond-> {"location_ids" [(:square-location/square-id location)] "limit" 10000} + d (merge (pc d)))) + :as :json} + clojure.pprint/pprint)) :body :orders)] (log/info "found " (count result)) @@ -143,8 +132,6 @@ (defn amount->money [amt] (* 0.01 (or (:amount amt) 0.0))) -(defn location_id->client-location [location] - (get-in env [:square-location location])) ;; to get totals: (comment @@ -164,9 +151,9 @@ (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 [:client/code client] - :location location - :external-id (str "square/order/" client "-" location "-" (:id order)) + :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)) :vendor :vendor/ccp-square :total (-> order :net_amounts :total_money amount->money) :tax (-> order :net_amounts :tax_money amount->money) @@ -211,10 +198,8 @@ :discount (amount->money (:total_discount_money li))}))))})) (defn daily-results - ([client] - (daily-results client nil)) - ([client d] - (daily-results client (get-in env [:square-config client :square-location]) d)) + ([client location] + (daily-results client location nil)) ([client location d] (->> (search client location d) (filter (fn [order] @@ -228,7 +213,7 @@ (and (not= "Koala" (:name (:source order))) (not= "koala-production" (:name (:source order)))))) - (map #(order->sales-order client (get-in env [:square-config client :location]) %))))) + (map #(order->sales-order client location %))))) #_(daily-results) @@ -246,11 +231,9 @@ (defn get-payment [client p] (:payment (:body (retry #(client/get (str "https://connect.squareup.com/v2/payments/" p) - {:headers (base-headers client) + {:headers (client-base-headers client) :as :json - :retry-handler (fn [ex try-count http-context] - (log/warn "Retrying after failure " ex) - (if (> try-count 4) false true))}))))) + :retry-handler retry-4}))))) (defn get-settlement-sales-date [client settlement] (let [concurrent 10 @@ -282,24 +265,23 @@ (drop 2) first))) -(defn get-settlement-details [client settlements] ;; pairs of [location settlement] +(defn get-settlement-details [client location settlements] ;; pairs of [location settlement] (log/info "getting settlement details for " settlements) (let [concurrent 10 output-chan (async/chan)] (async/pipeline-blocking concurrent output-chan - (map (fn [[location s :as g]] + (map (fn [s] (lc/with-context {:source "Square settlements loading "} - (log/info "Looking up settlement " s " for location " location) - (let [settlement (:body (retry #(client/get (str "https://connect.squareup.com/v1/" location "/settlements/" s) - {:headers (base-headers client) + (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 (fn [ex try-count http-context] - (log/warn "Retrying after failure " ex) - (if (> try-count 4) false true))})))] + :retry-handler retry-4}))) + sales-date (get-settlement-sales-date client settlement)] - (log/info "sales date for " s " is " (get-settlement-sales-date client settlement)) - (assoc settlement :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] @@ -308,14 +290,14 @@ (async/> lookup-dates (mapcat (fn [[start-date end-date]] - (log/info "looking up settlements for " l " on dates " start-date " to " end-date) - (let [settlements (->> (client/get (str "https://connect.squareup.com/v1/" l "/settlements") - {:headers (base-headers client) + (log/info "looking up settlements for " (:square-location/client-location location) " on dates " start-date " to " end-date) + (let [settlements (->> (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}) @@ -324,22 +306,17 @@ settlements))) set seq - (map (fn [s] - [l s])) - (get-settlement-details client) - ))) + (get-settlement-details client location)))) (defn daily-settlements - ([client] (daily-settlements client - (get-in env [:square-config client :square-location]))) - ([client location-id] - (->> (for [settlement (settlements client location-id)] + ([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 [:client/code client] - :location (get-in env [:square-config client :location]) + :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))) @@ -354,49 +331,47 @@ (filter :expected-deposit/date)))) (defn refunds - ([client] - (refunds client (get-in env [:square-config client :square-location]))) ([client l] - (let [refunds (:refunds (:body (client/get (str "https://connect.squareup.com/v2/refunds?location_id=" l) - {:headers (base-headers client) - :as :json - :retry-handler (fn [ex try-count http-context] - (log/warn "Retrying after failure " ex) - (if (> try-count 4) false true))})))] - (->> 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 [:client/code client] - :location (get-in env [:square-config client :location]) - :date (coerce/to-date (:created_at r)) - :type (:source_type (get-payment client (:payment_id r)))})))))) + (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 ] - (upsert client nil)) - ([client d] + (doseq [square-location (:client/square-locations client) + :when (:square-location/client-location square-location)] + (upsert client square-location nil))) + ([client location d] (lc/with-context {:source "Square loading"} (try - (let [existing (->> (d/query {:query {:find ['?external-id] - :in ['$ '?client] - :where ['[?o :sales-order/client ?client] - '[?o :sales-order/external-id ?external-id]]} - :args [(d/db conn) [:client/code client]]}) + (let [existing (->> (d/query {:query {:find ['?external-id] + :in ['$ '?client] + :where ['[?o :sales-order/client ?client] + '[?o :sales-order/external-id ?external-id]]} + :args [(d/db conn) (:db/id client)]}) (map first) set) - _ (log/info (count existing) "Sales orders already exist") + _ (log/info (count existing) "Sales orders already exist") to-create (filter #(not (existing (:sales-order/external-id %))) - (daily-results client d))] + (daily-results client location d))] (doseq [x (partition-all 20 to-create)] (log/info "Loading " (count x)) @(d/transact conn x))) @@ -404,20 +379,23 @@ (log/error e)))))) (defn upsert-settlements - ([client] (upsert-settlements client (get-in env [:square-config client :square-location]))) - ([client location-id] + ([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"} (try (let [existing (->> (d/query {:query {:find ['?external-id] :in ['$ '?client] :where ['[?d :expected-deposit/client ?client] '[?d :expected-deposit/external-id ?external-id]]} - :args [(d/db conn) [:client/code client]]}) + :args [(d/db conn) (:db/id client)]}) (map first) set) _ (log/info (count existing) "settlements already exist") to-create (filter #(not (existing (:expected-deposit/external-id %))) - (daily-settlements client location-id))] + (daily-settlements client location))] (doseq [x (partition-all 20 to-create)] (log/info "Loading expected deposit" (count x)) @(d/transact conn x))) @@ -426,16 +404,20 @@ (log/info "Done loading settlements")))) (defn upsert-refunds - ([client] (upsert-refunds client (get-in env [:square-config client :square-location]))) + ([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} + :client (:client/code client) + :location (:square-location/client-location client)} (try (let [existing (->> (d/query {:query {:find ['?external-id] :in ['$ '?client] :where ['[?r :sales-refund/client ?client] '[?r :sales-refund/external-id ?external-id]]} - :args [(d/db conn) [:client/code client]]}) + :args [(d/db conn) (:db/id client)]}) (map first) set) _ (log/info (count existing) "refunds already exist") @@ -448,6 +430,37 @@ (log/error e))) (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/code + :client/square-auth-token + {:client/square-locations [:db/id :square-location/name :square-location/square-id :square-location/client-location]}]) ...] + :where [?c :client/square-auth-token]] + (d/db conn))) + +(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)))) + (defn reset [] (->> (d/query {:query {:find ['?e] @@ -458,17 +471,17 @@ (map (fn [x] [:db/retractEntity x])))) (defn upsert-all [] - (doseq [[client-code] (get-in env [:square-config]) - :when (d/entid (d/db conn) [:client/code client-code])] - (lc/with-context {:source (str "Square loading for " client-code) - :client client-code} + (doseq [client (get-square-clients) + :when (seq (filter :square-location/client-location (:client/square-locations client)))] + (lc/with-context {:source "Square loading" + :client (:client/code client)} + (upsert-locations client) (log/info "Loading Orders") - (upsert client-code) + (upsert client) (log/info "Loading Settlements") - (upsert-settlements client-code) + (upsert-settlements client) (log/info "Loading refunds") - (upsert-refunds client-code) - ))) + (upsert-refunds client)))) (mount/defstate square-loader :start (scheduler/every (* 4 59 60 1000) upsert-all) @@ -478,18 +491,4 @@ -(comment - (daily-results) - (mount/stop (mount/only #{'auto-ap.square.core/square-settlement-loader})) - - (mount/stop (mount/only #{'auto-ap.square.core/square-refund-loader})) - - (mount/stop (mount/only #{'auto-ap.square.core/square-loader})) - - (mount/start (mount/only #{'auto-ap.square.core/square-settlement-loader})) - (mount/start (mount/only #{'auto-ap.square.core/square-refund-loader})) - (mount/start (mount/only #{'auto-ap.square.core/square-loader})) - (do (upsert) nil) - - (do @(d/transact conn (reset)) nil)) diff --git a/src/clj/user.clj b/src/clj/user.clj index 9eb12a33..14410985 100644 --- a/src/clj/user.clj +++ b/src/clj/user.clj @@ -2,9 +2,11 @@ (:require [auto-ap.datomic :refer [uri]] [config.core :refer [env]] [auto-ap.utils :refer [by]] + [auto-ap.time :as atime] [clojure.core.async :as async] #_[auto-ap.ledger :as l] [unilog.context :as lc] + [auto-ap.square.core :as square] [mount.core :as mount] [auto-ap.server ] [datomic.api :as d] @@ -610,29 +612,35 @@ (defn historical-load-sales [client-code days] - (println "orders") - (lc/with-context {:source "Historical loading data"} - (doseq [d (clj-time.periodic/periodic-seq (t/plus (t/now) (t/days (- days))) - (t/now) - (t/days 1))] - (println d) - (auto-ap.square.core/upsert client-code d))) + (let [client (d/pull (d/db auto-ap.datomic/conn) + square/square-read + [:client/code client-code])] + (doseq [square-location (:client/square-locations client) + :when (:square-location/client-location square-location)] + + (println "orders") + (lc/with-context {:source "Historical loading data"} + (doseq [d (clj-time.periodic/periodic-seq (t/plus (t/now) (t/days (- days))) + (t/now) + (t/days 1))] + (println d) + (square/upsert client square-location d))) - (println "refunds") - (auto-ap.square.core/upsert-refunds client-code) + (println "refunds") + (square/upsert-refunds client square-location) - (println "settlements") - (with-redefs [auto-ap.square.core/lookup-dates (fn lookup-dates [] - (->> (clj-time.periodic/periodic-seq (t/plus (t/now) (t/days (- days))) - (t/now) - (t/days 2)) - (map (fn [d] - [(auto-ap.time/unparse (t/plus d (t/days 1)) auto-ap.time/iso-date) + (println "settlements") + (with-redefs [square/lookup-dates (fn lookup-dates [] + (->> (clj-time.periodic/periodic-seq (t/plus (t/now) (t/days (- days))) + (t/now) + (t/days 2)) + (map (fn [d] + [(atime/unparse (t/plus d (t/days 1)) atime/iso-date) - (auto-ap.time/unparse (t/plus d (t/days 2)) auto-ap.time/iso-date)]))))] + (atime/unparse (t/plus d (t/days 2)) atime/iso-date)]))))] - (auto-ap.square.core/upsert-settlements client-code))) + (square/upsert-settlements client square-location))))) (defn upsert-invoice-amounts [tsv] (let [data (with-open [reader (io/reader (char-array tsv))] @@ -668,7 +676,7 @@ current-account-id (:db/id (:invoice-expense-account/account invoice-expense-account)) target-account-id (Long/parseLong (str/trim target-account)) - target-date (clj-time.coerce/to-date (auto-ap.time/parse target-date auto-ap.time/normal-date)) + target-date (clj-time.coerce/to-date (atime/parse target-date atime/normal-date)) current-date (:invoice/date invoice) diff --git a/src/cljs/auto_ap/events.cljs b/src/cljs/auto_ap/events.cljs index ff07c65a..1cf591f4 100644 --- a/src/cljs/auto_ap/events.cljs +++ b/src/cljs/auto_ap/events.cljs @@ -31,7 +31,8 @@ [:address [:street1 :street2 :city :state :zip]]]) (defn client-query [token] - (cond-> [:id :name :signature-file :code :email :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits :locations :locked-until + (cond-> [:id :name :signature-file :code :email :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits :locations :locked-until :square-auth-token + [:square-locations [:square-id :id :name :client-location]] [:emails [:id :email :description]] [:location-matches [:id :location :match]] [:bank-accounts [:id :start-date :numeric-code :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id diff --git a/src/cljs/auto_ap/views/pages/admin/clients/form.cljs b/src/cljs/auto_ap/views/pages/admin/clients/form.cljs index 921f1247..d4f76ebc 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients/form.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients/form.cljs @@ -108,6 +108,12 @@ :name (:name new-client-data) :code (:code new-client-data) ;; TODO add validation can't change :emails (:emails new-client-data) + :square-auth-token (:square-auth-token new-client-data) + :square-locations (map + (fn [x] + {:id (:id x) + :client-location (:client-location x)}) + (:square-locations new-client-data)) :locked-until (cond (not (:locked-until new-client-data)) nil @@ -654,5 +660,24 @@ :style {:width "7em"} :field [:amount] :step "0.01"}]]}])]] + (field "Square Authentication Token" + [:input.input {:type "text" + :field [:square-auth-token]}]) + [:div.field + [:label.label "Square Locations"] + [:div.control + (raw-field + [multi-field {:type "multi-field" + :field :square-locations + :template [[:input.input {:type "text" + :style {:width "15em"} + :disabled true + :field [:name]}] + [:input.input {:type "text" + :style {:width "4em"} + :field [:client-location] + :step "0.01"}]] + :disable-remove? true + :disable-new? true}])]] (error-notification) (submit-button "Save")])]])) diff --git a/src/cljs/auto_ap/views/utils.cljs b/src/cljs/auto_ap/views/utils.cljs index bf43fdab..051135a3 100644 --- a/src/cljs/auto_ap/views/utils.cljs +++ b/src/cljs/auto_ap/views/utils.cljs @@ -1,22 +1,22 @@ (ns auto-ap.views.utils - (:require [re-frame.core :as re-frame] - [reagent.core :as reagent] - [react-transition-group :as react-transition-group] - [react-datepicker] - [clojure.spec.alpha :as s] - [cljs-time.coerce :as c] - [cljs-time.core :as time] - [auto-ap.subs :as subs] - [cljs-time.format :as format] - [goog.i18n.NumberFormat.Format] - [cljs-time.core :as t] - [clojure.string :as str] - [goog.crypt.base64 :as base64] - [cljs.tools.reader.edn :as edn] - [reagent.core :as r]) + (:require + [cemerick.url] + [cljs-time.coerce :as c] + [cljs-time.core :as t] + [cljs-time.format :as format] + [cljs.tools.reader.edn :as edn] + [clojure.spec.alpha :as s] + [clojure.string :as str] + [goog.crypt.base64 :as base64] + [re-frame.core :as re-frame] + [react-transition-group :as react-transition-group] + #_{:clj-kondo/ignore [:unused-namespace]} + [react-datepicker :as react-datepicker] + [reagent.core :as reagent]) (:import (goog.i18n NumberFormat) (goog.i18n.NumberFormat Format))) + (def nff (NumberFormat. Format/CURRENCY)) @@ -27,8 +27,6 @@ (defn ->$ [x] (nf x)) - - (defn- nf% [num] (.format (doto @@ -59,7 +57,7 @@ (defn delayed-dispatch [e] - (fn [x] + (fn [_] (js/setTimeout #(re-frame/dispatch e) 151) false)) @@ -72,7 +70,7 @@ (re-frame/dispatch-sync event))) (defn dispatch-event-with-propagation [event] - (fn [e] + (fn [_] (re-frame/dispatch-sync event))) (def pretty-long (format/formatter "MM/dd/yyyy HH:mm:ss")) @@ -94,23 +92,23 @@ (format/parse f d))) (defn dispatch-date-change [event] - (fn [e g] + (fn [e] (re-frame/dispatch (conj event (if (str/blank? e) e - (date->str (time/from-default-time-zone (c/from-date e)) standard)))))) + (date->str (t/from-default-time-zone (c/from-date e)) standard)))))) (defn dispatch-cljs-date-change [event] - (fn [e g] + (fn [e] (re-frame/dispatch (conj event (if (str/blank? e) e - (time/from-default-time-zone (c/from-date e))))))) + (c/to-local-date e)))))) ;; TODO inline on-changes causes each field to be rerendered each time. When we fix this ;; let's make sure that we find away not to trigger a re-render for every component any time any form field ;; changes -(defmulti do-bind (fn [a {:keys [type] :as x}] +(defmulti do-bind (fn [_ {:keys [type]}] type)) (defn with-keys [children] @@ -121,7 +119,7 @@ (reagent/adapt-react-class react-transition-group/CSSTransition)) -(defn appearing [{:keys [visible? enter-class exit-class timeout]} & children ] +(defn appearing [{:keys [visible? enter-class exit-class timeout]}] (let [final-state (reagent/atom visible?)] (fn [{:keys [visible?]} & children] [css-transition-group {:in visible? :class-names {:exit exit-class :enter enter-class} :timeout timeout :onEnter (fn [] (reset! final-state true )) :onExited (fn [] (reset! final-state false))} @@ -130,15 +128,15 @@ [:span])]))) -(defn multi-field [{:keys [change-event data value template on-change allow-change?]} ] - (let [value-repr (r/atom (mapv +(defn multi-field [{:keys [value]} ] + (let [value-repr (reagent/atom (mapv (fn [x] (assoc x :key (random-uuid) :new? false)) value))] - (fn [{:keys [change-event data value template on-change allow-change?]} ] + (fn [{:keys [template on-change allow-change? disable-new? disable-remove?]} ] (let [value @value-repr already-has-new-row? (= [:key :new?] (keys (last value))) - value (if already-has-new-row? + value (if (or already-has-new-row? disable-new?) value (conj value {:key (random-uuid) :new? true}))] @@ -153,7 +151,7 @@ [:div.level-item (if (:new? override) - [:div.icon.is-medium {:class (if (not= i (dec (count value))) + [:div.icon.is-medium {:class (when (not= i (dec (count value))) "has-text-info")} [:i.fa.fa-plus]] [:div.icon.is-medium])] @@ -169,7 +167,7 @@ (if (= value {}) nil value)) - :disabled is-disabled? + :disabled (or is-disabled? (get-in template [1 :disabled])) :on-change (fn [e] (reset! value-repr (into [] @@ -184,29 +182,27 @@ (dissoc v :new? :key)) @value-repr))))]) ] - [:div.level-item - [:a.button.level-item - - {:disabled is-disabled? - :on-click (fn [] + (when-not disable-remove? + [:div.level-item + [:a.button.level-item + {:disabled is-disabled? + :on-click (fn [] + (when-not is-disabled? + (reset! value-repr (into [] + (filter (fn [{:keys [key ]}] + (not= key (:key override))) + (filter (fn [r] + (not= [:key :new?] (keys r))) + value)))) - (when-not is-disabled? - (reset! value-repr (into [] - - (filter (fn [{:keys [key ] :as v}] - (not= key (:key override))) - (filter (fn [r] - (not= [:key :new?] (keys r))) - value)))) - - (on-change (mapv - (fn [v] - (dissoc v :new? :key)) - @value-repr))))} - [:span.icon [:span.icon-remove]]]] + (on-change (mapv + (fn [v] + (dissoc v :new? :key)) + @value-repr))))} + [:span.icon [:span.icon-remove]]]]) ]])])))) -(defmethod do-bind "select" [dom {:keys [field allow-nil? subscription event class value spec] :as keys} & rest] +(defmethod do-bind "select" [dom {:keys [field allow-nil? subscription event class spec] :as keys} & rest] (let [field (if (keyword? field) [field] field) event (if (keyword? event) [event] event) keys (assoc keys @@ -235,7 +231,7 @@ keys (dissoc keys :field :subscription :event :spec)] (into [dom keys] (with-keys rest)))) -(defmethod do-bind "checkbox" [dom {:keys [field subscription event class value spec] :as keys} & rest] +(defmethod do-bind "checkbox" [dom {:keys [field subscription event class spec] :as keys} & rest] (let [field (if (keyword? field) [field] field) event (if (keyword? event) [event] event) keys (assoc keys @@ -253,7 +249,7 @@ (let [field (if (keyword? field) [field] field) event (if (keyword? event) [event] event) keys (assoc keys - :on-change (fn [selected text-description text-value] + :on-change (fn [selected text-value] (re-frame/dispatch (conj (conj event field) selected)) (when text-field (re-frame/dispatch (conj (conj (or text-event event) text-field) text-value)))) @@ -278,7 +274,7 @@ (into [dom keys] (with-keys rest)))) -(defmethod do-bind "typeahead-entity" [dom {:keys [field event text-event subscription class spec match->text] :as keys} & rest] +(defmethod do-bind "typeahead-entity" [dom {:keys [field event subscription class spec] :as keys} & rest] (let [field (if (keyword? field) [field] field) event (if (keyword? event) [event] event) keys (assoc keys @@ -293,7 +289,7 @@ keys (dissoc keys :field :subscription :event :spec)] (into [dom keys] (with-keys rest)))) -(defmethod do-bind "typeahead-v3" [dom {:keys [field event text-event subscription class spec match->text] :as keys} & rest] +(defmethod do-bind "typeahead-v3" [dom {:keys [field event subscription class spec] :as keys} & rest] (let [field (if (keyword? field) [field] field) event (if (keyword? event) [event] event) keys (assoc keys @@ -314,10 +310,10 @@ selected (get-in subscription field) selected (cond (string? selected) - (c/to-date (time/to-default-time-zone (time/from-default-time-zone (str->date selected standard)))) + (c/to-date (t/to-default-time-zone (t/from-default-time-zone (str->date selected standard)))) (instance? goog.date.DateTime selected) - (c/to-date (time/to-default-time-zone (time/from-default-time-zone selected))) + (c/to-date (t/to-default-time-zone (t/from-default-time-zone selected))) (instance? goog.date.Date selected) (c/to-date selected) @@ -361,7 +357,7 @@ keys (dissoc keys :field :event :subscription :spec)] (into [dom keys] (with-keys rest)))) -(defmethod do-bind "number" [dom {:keys [field precision event subscription class spec] :as keys :or {precision 2}} & rest] +(defmethod do-bind "number" [dom {:keys [field event subscription class spec] :as keys} & rest] (let [field (if (keyword? field) [field] field) event (if (keyword? event) [event] event) keys (assoc keys @@ -380,14 +376,13 @@ :else val)))))) :value (get-in subscription field) - :class (str class (when (and spec (not (s/valid? spec (get-in subscription field)))) " is-danger"))) keys (dissoc keys :field :subscription :event :spec)] (into [dom keys] (with-keys rest)))) -(defmethod do-bind "textarea->table" [dom {:keys [field event subscription class spec] :as keys :or {precision 2}} & rest] +(defmethod do-bind "textarea->table" [dom {:keys [field event subscription class spec] :as keys} & rest] (let [field (if (keyword? field) [field] field) event (if (keyword? event) [event] event) keys (assoc keys @@ -396,7 +391,6 @@ (conj field) (conj x)))) :value (get-in subscription field) - :class (str class (when (and spec (not (s/valid? spec (get-in subscription field)))) " is-danger"))) @@ -446,8 +440,7 @@ (with-keys (map (fn [x] [:div.field x]) controls)))]) (def date-picker - (do - (reagent/adapt-react-class (.-default react-datepicker)))) + (reagent/adapt-react-class (.-default react-datepicker))) (defn date-picker-friendly [params] [date-picker (assoc params @@ -506,15 +499,6 @@ {} (:query (cemerick.url/url (.-location js/window))))) -(defn loading [db] - (-> db - (assoc-in [:status] :loading) - (assoc-in [:error] nil))) - -(defn triggers-loading [form] - (re-frame/enrich - (fn [db event] - (loading db)))) (defn action-cell-width [cnt] (str (inc (* cnt 51)) "px"))