(ns auto-ap.graphql.transaction-rules (:require [auto-ap.datomic :refer [audit-transact conn merge-query query2]] [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 c] [clojure.string :as str] [com.brunobonacci.mulog :as mu] [datomic.api :as dc] [iol-ion.tx :refer [random-tempid]])) (defn get-transaction-rule-page [context args _] (let [args (assoc args :clients (:clients 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 _] (if (= "admin" (:user/role (:id context))) (let [transaction (update (d-transactions/get-by-id (:transaction_id args)) :transaction/date c/to-date) all-rules (tr/get-all-for-client (:db/id (:transaction/client transaction))) ] (mu/log ::counted :count (count all-rules)) (map ->graphql (rm/get-matching-rules transaction all-rules))) nil)) (defn transaction-rule-account->entity [{:keys [id account_id percentage location]}] #:transaction-rule-account {:percentage percentage :db/id (or id (random-tempid)) :account account_id :location location}) (defn delete-transaction-rule [context {:keys [transaction_rule_id ]} _] (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} _] (assert-admin (:id context)) (let [account-total (reduce + 0 (map (fn [x] (:percentage x)) accounts)) _ (try (. java.util.regex.Pattern (compile description java.util.regex.Pattern/CASE_INSENSITIVE)) (catch Exception e (throw (ex-info (ex-message e) {:validation-error (ex-message e)})))) _ (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]} (dc/pull (dc/db conn) [:account/location :account/name] (:account_id a)) client (dc/pull (dc/db conn) [:client/locations] 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 [[:upsert-entity #:transaction-rule {:db/id (or rule-id (random-tempid)) :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")) :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 -test-transaction-rule [id {:keys [:transaction-rule/description :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] (let [query (cond-> {:query {:find ['(pull ?e [* {:transaction/client [:client/name] :transaction/bank-account [:bank-account/name] :transaction/payment [:db/id]} ])] :in ['$ ] :where []} :args [(dc/db conn)]} description (merge-query {:query {:in ['?descr] :where ['[(iol-ion.query/->pattern ?descr) ?description-regex]]} :args [description]}) (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 {:where ['[?e :transaction/description-original ?do] '[(re-find ?description-regex ?do)]]}}) 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] '[(iol-ion.query/dom ?transaction-date) ?dom] '[(<= ?dom ?dom-lte)]]} :args [dom-lte]}) dom-gte (merge-query {:query {:in ['?dom-gte] :where ['[?e :transaction/date ?transaction-date] '[(iol-ion.query/dom ?transaction-date) ?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]]}}))] (->> (query2 query) (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 client_id bank_account_id amount_lte amount_gte dom_lte dom_gte yodlee_merchant_id]} :transaction_rule} _] (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]} _] (assert-admin id) (-test-transaction-rule id (tr/get-by-id transaction_rule_id) false count))