Allows users to self set up square

This commit is contained in:
2022-04-06 08:22:52 -07:00
parent 982a0f3bed
commit 3a49ba971c
9 changed files with 303 additions and 239 deletions

View File

@@ -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"}}

View File

@@ -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]

View File

@@ -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}]]}})

View File

@@ -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)}}}

View File

@@ -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/<!! (async/into [] output-chan))))
(defn settlements
([client l] (settlements client l (lookup-dates)))
([client l lookup-dates]
(log/info "Searching for" l)
([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 " 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))

View File

@@ -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)

View File

@@ -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

View File

@@ -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")])]]))

View File

@@ -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"))