Files
integreat/src/clj/auto_ap/datomic/transactions.clj

271 lines
15 KiB
Clojure

(ns auto-ap.datomic.transactions
(:require
[auto-ap.datomic
:refer [add-sorter-fields
apply-pagination
apply-sort-3
conn
merge-query
observable-query
pull-many]]
[auto-ap.datomic.accounts :as d-accounts]
[auto-ap.graphql.utils :refer [extract-client-ids]]
[clj-time.coerce :as coerce]
[clojure.string :as str]
[datomic.api :as dc]
[clj-time.core :as time]))
(defn potential-duplicate-ids [db args]
(when (and (:potential-duplicates args)
(:bank-account-id args))
(->> (dc/q '[:find ?tx ?amount ?date
:in $ ?ba
:where
[?tx :transaction/bank-account ?ba]
[?tx :transaction/amount ?amount]
[?tx :transaction/date ?date]
(not [?tx :transaction/approval-status :transaction-approval-status/suppressed])]
db
(:bank-account-id args))
(group-by (fn [[_ amount date]]
[amount date]))
(filter (fn [[_ txes]]
(> (count txes) 1)))
(vals)
(mapcat identity)
(map first)
set)))
(defn raw-graphql-ids
([args] (raw-graphql-ids (dc/db conn) args))
([db args]
(let [valid-clients (extract-client-ids (:clients args)
(:client-id args)
(when (:client-code args)
[:client/code (:client-code args)]))
potential-duplicates (potential-duplicate-ids db args)
query
(if (:exact-match-id args)
(cond-> {:query {:find '[?e]
:in '[$ ?e [?c ...]]
:where '[[?e :transaction/client ?c]]}
:args [db
(:exact-match-id args)
valid-clients]})
(cond-> {:query {:find []
:in '[$ [?clients ?start ?end]]
:where '[[(iol-ion.query/scan-transactions $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]]}
:args [db
[valid-clients
(some-> (:start (:date-range args)) coerce/to-date)
(some-> (:end (:date-range args)) coerce/to-date)]]}
(:potential-duplicates args)
(merge-query {:query {:in '[[?e ...]]}
:args [potential-duplicates]})
(:bank-account-id args)
(merge-query {:query {:in ['?bank-account-id]
:where ['[?e :transaction/bank-account ?bank-account-id]]}
:args [(:bank-account-id args)]})
(:import-batch-id args)
(merge-query {:query {:in ['?import-batch-id]
:where ['[?import-batch-id :import-batch/entry ?e]]}
:args [(:import-batch-id args)]})
(:account-id args)
(merge-query {:query {:in ['?account-id]
:where ['[?e :transaction/accounts ?accounts]
'[?accounts :transaction-account/account ?account-id]]}
:args [(:account-id args)]})
(:vendor-id args)
(merge-query {:query {:in ['?vendor-id]
:where ['[?e :transaction/vendor ?vendor-id]]}
:args [(:vendor-id args)]})
(:amount-gte args)
(merge-query {:query {:in ['?amount-gte]
:where ['[?e :transaction/amount ?a]
'[(>= ?a ?amount-gte)]]}
:args [(:amount-gte args)]})
(:amount-lte args)
(merge-query {:query {:in ['?amount-lte]
:where ['[?e :transaction/amount ?a]
'[(<= ?a ?amount-lte)]]}
:args [(:amount-lte args)]})
(:approval-status args)
(merge-query {:query {:in ['?approval-status]
:where ['[?e :transaction/approval-status ?approval-status]]}
:args [(:approval-status args)]})
(= (:linked-to args) :payment)
(merge-query {:query {:where ['[?e :transaction/payment]]}})
(= (:linked-to args) :expected-deposit)
(merge-query {:query {:where ['[?e :transaction/expected-deposit]]}})
(= (:linked-to args) :invoice)
(merge-query {:query {:where ['[?e :transaction/payment ?p]
'[_ :invoice-payment/payment ?p]]}})
(= (:linked-to args) :none)
(merge-query {:query {:where ['(not [?e :transaction/payment])
'(not [?e :transaction/expected-deposit])]}})
(:original-id args)
(merge-query {:query {:in ['?original-id]
:where ['[?e :transaction/client ?c]
'[?c :client/original-id ?original-id]]}
:args [(:original-id args)]})
(seq (:location args))
(merge-query {:query {:in ['?location]
:where ['[?e :transaction/accounts ?tas]
'[?tas :transaction-account/location ?location]]}
:args [(:location args)]})
(:unresolved args)
(merge-query {:query {:where ['[?e :transaction/date]
'(or-join [?e]
(not [?e :transaction/accounts])
(and [?e :transaction/accounts ?tas]
(not [?tas :transaction-account/account])))]}})
(:description args)
(merge-query {:query {:in ['?description]
:where ['[?e :transaction/description-original ?do]
'[(clojure.string/lower-case ?do) ?do2]
'[(.contains ?do2 ?description)]]}
:args [(clojure.string/lower-case (:description args))]})
(:sort args) (add-sorter-fields {"client" ['[?e :transaction/client ?c]
'[?c :client/name ?sort-client]]
"account" ['[?e :transaction/date]
'(or-join [?e ?sort-account]
(and [?e :transaction/bank-account ?c]
[?c :bank-account/name ?sort-account])
(and
(not [?e :transaction/bank-account])
[(ground "") ?sort-account]))]
"description-original" ['[?e :transaction/description-original ?sort-description-original]]
"date" ['[?e :transaction/date ?sort-date]]
"vendor" ['(or-join [?e ?sort-vendor]
(and [(missing? $ ?e :transaction/vendor)]
[?e :transaction/description-original ?sort-vendor])
(and [?e :transaction/vendor ?v]
[?v :vendor/name ?sort-vendor]))]
"amount" ['[?e :transaction/amount ?sort-amount]]
"status" ['[?e :transaction/status ?sort-status]]}
args)
true
(merge-query {:query {:find ['?sort-default '?e]
:where ['[?e :transaction/id]
'(not [?e :transaction/approval-status :transaction-approval-status/suppressed])]}})))]
(cond->> (observable-query query)
true (apply-sort-3 (assoc args :default-asc? false))
true (apply-pagination args)))))
(defn is-locked? [transaction]
(let [transaction-date (some-> transaction :transaction/date coerce/to-date-time)
bank-account-start-date (some-> transaction :transaction/bank-account :bank-account/start-date coerce/to-date-time)
client-locked-until (some-> transaction :transaction/client :client/locked-until coerce/to-date-time)
locked-by-client? (cond (not transaction-date) false
(not client-locked-until) false
:else (time/before? transaction-date client-locked-until))
locked-by-bank-account? (cond (not transaction-date) false
(not bank-account-start-date) false
:else (time/before? transaction-date bank-account-start-date))]
(or locked-by-bank-account? locked-by-client?)))
(defn graphql-results [ids db _]
(let [results (->> (pull-many db '[* {:transaction/client [:client/name :db/id :client/code :client/locked-until]
:transaction/approval-status [:db/ident :db/id]
:transaction/bank-account [:bank-account/name :bank-account/code :bank-account/yodlee-account-id :db/id :bank-account/locations :bank-account/current-balance :bank-account/start-date]
:transaction/forecast-match [:db/id :forecasted-transaction/identifier]
:transaction/vendor [:db/id :vendor/name]
:transaction/matched-rule [:db/id :transaction-rule/note]
:transaction/payment [:db/id :payment/date]
:transaction/expected-deposit [:db/id :expected-deposit/date]
:transaction/accounts [:transaction-account/amount
:db/id
:transaction-account/location
{:transaction-account/account [:account/name :db/id
:account/location
{:account/client-overrides [:account-client-override/name
{:account-client-override/client [:db/id]}]}]}]
:transaction/yodlee-merchant [:db/id :yodlee-merchant/yodlee-id :yodlee-merchant/name]
:transaction/plaid-merchant [:db/id :plaid-merchant/name]}]
ids)
(map #(assoc % :transaction/is-locked (is-locked? %)))
(map #(update % :transaction/date coerce/from-date))
(map #(update % :transaction/post-date coerce/from-date))
(map (fn [t]
(let [cn (:transaction/check-number t)]
(if (and cn (nat-int? cn) (> cn Integer/MAX_VALUE))
(dissoc t :transaction/check-number)
t))))
(map #(update % :transaction/accounts
(fn [tas]
(map
(fn [ta]
(update ta :transaction-account/account d-accounts/clientize (:db/id (:transaction/client %))))
tas))))
(map (fn [transaction]
(cond-> transaction
(:transaction/payment transaction) (update-in [:transaction/payment :payment/date] coerce/from-date)
(:transaction/expected-deposit transaction) (update-in [:transaction/expected-deposit :expected-deposit/date] coerce/from-date))))
(map #(dissoc % :transaction/id))
(group-by :db/id))]
(->> ids
(map results)
(map first))))
(defn get-graphql [args]
(let [db (dc/db conn)
{ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)]
[(->> (graphql-results ids-to-retrieve db args))
matching-count]))
(defn filter-ids [ids]
(if ids
(->> (dc/q {:find ['?e]
:in ['$ '[?e ...]]
:where ['[?e :transaction/date]]}
(dc/db conn) ids)
(map first)
vec)
[]))
(def default-read '[* {:transaction/client [:client/name :db/id :client/code :client/locations :client/groups]
:transaction/approval-status [:db/ident :db/id]
:transaction/bank-account [:bank-account/name :bank-account/code :bank-account/yodlee-account-id :db/id :bank-account/locations :bank-account/current-balance]
:transaction/vendor [:db/id :vendor/name]
:transaction/matched-rule [:db/id :transaction-rule/note]
:transaction/forecast-match [:db/id :forecasted-transaction/identifier]
:transaction/accounts [:transaction-account/amount
:db/id
:transaction-account/location
{:transaction-account/account [:account/name :db/id
:account/location
{:account/client-overrides [:account-client-override/name
{:account-client-override/client [:db/id]}]}]}]
:transaction/yodlee-merchant [:db/id :yodlee-merchant/yodlee-id :yodlee-merchant/name]
:transaction/plaid-merchant [:db/id :plaid-merchant/name]}])
(defn get-by-id [id]
(->
(dc/pull (dc/db conn) default-read id)
(update :transaction/date coerce/from-date)
(update :transaction/post-date coerce/from-date)
(dissoc :transaction/id)))