enormous refactor but simplified much!

This commit is contained in:
Bryce Covert
2021-12-22 18:14:49 -08:00
parent a7c9d376bc
commit 7489426ccb
25 changed files with 1188 additions and 1258 deletions

View File

@@ -0,0 +1,74 @@
(ns auto-ap.import.intuit
(:require [amazonica.aws.s3 :as s3]
[auto-ap.datomic :refer [conn remove-nils]]
[auto-ap.intuit.core :as i]
[auto-ap.utils :refer [by allow-once]]
[auto-ap.import.transactions :as t]
[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 get-intuit-bank-accounts [db]
(d/q '[:find ?external-id ?ba ?c
:in $
:where
[?c :client/bank-accounts ?ba]
[?ba :bank-account/intuit-bank-account ?iab]
[?iab :intuit-bank-account/external-id ?external-id]]
db))
(defn intuit->transaction [transaction]
{:transaction/description-original (:Memo/Description transaction)
:transaction/amount (Double/parseDouble (:Amount transaction))
:transaction/date (coerce/to-date (auto-ap.time/parse (:Date transaction) auto-ap.time/iso-date))
:transaction/status "POSTED"})
(defn intuits->transactions [transactions bank-account-id client-id]
(->> transactions
(map intuit->transaction)
(map #(assoc %
:transaction/bank-account bank-account-id
:transaction/client client-id))
(t/apply-synthetic-ids)))
(defn import-intuit []
(lc/with-context {:source "Import intuit transactions"}
(let [import-batch (t/start-import-batch :import-source/intuit "Automated intuit user")
db (d/db conn)
end (auto-ap.time/local-now)
start (time/plus end (time/days -30))]
(try
(doseq [[external-id bank-account-id client-id] (get-intuit-bank-accounts db)
transaction (-> (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)
(intuits->transactions bank-account-id client-id))]
(t/import-transaction! import-batch transaction))
(t/finish! import-batch)
(catch Exception e
(t/fail! import-batch e))))))
(def upsert-transactions (allow-once upsert-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))))
(mount/defstate import-worker
:start (scheduler/every (* 1000 60 60 24) import-intuit)
:stop (scheduler/stop import-worker))
(mount/defstate account-worker
:start (scheduler/every (* 1000 60 60 24) upsert-accounts)
:stop (scheduler/stop account-worker))

View File

@@ -0,0 +1,20 @@
(ns auto-ap.import.intuit-test
(:require [auto-ap.import.intuit :as sut]
[clojure.test :as t]))
(def base-transaction {:Memo/Description "this is a description"
:Amount "45.23"
:Date "2021-10-11"})
(t/deftest intuit->transaction
(t/testing "Should parse dates"
(t/is (= #inst "2021-01-01T00:00:00-08:00" (:transaction/date (sut/intuit->transaction (assoc base-transaction :Date "2021-01-01")))))
(t/is (= #inst "2021-06-01T00:00:00-07:00" (:transaction/date (sut/intuit->transaction (assoc base-transaction :Date "2021-06-01")))))))
(t/deftest intuits->transactions
(t/testing "should give unique ids to duplicates"
(t/is (= ["2021-10-11T00:00:00.000-07:00-123-this is a description-45.23-0-345"
"2021-10-11T00:00:00.000-07:00-123-this is a description-45.23-1-345"] (map :transaction/raw-id (sut/intuits->transactions [base-transaction base-transaction]
123
345))))))

View File

@@ -0,0 +1,66 @@
(ns auto-ap.import.manual
(:require [auto-ap.datomic :refer [conn]]
[auto-ap.import.manual.common :as c]
[auto-ap.import.transactions :as t]
[clj-time.coerce :as coerce]
[clojure.data.csv :as csv]
[clojure.tools.logging :as log]
[datomic.api :as d]
[unilog.context :as lc]))
(defn manual-import-batch [transactions user]
)
(def columns [:status :raw-date :description-original :high-level-category nil nil :amount nil nil nil nil nil :bank-account-code :client-code])
(defn tabulate-data [data]
(->>
(csv/read-csv data :separator \tab)
(drop 1)
(map (fn [row]
(into {} (->> (map vector columns row)
(filter (fn [[k v]] k))))))))
(defn manual->transaction [{:keys [description-original amount bank-account-code date] :as transaction} bank-account-lookup client-lookup]
(-> {:transaction/description-original description-original
:transaction/status "POSTED"}
(c/assoc-or-error :transaction/client #(if-let [client-id (client-lookup bank-account-code)]
client-id
(throw (Exception. (str "Cannot find client for bank account code " bank-account-code)))))
(c/assoc-or-error :transaction/bank-account #(if-let [bank-account-id (bank-account-lookup bank-account-code)]
bank-account-id
(throw (Exception. (str "Cannot find bank account by code " bank-account-code)))))
(c/assoc-or-error :transaction/date #(coerce/to-date (c/parse-date transaction)))
(c/assoc-or-error :transaction/amount #(c/parse-amount transaction))))
(defn import-batch [transactions user]
(lc/with-context {:source "Manual import transactions"}
(let [bank-account-code->client (into {}
(d/q '[:find ?bac ?c
:in $
:where
[?c :client/bank-accounts ?ba]
[?ba :bank-account/code ?bac]]
(d/db conn)))
bank-account-code->bank-account (into {}
(d/q '[:find ?bac ?ba
:in $
:where [?ba :bank-account/code ?bac]]
(d/db conn)))]
(let [import-batch (t/start-import-batch :import-source/manual user)
transactions (->> transactions
(map (fn [t]
(manual->transaction t bank-account-code->bank-account bank-account-code->client)))
(t/apply-synthetic-ids ))]
(try
(doseq [transaction transactions]
(when-not (seq (:errors transaction))
(t/import-transaction! import-batch transaction)))
(t/finish! import-batch)
(assoc (t/get-stats import-batch)
:failed-validation (count (filter :errors transactions))
:sample-error (first (first (map :errors (filter :errors transactions)))))
(catch Exception e
(t/fail! import-batch e)
(t/get-stats import-batch)))))))

View File

@@ -0,0 +1,53 @@
(ns auto-ap.import.manual.common
(:require [auto-ap.datomic.accounts :as d-accounts]
[auto-ap.parse.util :as parse-u]
[clojure.string :as str]))
(defn parse-amount [i]
(try
(Double/parseDouble (str/replace (or (second
(re-matches #"[^0-9\.,\\-]*([0-9\.,\\-]+)[^0-9\.,]*" (:amount i)))
"0")
#"," ""))
(catch Exception e
(throw (Exception. (str "Could not parse total from value '" (:amount i) "'") e)))))
(defn parse-account-numeric-code [i]
(try
(when-let [account-numeric-code (:account-numeric-code i)]
(:db/id (d-accounts/get-account-by-numeric-code-and-sets (Integer/parseInt account-numeric-code)
["default"])))
(catch Exception e
(throw (Exception. (str "Could not parse expense account from value '" (:account-numeric-code i) "'") e)))))
(defn parse-account-id [i]
(try
(Long/parseLong (second
(re-matches #"[^0-9,\\-]*([0-9,\\-]+)[^0-9,]*" (:bank-account-id i))))
(catch Exception e
(throw (Exception. (str "Could not parse account from value '" (:bank-account-id i) "'") e)))))
(defn parse-date [{:keys [raw-date]}]
(when-not
(re-find #"\d{1,2}/\d{1,2}/\d{4}" raw-date)
(throw (Exception. (str "Date " raw-date " must match MM/dd/yyyy"))))
(try
(parse-u/parse-value :clj-time "MM/dd/yyyy" raw-date)
(catch Exception e
(throw (Exception. (str "Could not parse date from '" raw-date "'") e)))))
(defn parse-or-error [key f]
(fn [x]
(try
(assoc x key (f x))
(catch Exception e
(update x :errors conj {:info (.getMessage e)
:details (str e)})))))
(defn assoc-or-error [m k f]
(try
(assoc m k (f))
(catch Exception e
(update m :errors conj {:info (.getMessage e)
:details (str e)}))))

View File

@@ -0,0 +1,60 @@
(ns auto-ap.import.plaid
(:require
[auto-ap.datomic :refer [conn]]
[auto-ap.plaid.core :as p]
[auto-ap.utils :refer [allow-once]]
[auto-ap.import.transactions :as t]
[clj-time.core :as time]
[clojure.tools.logging :as log]
[datomic.api :as d]
[mount.core :as mount]
[unilog.context :as lc]
[yang.scheduler :as scheduler]
[clj-time.coerce :as coerce]))
(defn get-plaid-accounts [db]
(-> (d/q '[:find ?ba ?c ?external-id ?t
:in $
:where
[?c :client/bank-accounts ?ba]
[?ba :bank-account/plaid-account ?pa]
[?pa :plaid-account/external-id ?external-id]
[?pi :plaid-item/accounts ?pa]
[?pi :plaid-item/access-token ?t]]
db )))
(defn plaid->transaction [t]
#:transaction {:description-original (:name t)
:raw-id (:transaction_id t)
:id (digest/sha-256 (:transaction_id t))
:amount (double (:amount t))
:date (coerce/to-date (auto-ap.time/parse (:date t) auto-ap.time/iso-date))
:status "POSTED"})
(defn import-plaid []
(lc/with-context {:source "Import plaid transactions"}
(let [import-batch (t/start-import-batch :import-source/plaid "Automated plaid user")
end (auto-ap.time/local-now)
start (time/plus end (time/days -30))]
(try
(doseq [[bank-account-id client-id external-id access-token] (get-plaid-accounts (d/db conn))
transaction (:transactions (p/get-transactions access-token external-id start end))]
(when (not (:pending transaction))
(t/import-transaction! import-batch (assoc (plaid->transaction transaction)
:transaction/bank-account bank-account-id
:transaction/client client-id))))
(t/finish! import-batch)
(catch Exception e
(t/fail! import-batch e))))))
(def import-plaid (allow-once import-plaid))
(mount/defstate import-worker
:start (scheduler/every (* 1000 60 60 3) import-plaid)
:stop (scheduler/stop import-worker))

View File

@@ -0,0 +1,336 @@
(ns auto-ap.import.transactions
(:require [auto-ap.datomic :refer [audit-transact conn remove-nils uri]]
[auto-ap.datomic.accounts :as a]
[auto-ap.datomic.checks :as d-checks]
[auto-ap.datomic.transaction-rules :as tr]
[auto-ap.datomic.transactions :as d-transactions]
[auto-ap.rule-matching :as rm]
[auto-ap.utils :refer [dollars=]]
[auto-ap.yodlee.core :as client]
[clj-time.coerce :as coerce]
[clj-time.core :as t]
[clojure.core.cache :as cache]
[clojure.tools.logging :as log]
[datomic.api :as d]
[digest :refer [sha-256]]))
(defn rough-match [client-id bank-account-id amount]
(if (and client-id bank-account-id amount)
(let [[matching-checks] (d-checks/get-graphql {:client-id client-id
:bank-account-id bank-account-id
:amount (- amount)
:status :payment-status/pending})]
(if (= 1 (count matching-checks))
(first matching-checks)
nil))
nil))
(defn transaction->existing-payment [_ check-number client-id bank-account-id amount id]
(log/info "Searching for a matching check for "
{:client-id client-id
:check-number check-number
:bank-account-id bank-account-id
:amount amount})
(cond (not (and client-id bank-account-id))
nil
(:transaction/payment (d-transactions/get-by-id [:transaction/id (sha-256 (str id))]))
nil
check-number
(or (-> (d-checks/get-graphql {:client-id client-id
:bank-account-id bank-account-id
:check-number check-number
:amount (- amount)
:status :payment-status/pending})
first
first)
(rough-match client-id bank-account-id amount))
:else
(rough-match client-id bank-account-id amount)))
(defn match-transaction-to-unfulfilled-autopayments [amount client-id]
(log/info "trying to find uncleared autopay invoices")
(let [candidate-invoices-vendor-groups (->> (d/query {:query {:find ['?vendor-id '?e '?total '?sd]
:in ['$ '?client-id]
:where ['[?e :invoice/client ?client-id]
'[?e :invoice/scheduled-payment ?sd]
'[?e :invoice/status :invoice-status/paid]
'(not [_ :invoice-payment/invoice ?e])
'[?e :invoice/vendor ?vendor-id]
'[?e :invoice/total ?total]]}
:args [(d/db conn) client-id]})
(sort-by last) ;; sort by scheduled payment date
(group-by first) ;; group by vendors
vals)
considerations (for [candidate-invoices candidate-invoices-vendor-groups
invoice-count (range 1 32)
consideration (partition invoice-count 1 candidate-invoices)
:when (dollars= (reduce (fn [acc [_ _ amount]]
(+ acc amount)) 0.0 consideration)
(- amount))]
consideration)]
(log/info "Found " (count considerations) "considerations for transaction of" amount)
considerations
))
(defn match-transaction-to-unpaid-invoices [amount client-id]
(log/info "trying to find unpaid invoices for " client-id amount)
(let [candidate-invoices-vendor-groups (->> (d/query {:query {:find ['?vendor-id '?e '?outstanding-balance '?d]
:in ['$ '?client-id]
:where ['[?e :invoice/client ?client-id]
'[?e :invoice/status :invoice-status/unpaid]
'(not [_ :invoice-payment/invoice ?e])
'[?e :invoice/vendor ?vendor-id]
'[?e :invoice/outstanding-balance ?outstanding-balance]
'[?e :invoice/date ?d]]}
:args [(d/db conn) client-id]})
(sort-by last) ;; sort by scheduled payment date
(group-by first) ;; group by vendors
vals)
considerations (for [candidate-invoices candidate-invoices-vendor-groups
invoice-count (range 1 32)
consideration (partition invoice-count 1 candidate-invoices)
:when (dollars= (reduce (fn [acc [_ _ amount]]
(+ acc amount)) 0.0 consideration)
(- amount))]
consideration)]
(log/info "Found " (count considerations) "unpaid invoice considerations for transaction of" amount)
considerations))
(defn match-transaction-to-single-unfulfilled-autopayments [amount client-id]
(let [considerations (match-transaction-to-unfulfilled-autopayments amount client-id)]
(if (= 1 (count considerations))
(first considerations)
[])))
(defn add-new-payment [[transaction :as tx] [[vendor] :as invoice-payments] bank-account-id client-id]
(log/info "Adding a new payment for transaction " (:transaction/id transaction) " and invoices " invoice-payments)
(let [payment-id (d/tempid :db.part/user)]
(-> tx
(conj {:payment/bank-account bank-account-id
:payment/client client-id
:payment/amount (- (:transaction/amount transaction))
:payment/vendor vendor
:payment/date (:transaction/date transaction)
:payment/type :payment-type/debit
:payment/status :payment-status/cleared
:db/id payment-id})
(into (mapcat (fn [[vendor invoice-id invoice-amount]]
[{:invoice-payment/invoice invoice-id
:invoice-payment/payment payment-id
:invoice-payment/amount invoice-amount}
{:db/id invoice-id
:invoice/outstanding-balance 0.0
:invoice/status :invoice-status/paid}])
invoice-payments))
(update 0 assoc
:transaction/payment payment-id
:transaction/approval-status :transaction-approval-status/approved
:transaction/vendor vendor
:transaction/location "A")
(conj [:reset (:db/id transaction) :transaction/accounts
[#:transaction-account
{:account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"]))
:location "A"
:amount (Math/abs (:transaction/amount transaction))}]]))))
(defn extract-check-number [{:transaction/keys [description-original]}]
(if-let [[_ _ check-number] (re-find #"(?i)check(card|[^0-9]+([0-9]*))" description-original)]
(try
(Integer/parseInt check-number)
(catch NumberFormatException e
nil))
nil))
(defn find-expected-deposit [client-id amount date]
(when date
(-> (d/q
'[:find ?ed
:in $ ?c ?a ?d-start
:where
[?ed :expected-deposit/client ?c]
(not [?ed :expected-deposit/status :expected-deposit-status/cleared])
[?ed :expected-deposit/date ?d]
[(>= ?d ?d-start)]
[?ed :expected-deposit/total ?a2]
[(auto-ap.utils/dollars= ?a2 ?a)]
]
(d/db conn) client-id amount (coerce/to-date (t/plus date (t/days -10))))
first
first)))
(defn categorize-transaction [transaction bank-account existing]
(let [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))]
(cond (= :transaction-approval-status/suppressed (existing (:transaction/id transaction)))
:suppressed
(existing (:transaction/id transaction))
:extant
(not (:transaction/client transaction))
:error
(not (:transaction/bank-account transaction))
:error
(not (:transaction/id transaction))
:error
(not= "POSTED" (:transaction/status transaction))
:not-posted
(and (:bank-account/start-date bank-account)
(not (t/after? (coerce/to-date-time (:transaction/date transaction))
(-> bank-account :bank-account/start-date coerce/to-date-time))))
:not-ready
:else
:import)))
(defn transaction->txs [transaction bank-account apply-rules]
(let [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))]
(into []
(let [{:transaction/keys [amount id date]} transaction
check-number (extract-check-number transaction)
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 (:db/id client) amount (coerce/to-date-time date)))]
(cond->
[(assoc transaction :transaction/approval-status :transaction-approval-status/unapproved)]
check-number (update 0 #(assoc % :transaction/check-number check-number))
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}))
(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))))))
(defn get-existing [bank-account]
(log/info "looking up bank account data for" bank-account)
(into {}
(d/query {:query {:find ['?tid '?as2]
:in ['$ '?ba]
:where ['[?e :transaction/bank-account ?ba]
'[?e :transaction/id ?tid]
'[?e :transaction/approval-status ?as]
'[?as :db/ident ?as2]]}
:args [(d/db (d/connect uri)) bank-account]})))
(defprotocol ImportBatch
(import-transaction! [this transaction])
(get-stats [this ])
(finish! [this])
(fail! [this error]))
(defn start-import-batch [source user]
(let [stats (atom {:import-batch/imported 0
:import-batch/suppressed 0
:import-batch/error 0
:import-batch/not-ready 0
:import-batch/extant 0})
extant-cache (atom (cache/ttl-cache-factory {} :ttl 60000 ))
import-id (get (:tempids @(d/transact conn [{:db/id "import-batch"
:import-batch/date (coerce/to-date (t/now))
:import-batch/source source
:import-batch/status :import-status/started
:import-batch/user-name user}])) "import-batch")
rule-applying-function (rm/rule-applying-fn (tr/get-all))]
(log/info "Importing transactions from " source)
(reify ImportBatch
(import-transaction! [this transaction]
(let [bank-account (d/pull (d/db conn)
[:bank-account/code
:db/id
:bank-account/locations
:bank-account/start-date
{:client/_bank-accounts [:client/code :client/locations :db/id]} ]
(:transaction/bank-account transaction))
extant (get (swap! extant-cache cache/through-cache (:transaction/bank-account transaction) get-existing)
(:transaction/bank-account transaction))
action (categorize-transaction transaction bank-account extant)
transaction (assoc transaction :import-batch/_entry import-id)]
(swap! stats
#(update % (condp = action
:import :import-batch/imported
:extant :import-batch/extant
:suppressed :import-batch/suppressed
:error :import-batch/error
:not-read :import-batch/not-ready) inc))
(when (= :import action)
(audit-transact (transaction->txs transaction bank-account rule-applying-function)
{:user/name user
:user/role ":admin"}))))
(get-stats [this]
@stats)
(fail! [this error]
(log/errorf "Couldn't complete import %d with error." import-id)
(log/error error)
@(d/transact conn [(merge {:db/id import-id
:import-batch/status :import-status/completed
:import-batch/error-message (str error)}
@stats)]))
(finish! [this]
(log/infof "Finishing import batch %d for %s with stats %s " import-id (name source) (pr-str @stats))
@(d/transact conn [(merge {:db/id import-id
:import-batch/status :import-status/completed}
@stats)])))))
(defn synthetic-key [{:transaction/keys [date bank-account description-original amount client] } index]
(str (str (some-> date coerce/to-date-time auto-ap.time/localize)) "-" bank-account "-" description-original "-" amount "-" index "-" client))
(defn apply-synthetic-ids [transactions]
(->> transactions
(group-by #(select-keys % [:transaction/date :transaction/bank-account :transaction/description-original :transaction/amount :transaction/client]))
(vals)
(mapcat (fn [group]
(map (fn [index transaction]
(let [raw-id (synthetic-key transaction index)]
(assoc transaction
:transaction/id (digest/sha-256 raw-id)
:transaction/raw-id raw-id)))
(range)
group)))))

View File

@@ -0,0 +1,70 @@
(ns auto-ap.import.yodlee
(:require [auto-ap.datomic :refer [conn]]
[auto-ap.import.transactions :as t]
[auto-ap.time :as atime]
[auto-ap.utils :refer [allow-once]]
[auto-ap.yodlee.core :as client]
[clj-time.coerce :as coerce]
[clojure.string :as str]
[datomic.api :as d]
[digest :refer [sha-256]]
[mount.core :as mount]
[unilog.context :as lc]
[yang.scheduler :as scheduler]
[clojure.tools.logging :as log]))
(defn yodlee->transaction [transaction]
(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)
date (atime/parse date "YYYY-MM-dd")]
#:transaction
{:post-date (coerce/to-date (atime/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+" " "))
:type type
:status status}))
(defn import-yodlee []
(lc/with-context {:source "Import yodlee transactions"}
(let [import-batch (t/start-import-batch :import-source/yodlee "Automated yodlee user")]
(try
(let [account-lookup (d/q '[:find ?ya ?ba ?c
:in $
:where [?ba :bank-account/yodlee-account-id ?ya]
[?c :client/bank-accounts ?ba]]
(d/db conn))]
(doseq [[yodlee-account bank-account client-id] account-lookup
transaction (client/get-specific-transactions yodlee-account)]
(log/info "importing")
(t/import-transaction! import-batch (assoc (yodlee->transaction transaction)
:transaction/bank-account bank-account
:transaction/client client-id)))
(t/finish! import-batch))
(catch Exception e
(t/fail! import-batch e))))))
(def import-yodlee (allow-once import-yodlee))
(mount/defstate import-worker
:start (scheduler/every (* 1000 60 60 4) import-yodlee)
:stop (scheduler/stop import-worker))

View File

@@ -0,0 +1,45 @@
(ns auto-ap.import.yodlee2
(:require [auto-ap.datomic :refer [conn]]
[auto-ap.import.transactions :as t]
[auto-ap.import.yodlee :as y]
[auto-ap.utils :refer [allow-once]]
[auto-ap.yodlee.core2 :as client2]
[datomic.api :as d]
[mount.core :as mount]
[unilog.context :as lc]
[yang.scheduler :as scheduler]))
(defn import-yodlee2 []
(lc/with-context {:source "Import yodlee2 transactions"}
(let [import-batch (t/start-import-batch :import-source/yodlee2 "Automated yodlee2 user")]
(try
(let [account-lookup (d/q '[:find ?ya ?ba ?cd
:in $
:where
[?ba :bank-account/yodlee-account ?y]
[?c :client/bank-accounts ?ba]
[?c :client/code ?cd]
[?y :yodlee-account/id ?ya]]
(d/db conn))]
(doseq [[yodlee-account bank-account client-code] account-lookup
transaction (client2/get-specific-transactions client-code yodlee-account)]
(t/import-transaction! import-batch (assoc (y/yodlee->transaction transaction)
:transaction/bank-account bank-account
:transaction/client [:client/code client-code])))
(t/finish! import-batch))
(catch Exception e
(t/fail! import-batch e))))))
(def import-yodlee2 (allow-once import-yodlee2))
(mount/defstate import-worker
:start (scheduler/every (* 1000 60 60 4) import-yodlee2)
:stop (scheduler/stop import-worker))
(mount/defstate account-worker
:start (scheduler/every (* 5 60 1000) client2/upsert-accounts)
:stop (scheduler/stop account-worker))