223 lines
12 KiB
Clojure
223 lines
12 KiB
Clojure
(ns auto-ap.graphql.transaction-rules
|
|
(:require [auto-ap.datomic
|
|
:refer
|
|
[audit-transact merge-query remove-nils replace-nils-with-retract uri conn]]
|
|
[auto-ap.datomic.transaction-rules :as tr]
|
|
[auto-ap.datomic.transactions :as d-transactions]
|
|
[auto-ap.graphql.utils
|
|
:refer
|
|
[->graphql
|
|
<-graphql
|
|
assert-admin
|
|
ident->enum-f
|
|
limited-clients
|
|
result->page
|
|
snake->kebab]]
|
|
[auto-ap.rule-matching :as rm]
|
|
[auto-ap.utils :refer [dollars=]]
|
|
[clj-time.coerce :as coerce]
|
|
[clojure.set :as set]
|
|
[clojure.string :as str]
|
|
[clojure.tools.logging :as log]
|
|
[datomic.api :as d]
|
|
[clj-time.coerce :as c])
|
|
(:import java.time.temporal.ChronoField))
|
|
|
|
(defn get-transaction-rule-page [context args value]
|
|
(let [args (assoc args :id (:id context))
|
|
[journal-entries journal-entries-count] (tr/get-graphql (<-graphql args))]
|
|
(result->page (->> journal-entries
|
|
(map (ident->enum-f :transaction-rule/transaction-approval-status)))
|
|
journal-entries-count :transaction_rules args)))
|
|
|
|
(defn get-transaction-rule-matches [context args value]
|
|
(if (= "admin" (:user/role (:id context)))
|
|
(let [all-rules (tr/get-all)
|
|
transaction (update (d-transactions/get-by-id (:transaction_id args)) :transaction/date coerce/to-date)]
|
|
(map ->graphql (rm/get-matching-rules transaction all-rules)))
|
|
nil))
|
|
|
|
(defn deleted-accounts [transaction accounts]
|
|
(let [current-accounts (:transaction-rule/accounts transaction)
|
|
specified-ids (->> accounts
|
|
(map :id)
|
|
set)
|
|
existing-ids (->> current-accounts
|
|
(map :db/id)
|
|
set)]
|
|
(set/difference existing-ids specified-ids)))
|
|
|
|
(defn transaction-rule-account->entity [{:keys [id account_id percentage location]}]
|
|
(remove-nils #:transaction-rule-account {:percentage percentage
|
|
:db/id id
|
|
:account account_id
|
|
:location location}))
|
|
|
|
(defn delete-transaction-rule [context {:keys [transaction_rule_id ]} value]
|
|
(assert-admin (:id context))
|
|
(let [existing-transaction-rule (tr/get-by-id transaction_rule_id)]
|
|
(when-not (:transaction-rule/description existing-transaction-rule)
|
|
(throw (ex-info "Transaction rule not found" {:validation-error "Transaction Rule not found"})))
|
|
|
|
(audit-transact [[:db/retractEntity transaction_rule_id]] (:id context))
|
|
transaction_rule_id))
|
|
|
|
(defn upsert-transaction-rule [context {{:keys [id description yodlee_merchant_id note client_id bank_account_id amount_lte amount_gte vendor_id accounts transaction_approval_status dom_gte dom_lte]} :transaction_rule :as z} value]
|
|
(assert-admin (:id context))
|
|
(let [existing-transaction (tr/get-by-id id)
|
|
deleted (deleted-accounts existing-transaction accounts)
|
|
account-total (reduce + 0 (map (fn [x] (:percentage x)) accounts))
|
|
_ (when-not (dollars= 1.0 account-total)
|
|
(let [error (str "Account total (" account-total ") does not reach 100%")]
|
|
(throw (ex-info error {:validation-error error}))))
|
|
_ (when (and (str/blank? description)
|
|
(nil? yodlee_merchant_id))
|
|
(let [error (str "You must provide a description or a yodlee merchant")]
|
|
(throw (ex-info error {:validation-error error}))))
|
|
_ (doseq [a accounts
|
|
:let [{:keys [:account/location :account/name] :as account} (d/entity (d/db conn) (:account_id a))
|
|
client (d/entity (d/db conn) client_id)
|
|
]]
|
|
(when (and location (not= location (:location a)))
|
|
(let [err (str "Account " name " uses location " (:location a) ", but is supposed to be " location)]
|
|
(throw (ex-info err {:validation-error err}) )))
|
|
|
|
(when (and (not location)
|
|
(not (get (into #{"Shared"} (:client/locations client))
|
|
(:location a))))
|
|
(let [err (str "Account " name " uses location " (:location a) ", but doesn't belong to the client.")]
|
|
(throw (ex-info err {:validation-error err}) ))))
|
|
rule-id (if id
|
|
id
|
|
"transaction-rule")
|
|
transaction (replace-nils-with-retract #:transaction-rule {:db/id rule-id
|
|
:description description
|
|
:note note
|
|
:client client_id
|
|
:bank-account bank_account_id
|
|
:yodlee-merchant yodlee_merchant_id
|
|
:dom-lte dom_lte
|
|
:dom-gte dom_gte
|
|
:amount-lte amount_lte
|
|
:amount-gte amount_gte
|
|
:vendor vendor_id
|
|
:transaction-approval-status
|
|
(some->> transaction_approval_status
|
|
name
|
|
snake->kebab
|
|
(keyword "transaction-approval-status"))}
|
|
existing-transaction)
|
|
|
|
|
|
transaction (conj transaction
|
|
[:reset rule-id :transaction-rule/accounts (map transaction-rule-account->entity accounts)])
|
|
transaction-result (audit-transact transaction (:id context))]
|
|
(-> (tr/get-by-id (or (-> transaction-result :tempids (get "transaction-rule"))
|
|
id))
|
|
((ident->enum-f :transaction-rule/transaction-approval-status))
|
|
(->graphql))))
|
|
|
|
(defn tr [z x]
|
|
(re-find (re-pattern z) x))
|
|
|
|
(defn -test-transaction-rule [id {:keys [:transaction-rule/description :transaction-rule/note :transaction-rule/client :transaction-rule/bank-account :transaction-rule/amount-lte :transaction-rule/amount-gte :transaction-rule/dom-lte :transaction-rule/dom-gte :transaction-rule/yodlee-merchant]} include-coded? count]
|
|
(->>
|
|
(d/query
|
|
(cond-> {:query {:find ['(pull ?e [* {:transaction/client [:client/name]
|
|
:transaction/bank-account [:bank-account/name]
|
|
:transaction/payment [:db/id]}
|
|
])]
|
|
:in ['$ ]
|
|
:where []}
|
|
:args [(d/db conn)]}
|
|
|
|
(limited-clients id)
|
|
(merge-query {:query {:in ['[?xx ...]]
|
|
:where ['[?e :transaction/client ?xx]]}
|
|
:args [(set (map :db/id (limited-clients id)))]})
|
|
|
|
bank-account
|
|
(merge-query {:query {:in ['?bank-account-id]
|
|
:where ['[?e :transaction/bank-account ?bank-account-id]]}
|
|
:args [(:db/id bank-account)]})
|
|
|
|
description
|
|
(merge-query {:query {:in ['?description-regex]
|
|
:where ['[?e :transaction/description-original ?do]
|
|
'[(re-find ?description-regex ?do)]]}
|
|
:args [(rm/->pattern description)]})
|
|
|
|
yodlee-merchant
|
|
(merge-query {:query {:in ['?yodlee-merchant-id]
|
|
:where ['[?e :transaction/yodlee-merchant ?yodlee-merchant-id]]}
|
|
:args [(:db/id yodlee-merchant)]})
|
|
|
|
amount-gte
|
|
(merge-query {:query {:in ['?amount-gte]
|
|
:where ['[?e :transaction/amount ?ta]
|
|
'[(>= ?ta ?amount-gte)]]}
|
|
:args [amount-gte]})
|
|
|
|
amount-lte
|
|
(merge-query {:query {:in ['?amount-lte]
|
|
:where ['[?e :transaction/amount ?ta]
|
|
'[(<= ?ta ?amount-lte)]]}
|
|
:args [amount-lte]})
|
|
|
|
dom-lte
|
|
(merge-query {:query {:in ['?dom-lte]
|
|
:where ['[?e :transaction/date ?transaction-date]
|
|
'[(.toInstant ^java.util.Date ?transaction-date ) ?transaction-instant]
|
|
'[(.atZone ^java.time.Instant ?transaction-instant (java.time.ZoneId/of "US/Pacific")) ?transaction-local]
|
|
'[(.get ?transaction-local java.time.temporal.ChronoField/DAY_OF_MONTH) ?dom]
|
|
'[(<= ?dom ?dom-lte)]]}
|
|
:args [dom-lte]})
|
|
|
|
dom-gte
|
|
(merge-query {:query {:in ['?dom-gte]
|
|
:where ['[?e :transaction/date ?transaction-date]
|
|
'[(.toInstant ^java.util.Date ?transaction-date ) ?transaction-instant]
|
|
'[(.atZone ^java.time.Instant ?transaction-instant (java.time.ZoneId/of "US/Pacific")) ?transaction-local]
|
|
'[(.get ?transaction-local java.time.temporal.ChronoField/DAY_OF_MONTH) ?dom]
|
|
'[(>= ?dom ?dom-gte)]]}
|
|
:args [dom-gte]})
|
|
|
|
client
|
|
(merge-query {:query {:in ['?client-id]
|
|
:where ['[?e :transaction/client ?client-id]]}
|
|
:args [(:db/id client)]})
|
|
|
|
|
|
(not include-coded?)
|
|
(merge-query {:query {:where ['[or [?e :transaction/approval-status :transaction-approval-status/unapproved]
|
|
[(missing? $ ?e :transaction/approval-status)]]]}})
|
|
|
|
true
|
|
(merge-query {:query {:where ['[?e :transaction/id]]}})))
|
|
|
|
(transduce (comp
|
|
(take (or count 15))
|
|
(map first)
|
|
(map #(dissoc % :transaction/id))
|
|
(map (fn [x]
|
|
(update x :transaction/date c/from-date)))
|
|
(map ->graphql))
|
|
conj [])))
|
|
|
|
(defn test-transaction-rule [{:keys [id]} {{:keys [description note client_id bank_account_id amount_lte amount_gte dom_lte dom_gte yodlee_merchant_id]} :transaction_rule :as z} value]
|
|
(assert-admin id)
|
|
(-test-transaction-rule id #:transaction-rule {:description description
|
|
:client (when client_id {:db/id client_id})
|
|
:bank-account (when bank_account_id {:db/id bank_account_id})
|
|
:amount-lte amount_lte
|
|
:amount-gte amount_gte
|
|
:dom-lte dom_lte
|
|
:dom-gte dom_gte
|
|
:yodlee-merchant (when yodlee_merchant_id {:db/id yodlee_merchant_id})}
|
|
true 15))
|
|
|
|
|
|
(defn run-transaction-rule [{:keys [id]} {:keys [transaction_rule_id count]} value]
|
|
(assert-admin id)
|
|
(-test-transaction-rule id (tr/get-by-id transaction_rule_id) false count))
|