beginnings of better import.
This commit is contained in:
@@ -108,4 +108,9 @@
|
||||
{:square-location "L2579ATQ0X1ET",
|
||||
:location "WG",
|
||||
:token "EAAAEEr749Ea6AdPTdngsmUPwIM3ETbPwcx3QQl_NS0KWuIL-JNzAg4f3W9DGQhb"}}
|
||||
|
||||
:intuit {:client-id "ABBAQI0qeck149vEC1e8tV6b3YJNujOCdwsUMkJ1ZoptzumyYu"
|
||||
:client-secret "7DriIEend1K9RHlzhupIxPFQozXHELLfeFW2GfTR"
|
||||
:redirect-uri "https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl"}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
(->> (d/q '[:find (pull ?e [*
|
||||
{:client/address [*]}
|
||||
{:client/bank-accounts [* {:bank-account/type [*]
|
||||
:bank-account/yodlee-account [:yodlee-account/name :yodlee-account/id :yodlee-account/number]}]}
|
||||
:bank-account/yodlee-account [:yodlee-account/name :yodlee-account/id :yodlee-account/number]
|
||||
:bank-account/intuit-bank-account [:intuit-bank-account/name :intuit-bank-account/external-id :db/id]}
|
||||
]}
|
||||
{:yodlee-provider-account/_client [*]}])
|
||||
:where [?e :client/name]]
|
||||
(d/db (d/connect uri)))
|
||||
@@ -35,7 +37,8 @@
|
||||
(->>
|
||||
(d/pull (d/db conn )
|
||||
'[* {:client/bank-accounts [* {:bank-account/type [*]
|
||||
:bank-account/yodlee-account [:yodlee-account/name :yodlee-account/id :yodlee-account/number]}]}
|
||||
:bank-account/yodlee-account [:yodlee-account/name :yodlee-account/id :yodlee-account/number]
|
||||
:bank-account/intuit-bank-account [:intuit-bank-account/name :intuit-bank-account/external-id :db/id]}]}
|
||||
{:yodlee-provider-account/_client [*]}]
|
||||
id)
|
||||
(cleanse)))
|
||||
|
||||
@@ -359,7 +359,26 @@
|
||||
:depends-on [:auto-ap/base-schema]}
|
||||
|
||||
:auto-ap/add-propose-invoice {:txes-fn `propose-invoice-fn
|
||||
:depends-on [:auto-ap/base-schema]}}
|
||||
:depends-on [:auto-ap/base-schema]}
|
||||
:auto-ap/add-intuit-banks-4 {:txes [[{:db/ident :intuit-bank-account/external-id
|
||||
:db/doc "Id of the intui bank"
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/unique :db.unique/identity}
|
||||
{:db/ident :intuit-bank-account/name
|
||||
:db/doc "Name of intuit bank"
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/unique :db.unique/identity}
|
||||
{:db/ident :bank-account/intuit-bank-account
|
||||
:db/doc "intuit external bank account"
|
||||
:db/valueType :db.type/ref
|
||||
:db/cardinality :db.cardinality/one}
|
||||
{:db/ident :transaction/raw-id
|
||||
:db/doc "An unhashed version of the id"
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one}]]
|
||||
:depends-on [:auto-ap/base-schema]}}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[auto-ap.graphql.invoices :as gq-invoices]
|
||||
[auto-ap.graphql.ledger :as gq-ledger]
|
||||
[auto-ap.graphql.sales-orders :as gq-sales-orders]
|
||||
[auto-ap.graphql.intuit-bank-accounts :as gq-intuit-bank-accounts]
|
||||
[auto-ap.graphql.transaction-rules :as gq-transaction-rules]
|
||||
[auto-ap.graphql.transactions :as gq-transactions]
|
||||
[auto-ap.graphql.users :as gq-users]
|
||||
@@ -144,6 +145,7 @@
|
||||
:yodlee_balance_old {:type :money}
|
||||
:yodlee_account_id {:type 'Int}
|
||||
:yodlee_account {:type :yodlee_account}
|
||||
:intuit_bank_account {:type :intuit_bank_account}
|
||||
:locations {:type '(list String)}}}
|
||||
:forecasted_transaction {:fields {:identifier {:type 'String}
|
||||
:id {:type :id}
|
||||
@@ -326,6 +328,10 @@
|
||||
:yodlee_id {:type 'String}
|
||||
:name {:type 'String}}}
|
||||
|
||||
:intuit_bank_account {:fields {:id {:type :id}
|
||||
:external_id {:type 'String}
|
||||
:name {:type 'String}}}
|
||||
|
||||
:forecast_match {:fields {:id {:type :id}
|
||||
:identifier {:type 'String}}}
|
||||
:transaction {:fields {:id {:type :id}
|
||||
@@ -617,6 +623,10 @@
|
||||
:yodlee_merchants {:type '(list :yodlee_merchant)
|
||||
:args {}
|
||||
:resolve :get-yodlee-merchants}
|
||||
|
||||
:intuit_bank_accounts {:type '(list :intuit_bank_account)
|
||||
:args {}
|
||||
:resolve :get-intuit-bank-accounts}
|
||||
|
||||
:transaction_page {:type :transaction_page
|
||||
:args {:filters {:type :transaction_filters}}
|
||||
@@ -788,6 +798,7 @@
|
||||
:bank_name {:type 'String}
|
||||
:locations {:type '(list String)}
|
||||
:yodlee_account_id {:type 'Int}
|
||||
:intuit_bank_account {:type :id}
|
||||
:yodlee_account {:type 'Int}}}
|
||||
:edit_user
|
||||
{:fields {:id {:type :id}
|
||||
@@ -1327,6 +1338,7 @@
|
||||
:get-invoice-stats get-invoice-stats
|
||||
:get-cash-flow get-cash-flow
|
||||
:get-yodlee-merchants ym/get-yodlee-merchants
|
||||
:get-intuit-bank-accounts gq-intuit-bank-accounts/get-intuit-bank-accounts
|
||||
:get-client gq-clients/get-client
|
||||
:get-user get-user
|
||||
:mutation/add-handwritten-check gq-checks/add-handwritten-check
|
||||
|
||||
@@ -52,15 +52,20 @@
|
||||
(mapv (fn [lm] [:db/retractEntity (:db/id lm)]) (:client/location-matches client))
|
||||
(mapv (fn [m] [:db/retract (:db/id client) :client/matches m]) (:client/matches client)))
|
||||
(:id context)))
|
||||
reverts (into (->> (:bank_accounts edit_client)
|
||||
(filter #(nil? (:yodlee_account_id %)))
|
||||
(filter #(:bank-account/yodlee-account-id (d/entity (d/db conn) (:id %))))
|
||||
(map (fn [ba] [:db/retract (:id ba) :bank-account/yodlee-account-id (:bank-account/yodlee-account-id (d/entity (d/db conn) (:id ba)))])))
|
||||
|
||||
(->> (:bank_accounts edit_client)
|
||||
(filter #(nil? (:yodlee_account %)))
|
||||
(filter #(:bank-account/yodlee-account (d/entity (d/db conn) (:id %))))
|
||||
(map (fn [ba] [:db/retract (:id ba) :bank-account/yodlee-account (:db/id (:bank-account/yodlee-account (d/entity (d/db conn) (:id ba))))]))))
|
||||
reverts (-> []
|
||||
(into (->> (:bank_accounts edit_client)
|
||||
(filter #(nil? (:yodlee_account_id %)))
|
||||
(filter #(:bank-account/yodlee-account-id (d/entity (d/db conn) (:id %))))
|
||||
(map (fn [ba] [:db/retract (:id ba) :bank-account/yodlee-account-id (:bank-account/yodlee-account-id (d/entity (d/db conn) (:id ba)))]))))
|
||||
(into (->> (:bank_accounts edit_client)
|
||||
(filter #(nil? (:yodlee_account %)))
|
||||
(filter #(:bank-account/yodlee-account (d/entity (d/db conn) (:id %))))
|
||||
(map (fn [ba] [:db/retract (:id ba) :bank-account/yodlee-account (:db/id (:bank-account/yodlee-account (d/entity (d/db conn) (:id ba))))]))))
|
||||
(into (->> (:bank_accounts edit_client)
|
||||
(filter #(nil? (:intuit_bank_account %)))
|
||||
(filter #(:bank-account/intuit-bank-account (d/entity (d/db conn) (:id %))))
|
||||
(map (fn [ba] [:db/retract (:id ba) :bank-account/intuit-bank-account (:db/id (:bank-account/intuit-bank-account (d/entity (d/db conn) (:id ba))))])))))
|
||||
|
||||
|
||||
transactions (into [(remove-nils {:db/id id
|
||||
:client/code (if (str/blank? (:client/code client))
|
||||
@@ -104,7 +109,8 @@
|
||||
|
||||
:bank-account/yodlee-account-id (:yodlee_account_id %)
|
||||
:bank-account/type (keyword "bank-account-type" (name (:type %)))}
|
||||
(:yodlee_account %) (assoc :bank-account/yodlee-account [:yodlee-account/id (:yodlee_account %)])))
|
||||
(:yodlee_account %) (assoc :bank-account/yodlee-account [:yodlee-account/id (:yodlee_account %)])
|
||||
(:intuit_bank_account %) (assoc :bank-account/intuit-bank-account (:intuit_bank_account %))))
|
||||
(:bank_accounts edit_client))
|
||||
|
||||
})
|
||||
|
||||
11
src/clj/auto_ap/graphql/intuit_bank_accounts.clj
Normal file
11
src/clj/auto_ap/graphql/intuit_bank_accounts.clj
Normal file
@@ -0,0 +1,11 @@
|
||||
(ns auto-ap.graphql.intuit-bank-accounts
|
||||
(:require [auto-ap.graphql.utils :refer [->graphql <-graphql assert-admin]]
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[datomic.api :as d]))
|
||||
|
||||
(defn get-intuit-bank-accounts [context args value]
|
||||
#_(assert-admin (:id context))
|
||||
(->graphql (map first (d/q '[:find (pull ?e [*])
|
||||
:in $
|
||||
:where [?e :intuit-bank-account/external-id]]
|
||||
(d/db conn)))))
|
||||
145
src/clj/auto_ap/intuit/core.clj
Normal file
145
src/clj/auto_ap/intuit/core.clj
Normal file
@@ -0,0 +1,145 @@
|
||||
(ns auto-ap.intuit.core
|
||||
(:require [auto-ap.datomic :refer [conn remove-nils]]
|
||||
[amazonica.aws.s3 :as s3]
|
||||
[auto-ap.utils :refer [by]]
|
||||
[clj-http.client :as client]
|
||||
[clj-time.coerce :as coerce]
|
||||
[clj-time.core :as time]
|
||||
[clj-time.format :as f]
|
||||
[config.core :refer [env] :as cfg ]
|
||||
[clojure.string :as str]
|
||||
[clojure.data.json :as json]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.tools.logging :as log]
|
||||
[datomic.api :as d]
|
||||
[mount.core :as mount]
|
||||
[unilog.context :as lc]
|
||||
[yang.scheduler :as scheduler]
|
||||
[clojure.core.async :as async])
|
||||
(:import [org.apache.commons.codec.binary Base64]))
|
||||
|
||||
|
||||
(def authorization-code "AB11638463964I0tYPR3A1inog2HL407u2bZBXHg6LEqCbILRO")
|
||||
(def realm-id "4620816365202617680")
|
||||
(def access-token "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..PUPVn2EYxMCGTIqIjaAm5Q.jV43shT64akeaLZ0JDV_B9kW-rpDH6G-UHqOQgK6Tsupju9noyW6ct1XZxOq6m866kksisdTALzRtojdHhXGSArpk9aql6eSdzr0tq8KvxFNDfURw8GYY7cwbElkD6F0DAfotssFMXYTpGma6EfQz2u4q7UshHDnLe3Fk8EfRn4wob1FrcjB4KcuVNw86F959TMcMQ78N7joYs9PP5OwEnvQ5_Eg9HRMlKcFjs5YQJJW3t6i3j10gn0P7iXnZbIKHF_h_67cd2XJMS0bfyW-X0TfPVuHH0THNEeDM7al86L98fINtUeOKijDY7iJSDWOz0Kma41PM2fvWQbg0JVkpHqaK6mABRzMfK9XuX2pyskKc2AaODtPcB-Uv6WHcgGfND9BHlULweNeI1CsX0NZFFoH8P59XfdmYy92Ul5kHH_ND3D60e4v6mgy3TSSyPkCx6rNZdPCzxMwAUT86k-VXW1tapPmPLdDOiOfnS9UI_tc51YeExWRpDMnHRbIa2TRlaAchG9h41ZrgUiuKl9QoLRhPXP1yg8O5MCQbPzB23sQWN9OngO62opwvnqQg2kbn1S5wHxmpI0a3QPmegFCF9idy8lQVlUXp2myw_WuCZI6SZa5gbbKD33yo2crQX5-gg7ImyGT7tYGe-C440lQC8BWcL2gPr1gS3vHt9knja4EtWDfRCW278IQ2jibH3mr-XjQkWz9Rto7lPhYNLcyrUrlNmS800ZyQN5f01pRr-TrSMqiT5H0bTs7hGDGRdRp.jSDjETBLUrBxmvIiT3sOqg")
|
||||
(def company-id "4620816365202617680")
|
||||
|
||||
(def base-url "https://sandbox-quickbooks.api.intuit.com/v3")
|
||||
(def base-headers {"Authorization" (str "Bearer " access-token)
|
||||
"Accept" "application/json"
|
||||
"Content-Type" "application/json"})
|
||||
|
||||
(def prod-client-id "ABFRwAiOqQiLN66HKplXfyRE3ipD390DHsrUquflRCiOa81mxa")
|
||||
(def prod-client-secret "xDUj04GeQXpLvrhxep1jjDYwjJWbzzOPrirUQTKF")
|
||||
(def prod-redirect-uri "https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl")
|
||||
(def prod-authorization-code "AB11638464998wYuapsEGtIEnRqphrw0H97XUnvEG2dK4cGUyL")
|
||||
(def prod-realm-id "123146163906404")
|
||||
;; "refreshToken": "AB11647191065B0olWYQ61wfq8uszBusfe6Jpn7Au7qY5exkLL",
|
||||
;; "accessToken":,
|
||||
;;
|
||||
(def prod-access-token "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..zbZ9E2iJQfn8cFNV9ROo-A.Md9HAuz1Vv08pANm-4tJSrZVGwhuBuElF8lrOFH_5mO2pBXUeYLra1ag6AdUBP7fdBgJvV53aXSWx2m7tnDygnaEHm95Wxr091HsawIAslzg59QkmAyC1UdbHCE7RX8q2pBeQAt2EBg8G-kmtFxtIIWlFuvt49vHPb1DpTG5z_Mf369OhjEBT0DrKfiCa9Jo1hcppkub6_aHt_aEFKLAshlvpGGV_peYImBiQH-Z2rM5ya7j1leuBdkmVzJH1MeH3mYJDKWoL1v7BTVPwE-4KTo72qCp-6dPvQPnVsHk1hpbvI2qjR9m5CvM3SmuwZqirpmOPGp9IL6gOJnEjGRf1mrk7MJqQ5DEr2T9nhCp9TWDoOle26hoE1dlNi83qFx0I5VwKcGHNmJz7BJxHhyidOcmi4V2RZLQ3CmA032Kc6AL8cgRKRuuhiyFcPBinlgFKzM-a82hp0lwpw6KeaaRSlR3i57W5SBmDoUaelyv9iVaIScs5tgjKAIPFv1Wu-6OnyUIAFwmhi_2dSNSj7NzXlEzDQ9ByoHJYG_DCN_2H-MwOpavNSz-rrGnqocBcTQYNH9P9D61tthS4NCxLLVtiLYHtT0ioJoS5esKuk8wM_jSz5oDNoR364CjEw0Ij9vIZ6eANXVf8Qu_IE9S8O4_G4__Wc4pi4nLKk5q3_kUj414m0ASE8Cm4Qph2b8i7wv1WsuBbQrdMOVQxuE2xFvuHYvT7lNuxO2SbV10iqKlepI.Sa9o2pE3xsVnuEMuuhXl2Q")
|
||||
|
||||
|
||||
|
||||
(def prod-company-id "123146163906404")
|
||||
|
||||
|
||||
(def prod-base-url "https://quickbooks.api.intuit.com/v3")
|
||||
|
||||
(defn set-access-token [t]
|
||||
(s3/put-object :bucket-name (:data-bucket env)
|
||||
:key (str "intuit/access-token")
|
||||
:input-stream (io/make-input-stream (.getBytes t) {})
|
||||
:metadata {:content-type "application/text"}))
|
||||
(defn set-refresh-token [t]
|
||||
(s3/put-object :bucket-name (:data-bucket env)
|
||||
:key (str "intuit/refresh-token")
|
||||
:input-stream (io/make-input-stream (.getBytes t) {})
|
||||
:metadata {:content-type "application/text"}))
|
||||
|
||||
(defn init-tokens [access refresh]
|
||||
(set-access-token access)
|
||||
(set-refresh-token refresh))
|
||||
|
||||
(defn lookup-access-token []
|
||||
(slurp (:object-content (s3/get-object
|
||||
:bucket-name (:data-bucket env)
|
||||
:key "intuit/access-token"))))
|
||||
|
||||
(defn lookup-refresh-token []
|
||||
(slurp (:object-content (s3/get-object
|
||||
:bucket-name (:data-bucket env)
|
||||
:key "intuit/refresh-token"))))
|
||||
|
||||
(defn get-basic-auth []
|
||||
(Base64/encodeBase64String (.getBytes (str prod-client-id ":" prod-client-secret))))
|
||||
|
||||
|
||||
(defn get-fresh-access-token []
|
||||
(let [refresh-token (lookup-refresh-token)
|
||||
response (:body (client/post (str "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer" )
|
||||
|
||||
{:headers {"Accept" "application/json"
|
||||
"Content-Type" "application/x-www-form-urlencoded"
|
||||
"Authorization" (str "Basic " (get-basic-auth))}
|
||||
:form-params {"grant_type" "refresh_token"
|
||||
"refresh_token" refresh-token}
|
||||
:as :json}))]
|
||||
(set-access-token (:access_token response))
|
||||
(set-refresh-token (:refresh_token response))
|
||||
(:access_token response)))
|
||||
|
||||
(def prod-base-headers {"Authorization" (str "Bearer " prod-access-token)
|
||||
"Accept" "application/json"
|
||||
"Content-Type" "application/json"})
|
||||
(defn with-auth [t token]
|
||||
(assoc t "Authorization" (str "Bearer " token)))
|
||||
|
||||
#_(client/get (str base-url "/company/4620816365202617680")
|
||||
{:headers base-headers
|
||||
:as :json})
|
||||
|
||||
|
||||
|
||||
(defn get-bank-accounts [token]
|
||||
(->> (:body (client/get (str prod-base-url "/company/" prod-company-id "/query" )
|
||||
{:headers
|
||||
(with-auth prod-base-headers token)
|
||||
:as :json
|
||||
:query-params {"query" "SELECT * From Account"}}))
|
||||
:QueryResponse
|
||||
:Account
|
||||
(filter
|
||||
#(#{"Bank" "Credit Card"} (:AccountType %)))
|
||||
(map (juxt :Id :Name))
|
||||
(map (fn [[id name]]
|
||||
{:id id
|
||||
:name name}))))
|
||||
|
||||
|
||||
|
||||
(defn get-transactions []
|
||||
(client/get (str prod-base-url "/company/" prod-company-id "/reports/TransactionList" "?minorversion=63&start_date=2021-11-30&end_date=2021-11-30")
|
||||
{:headers prod-base-headers
|
||||
:as :json}))
|
||||
|
||||
(defn get-transactions [start end external-id]
|
||||
(let [token (get-fresh-access-token)]
|
||||
(let [body (:body (client/get (str prod-base-url "/company/" prod-company-id "/reports/TransactionList" "?minorversion=63&start_date=" start "&end_date=" end)
|
||||
{:headers (with-auth prod-base-headers token)
|
||||
:as :json}))
|
||||
headers (map :ColTitle (:Column (:Columns body)))
|
||||
rows (map :ColData (:Row (:Rows body)))]
|
||||
(->> rows
|
||||
(map
|
||||
(fn [row]
|
||||
(into {}
|
||||
(map
|
||||
(fn [h r]
|
||||
[(keyword h) (:value r)])
|
||||
headers
|
||||
row))))
|
||||
(filter #(= external-id
|
||||
(:Account %)))))))
|
||||
|
||||
|
||||
52
src/clj/auto_ap/intuit/import.clj
Normal file
52
src/clj/auto_ap/intuit/import.clj
Normal file
@@ -0,0 +1,52 @@
|
||||
(ns auto-ap.intuit.import
|
||||
(:require [amazonica.aws.s3 :as s3]
|
||||
[auto-ap.datomic :refer [conn remove-nils]]
|
||||
[auto-ap.intuit.core :as i]
|
||||
[auto-ap.utils :refer [by]]
|
||||
[auto-ap.yodlee.import :as y]
|
||||
[clj-time.coerce :as coerce]
|
||||
[clj-time.core :as time]
|
||||
[clj-time.format :as f]
|
||||
[clojure.string :as str]
|
||||
[clojure.tools.logging :as log]
|
||||
[datomic.api :as d]
|
||||
[mount.core :as mount]
|
||||
[unilog.context :as lc]
|
||||
[yang.scheduler :as scheduler]))
|
||||
|
||||
|
||||
(defn upsert-transactions []
|
||||
(let [db (d/db conn)]
|
||||
(doseq [[bank-account external-id] (-> (d/q '[:find ?ba ?external-id
|
||||
:in $
|
||||
:where [?ba :bank-account/intuit-bank-account ?iab]
|
||||
[?iab :intuit-bank-account/external-id ?external-id]]
|
||||
|
||||
db))
|
||||
:let [bank-account (d/entity db bank-account)
|
||||
end (auto-ap.time/local-now)
|
||||
start (time/plus end (time/days -10))]
|
||||
]
|
||||
(log/infof "importing from %s to %s for %s" start end external-id)
|
||||
(let [transactions (->> (i/get-transactions (auto-ap.time/unparse start auto-ap.time/iso-date)
|
||||
(auto-ap.time/unparse end auto-ap.time/iso-date)
|
||||
external-id)
|
||||
(mapv (fn [r]
|
||||
{:client-id (:db/id (:client/_bank-accounts bank-account))
|
||||
:bank-account-id (:db/id bank-account)
|
||||
:description-original (:Memo/Description r)
|
||||
:amount (Double/parseDouble (:Amount r))
|
||||
:date (auto-ap.time/parse (:Date r) auto-ap.time/iso-date)
|
||||
:status "posted"})))]
|
||||
(log/infof "%d transactions found" (count transactions))
|
||||
(y/grouped-import transactions)))))
|
||||
|
||||
|
||||
(defn upsert-accounts []
|
||||
(let [token (i/get-fresh-access-token)
|
||||
bank-accounts (i/get-bank-accounts token)]
|
||||
@(d/transact conn (mapv
|
||||
(fn [ba]
|
||||
{:intuit-bank-account/external-id (:name ba)
|
||||
:intuit-bank-account/name (:name ba)})
|
||||
bank-accounts))))
|
||||
@@ -173,83 +173,86 @@
|
||||
first)))
|
||||
|
||||
(defn transactions->txs [transactions transaction->bank-account apply-rules existing]
|
||||
(into []
|
||||
(doto
|
||||
(into []
|
||||
|
||||
(for [transaction transactions
|
||||
:let [{post-date :postDate
|
||||
account-id :accountId
|
||||
date :date
|
||||
id :id
|
||||
{amount :amount} :amount
|
||||
{description-original :original
|
||||
description-simple :simple} :description
|
||||
{merchant-id :id
|
||||
merchant-name :name} :merchant
|
||||
base-type :baseType
|
||||
type :type
|
||||
status :status}
|
||||
transaction
|
||||
amount (if (= "DEBIT" base-type)
|
||||
(- amount)
|
||||
amount)
|
||||
check-number (extract-check-number transaction)
|
||||
bank-account (transaction->bank-account transaction)
|
||||
bank-account-id (:db/id bank-account)
|
||||
client (:client/_bank-accounts bank-account)
|
||||
client-id (:db/id client)
|
||||
valid-locations (or (:bank-account/locations bank-account) (:client/locations client))
|
||||
|
||||
date (time/parse date "YYYY-MM-dd")]
|
||||
:when (and client-id
|
||||
(not (existing (sha-256 (str id))))
|
||||
(= "POSTED" status)
|
||||
(for [transaction transactions
|
||||
:let [{post-date :postDate
|
||||
account-id :accountId
|
||||
date :date
|
||||
id :id
|
||||
{amount :amount} :amount
|
||||
{description-original :original
|
||||
description-simple :simple} :description
|
||||
{merchant-id :id
|
||||
merchant-name :name} :merchant
|
||||
base-type :baseType
|
||||
type :type
|
||||
status :status}
|
||||
transaction
|
||||
amount (if (= "DEBIT" base-type)
|
||||
(- amount)
|
||||
amount)
|
||||
check-number (extract-check-number transaction)
|
||||
bank-account (transaction->bank-account transaction)
|
||||
bank-account-id (:db/id bank-account)
|
||||
client (:client/_bank-accounts bank-account)
|
||||
client-id (:db/id client)
|
||||
valid-locations (or (:bank-account/locations bank-account) (:client/locations client))
|
||||
|
||||
date (time/parse date "YYYY-MM-dd")]
|
||||
:when (and client-id
|
||||
(not (existing (sha-256 (str id))))
|
||||
(= "POSTED" status)
|
||||
|
||||
(or (not (:start-date bank-account))
|
||||
(t/after? date (:start-date bank-account))))]
|
||||
(let [existing-check (transaction->existing-payment transaction check-number client-id bank-account-id amount id)
|
||||
autopay-invoices-matches (when-not existing-check
|
||||
(match-transaction-to-unfulfilled-autopayments amount client-id ))
|
||||
unpaid-invoices-matches (when-not existing-check
|
||||
(match-transaction-to-unpaid-invoices amount client-id ))
|
||||
expected-deposit (when (and (> amount 0.0)
|
||||
(not existing-check))
|
||||
(find-expected-deposit client-id amount date))]
|
||||
(cond->
|
||||
[#:transaction
|
||||
{:post-date (coerce/to-date (time/parse post-date "YYYY-MM-dd"))
|
||||
:id (sha-256 (str id))
|
||||
:account-id account-id
|
||||
:date (coerce/to-date date)
|
||||
:amount (double amount)
|
||||
:description-original (some-> description-original (str/replace #"\s+" " "))
|
||||
:description-simple (some-> description-simple (str/replace #"\s+" " "))
|
||||
:approval-status :transaction-approval-status/unapproved
|
||||
:type type
|
||||
:status status
|
||||
:client client-id
|
||||
:check-number check-number
|
||||
:bank-account bank-account-id}]
|
||||
existing-check (update 0 #(assoc % :transaction/approval-status :transaction-approval-status/approved
|
||||
:transaction/payment {:db/id (:db/id existing-check)
|
||||
:payment/status :payment-status/cleared}
|
||||
:transaction/vendor (:db/id (:payment/vendor existing-check))
|
||||
:transaction/location "A"
|
||||
:transaction/accounts [#:transaction-account
|
||||
{:account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
|
||||
:location "A"
|
||||
:amount (Math/abs (double amount))}]))
|
||||
(or (not (:start-date bank-account))
|
||||
(t/after? date (:start-date bank-account))))]
|
||||
(let [existing-check (transaction->existing-payment transaction check-number client-id bank-account-id amount id)
|
||||
autopay-invoices-matches (when-not existing-check
|
||||
(match-transaction-to-unfulfilled-autopayments amount client-id ))
|
||||
unpaid-invoices-matches (when-not existing-check
|
||||
(match-transaction-to-unpaid-invoices amount client-id ))
|
||||
expected-deposit (when (and (> amount 0.0)
|
||||
(not existing-check))
|
||||
(find-expected-deposit client-id amount date))]
|
||||
(cond->
|
||||
[#:transaction
|
||||
{:post-date (coerce/to-date (time/parse post-date "YYYY-MM-dd"))
|
||||
:id (sha-256 (str id))
|
||||
:raw-id (str id)
|
||||
:account-id account-id
|
||||
:date (coerce/to-date date)
|
||||
:amount (double amount)
|
||||
:description-original (some-> description-original (str/replace #"\s+" " "))
|
||||
:description-simple (some-> description-simple (str/replace #"\s+" " "))
|
||||
:approval-status :transaction-approval-status/unapproved
|
||||
:type type
|
||||
:status status
|
||||
:client client-id
|
||||
:check-number check-number
|
||||
:bank-account bank-account-id}]
|
||||
existing-check (update 0 #(assoc % :transaction/approval-status :transaction-approval-status/approved
|
||||
:transaction/payment {:db/id (:db/id existing-check)
|
||||
:payment/status :payment-status/cleared}
|
||||
:transaction/vendor (:db/id (:payment/vendor existing-check))
|
||||
:transaction/location "A"
|
||||
:transaction/accounts [#:transaction-account
|
||||
{:account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
|
||||
:location "A"
|
||||
:amount (Math/abs (double amount))}]))
|
||||
|
||||
;; temporarily removed to automatically match autopaid invoices
|
||||
#_(and (not existing-check)
|
||||
(seq autopay-invoices-matches)) #_(add-new-payment autopay-invoices-matches bank-account-id client-id)
|
||||
expected-deposit (update 0 #(assoc % :transaction/expected-deposit {:db/id expected-deposit
|
||||
:expected-deposit/status :expected-deposit-status/cleared}))
|
||||
|
||||
;; temporarily removed to automatically match autopaid invoices
|
||||
#_(and (not existing-check)
|
||||
(seq autopay-invoices-matches)) #_(add-new-payment autopay-invoices-matches bank-account-id client-id)
|
||||
expected-deposit (update 0 #(assoc % :transaction/expected-deposit {:db/id expected-deposit
|
||||
:expected-deposit/status :expected-deposit-status/cleared}))
|
||||
|
||||
|
||||
(and (not (seq autopay-invoices-matches))
|
||||
(not (seq unpaid-invoices-matches))
|
||||
(not expected-deposit)) (update 0 #(apply-rules % valid-locations))
|
||||
true (update 0 remove-nils))))))
|
||||
(and (not (seq autopay-invoices-matches))
|
||||
(not (seq unpaid-invoices-matches))
|
||||
(not expected-deposit)) (update 0 #(apply-rules % valid-locations))
|
||||
true (update 0 remove-nils)))))
|
||||
println))
|
||||
|
||||
|
||||
(defn batch-transact [transactions]
|
||||
@@ -310,6 +313,34 @@
|
||||
:user/role ":admin"}))
|
||||
(log/info "Imported manual transactions"))))
|
||||
|
||||
(defn grouped-import [manual-transactions]
|
||||
(lc/with-context {:source "grouped import"}
|
||||
(let [transformed-transactions (->> manual-transactions
|
||||
(filter #(= "posted" (:status %)))
|
||||
(group-by #(select-keys % [:date :description-original :amount]))
|
||||
(vals)
|
||||
(mapcat (fn [transaction-group]
|
||||
(map
|
||||
(fn [index {:keys [date description-original high-level-category amount bank-account-id client-id] :as transaction}]
|
||||
{:id (str date "-" bank-account-id "-" description-original "-" amount "-" index "-" client-id)
|
||||
:bank-account-id bank-account-id
|
||||
:date (time/unparse date "YYYY-MM-dd")
|
||||
:amount {:amount amount}
|
||||
:description {:original description-original
|
||||
:simple high-level-category}
|
||||
:status "POSTED"})
|
||||
(range)
|
||||
transaction-group))))
|
||||
all-rules (tr/get-all)
|
||||
all-bank-accounts (by :db/id (get-all-bank-accounts))
|
||||
transaction->bank-account (comp all-bank-accounts :bank-account-id)]
|
||||
(log/info "Importing " (count transformed-transactions) " grouped transactions")
|
||||
|
||||
(doseq [tx (transactions->txs transformed-transactions transaction->bank-account (rm/rule-applying-fn all-rules) (get-existing))]
|
||||
(audit-transact tx {:user/name "Yodlee import"
|
||||
:user/role ":admin"}))
|
||||
(log/info "Imported grouped transactions"))))
|
||||
|
||||
(defn do-import
|
||||
([]
|
||||
(do-import (client/get-transactions)))
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
[: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
|
||||
[:yodlee-account [:name :id :number]]
|
||||
[:intuit-bank-account [:name :id :external-id]]
|
||||
:locations :include-in-reports :current-balance :yodlee-balance-old] ]
|
||||
[:address [:street1 :street2 :city :state :zip]]
|
||||
[:forecasted-transactions [:id :amount :identifier :day-of-month]]]
|
||||
|
||||
@@ -174,6 +174,11 @@
|
||||
(fn [db]
|
||||
(:is-initial-loading? db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::intuit-bank-accounts
|
||||
(fn [db]
|
||||
(::intuit-bank-accounts db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::modal-state
|
||||
(fn [db [_ id status-from]]
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
[auto-ap.views.components.address :refer [address-field]]
|
||||
[auto-ap.views.components.layouts :refer [side-bar-layout appearing-side-bar side-bar] ]
|
||||
[auto-ap.views.components.admin.side-bar :refer [admin-side-bar]]
|
||||
[auto-ap.views.utils :refer [login-url dispatch-event dispatch-value-change bind-field horizontal-field nf]]
|
||||
[auto-ap.views.utils :refer [login-url dispatch-event dispatch-value-change bind-field horizontal-field nf with-user]]
|
||||
[auto-ap.views.pages.admin.clients.table :as table]
|
||||
[auto-ap.views.pages.admin.clients.form :as form]
|
||||
[cljs.reader :as edn]
|
||||
@@ -23,12 +23,22 @@
|
||||
[auto-ap.views.pages.admin.clients.side-bar :as side-bar]
|
||||
[vimsical.re-frame.fx.track :as track]))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::received-intuit-bank-accounts
|
||||
(fn [db [_ result]]
|
||||
(assoc db ::subs/intuit-bank-accounts (:intuit-bank-accounts result))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::mounted
|
||||
(fn [{:keys [db]} _]
|
||||
[with-user]
|
||||
(fn [{:keys [db user]} _]
|
||||
{::track/register {:id ::params
|
||||
:subscription [::params]
|
||||
:event-fn (fn [params] [::params-change params])}}))
|
||||
:event-fn (fn [params] [::params-change params])}
|
||||
:graphql {:token user
|
||||
:query-obj {:venia/queries [[:intuit_bank_accounts [:external_id :id :name]]]}
|
||||
:owns-state {:single [::load-intuit-bank-accounts]}
|
||||
:on-success [::received-intuit-bank-accounts]} }))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::unmounted
|
||||
@@ -45,6 +55,8 @@
|
||||
(seq filter-params) (merge filter-params)
|
||||
(seq table-params) (merge table-params))))
|
||||
|
||||
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::new
|
||||
(fn [db [_ client-id]]
|
||||
|
||||
@@ -127,7 +127,8 @@
|
||||
:identifier identifier
|
||||
:amount amount})
|
||||
(:forecasted-transactions new-client-data))
|
||||
:bank-accounts (map (fn [{:keys [number name check-number include-in-reports type id code numeric-code start-date bank-name routing bank-code new? sort-order visible yodlee-account-id locations yodlee-account]}]
|
||||
:bank-accounts (map (fn [{:keys [number name check-number intuit-bank-account include-in-reports type id code numeric-code start-date bank-name routing bank-code new? sort-order visible yodlee-account-id locations yodlee-account]}]
|
||||
(println intuit-bank-account)
|
||||
{:number number
|
||||
:name name
|
||||
:check-number (when-not (str/blank? check-number)
|
||||
@@ -152,6 +153,7 @@
|
||||
:yodlee-account-id (when-not (str/blank? yodlee-account-id)
|
||||
(js/parseInt yodlee-account-id))
|
||||
:yodlee-account (:id yodlee-account)
|
||||
:intuit-bank-account (:id intuit-bank-account)
|
||||
:code (if new?
|
||||
(str (:code new-client-data) "-" code)
|
||||
code)
|
||||
@@ -390,7 +392,12 @@
|
||||
[typeahead-v3 {:entities @(re-frame/subscribe [::yodlee-accounts (:id new-client)])
|
||||
:entity->text (fn [m] (str (:name m) " - " (:number m)))
|
||||
:type "typeahead-v3"
|
||||
:field [:bank-accounts sort-order :yodlee-account]}]]])
|
||||
:field [:bank-accounts sort-order :yodlee-account]}]]
|
||||
[field "Intuit Bank Account"
|
||||
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts])
|
||||
:entity->text (fn [m] (str (:name m)))
|
||||
:type "typeahead-v3"
|
||||
:field [:bank-accounts sort-order :intuit-bank-account]}]]])
|
||||
|
||||
(when (#{:credit ":credit"} type )
|
||||
[:div
|
||||
@@ -420,7 +427,12 @@
|
||||
[typeahead-v3 {:entities @(re-frame/subscribe [::yodlee-accounts (:id new-client)])
|
||||
:entity->text (fn [m] (str (:name m) " - " (:number m)))
|
||||
:type "typeahead-v3"
|
||||
:field [:bank-accounts sort-order :yodlee-account]}]]])
|
||||
:field [:bank-accounts sort-order :yodlee-account]}]]
|
||||
[field "Intuit Bank Account"
|
||||
[typeahead-v3 {:entities @(re-frame/subscribe [::subs/intuit-bank-accounts])
|
||||
:entity->text (fn [m] (str (:name m)))
|
||||
:type "typeahead-v3"
|
||||
:field [:bank-accounts sort-order :intuit-bank-account]}]]])
|
||||
[:div.field
|
||||
[:label.label "Locations"]
|
||||
[:div.control
|
||||
|
||||
Reference in New Issue
Block a user