First cut at bulk coding

This commit is contained in:
2022-03-07 09:33:46 -08:00
parent 0f61cfa6cc
commit 52f8c08569
4 changed files with 202 additions and 141 deletions

View File

@@ -1,32 +1,31 @@
(ns auto-ap.graphql.transactions
(:require [auto-ap.datomic
:refer
[audit-transact audit-transact-batch conn remove-nils]]
[auto-ap.datomic.accounts :as a]
[auto-ap.datomic.checks :as d-checks]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.datomic.transaction-rules :as tr]
[auto-ap.datomic.transactions :as d-transactions]
[auto-ap.graphql.transaction-rules :as g-tr]
[auto-ap.graphql.utils
:refer
[->graphql
<-graphql
assert-admin
assert-can-see-client
assert-power-user
enum->keyword
ident->enum-f
snake->kebab]]
[auto-ap.import.transactions :as i-transactions]
[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]
[com.walmartlabs.lacinia.util :refer [attach-resolvers]]
[datomic.api :as d]))
(:require
[auto-ap.datomic
:refer [audit-transact audit-transact-batch conn remove-nils]]
[auto-ap.datomic.accounts :as a]
[auto-ap.datomic.checks :as d-checks]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.datomic.transaction-rules :as tr]
[auto-ap.datomic.transactions :as d-transactions]
[auto-ap.graphql.transaction-rules :as g-tr]
[auto-ap.graphql.utils
:refer [->graphql
<-graphql
assert-admin
assert-can-see-client
assert-power-user
enum->keyword
ident->enum-f
snake->kebab]]
[auto-ap.import.transactions :as i-transactions]
[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]
[com.walmartlabs.lacinia.util :refer [attach-resolvers]]
[datomic.api :as d]))
(def approval-status->graphql (ident->enum-f :transaction/approval-status))
@@ -37,7 +36,7 @@
(throw (ex-info "In order to select potential duplicates, you must choose a bank account."
{:validation-error "In order to select potential duplicates, you must choose a bank account."})))
(when-not (seq (->> filters
(filter (fn [[k v]]
(filter (fn [[_ v]]
(not (nil? v))))
(filter (comp (complement #{:id :start :sort :client_id :bank_account_id :potential_duplicates :per_page})
first))
@@ -49,7 +48,7 @@
(throw (ex-info "In order to select potential duplicates, you must filter your view more."
{:validation-error "In order to select potential duplicates, you must filter your view more."})))))
(defn get-transaction-page [context args value]
(defn get-transaction-page [context args _]
(let [args (assoc (:filters args) :id (:id context))
_ (assert-filtered-enough args)
[transactions transactions-count] (d-transactions/get-graphql (update (<-graphql args) :approval-status enum->keyword "transaction-approval-status"))
@@ -60,18 +59,20 @@
:start (:start args 0)
:end (+ (:start args 0) (count transactions))}))
(defn bulk-change-status [context args value]
(defn get-ids-matching-filters [args]
(let [ids (some-> (:filters args)
(<-graphql)
(update :approval-status enum->keyword "transaction-approval-status")
(assoc :per-page Integer/MAX_VALUE)
(d-transactions/raw-graphql-ids )
:ids)
specific-ids (d-transactions/filter-ids (:ids args))]
(into (set ids) specific-ids)))
(defn bulk-change-status [context args _]
(let [_ (assert-admin (:id context))
args (assoc args :id (:id context))
ids (some-> (:filters args)
(assoc :id (:id context))
(<-graphql)
(update :approval-status enum->keyword "transaction-approval-status")
(assoc :per-page Integer/MAX_VALUE)
(d-transactions/raw-graphql-ids )
:ids)
specific-ids (d-transactions/filter-ids (:ids args))
all-ids (into (set ids) specific-ids)]
all-ids (get-ids-matching-filters args)]
(log/info "Unapproving " (count all-ids) args)
@@ -83,18 +84,68 @@
(:id context))
{:message (str "Succesfully changed " (count all-ids) " transactions to be " (name (:status args) ) ".")}))
;; TODO very similar to rule-matching
(defn maybe-code-accounts [transaction account-rules valid-locations]
(with-precision 2
(let [accounts (vec (mapcat
(fn [ar]
(println ar)
(let [cents-to-distribute (int (Math/round (Math/abs (* (:percentage ar)
(:transaction/amount transaction)
100))))]
(if (= "Shared" (:location ar))
(do
(log/info "here" valid-locations)
(->> valid-locations
(map
(fn [cents location]
{:transaction-account/account (:account_id ar)
:transaction-account/amount (* 0.01 cents)
:transaction-account/location location})
(rm/spread-cents cents-to-distribute (count valid-locations)))))
[(cond-> {:transaction-account/account (:account_id ar)
:transaction-account/amount (* 0.01 cents-to-distribute)}
(:location ar) (assoc :transaction-account/location (:location ar)))])))
account-rules))
accounts (mapv
(fn [a]
(update a :transaction-account/amount
#(with-precision 2
(double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP)))))
accounts)
leftover (with-precision 2 (.round (bigdec (- (Math/abs (:transaction/amount transaction))
(Math/abs (reduce + 0.0 (map #(:transaction-account/amount %) accounts)))))
*math-context*))
accounts (if (seq accounts)
(update-in accounts [(dec (count accounts)) :transaction-account/amount] #(+ % (double leftover)))
[])]
[:reset (:db/id transaction) :transaction/accounts accounts])))
(defn delete-transactions [context args value]
(defn bulk-code-transactions [context args _]
(assert-admin (:id context))
(let [args (assoc args :id (:id context))
locations (:client/locations (d/pull (d/db conn)
[:client/locations]
(:client_id (:filters args))))
all-ids (get-ids-matching-filters args)
transactions (d/pull-many (d/db conn) '[:db/id :transaction/amount] (vec all-ids))]
(log/info "Bulk coding " (count all-ids) args)
(audit-transact-batch
(mapcat (fn [i]
(cond-> [(cond-> i
(:approval_status args) (assoc :transaction/approval-status (enum->keyword (:approval_status args) "transaction-approval-status"))
(:vendor args) (assoc :transaction/vendor (:vendor args)))]
(seq (:accounts args)) (conj (maybe-code-accounts i (:accounts args) locations))))
transactions)
(:id context))
{:message (str "Successfully coded " (count all-ids) " transactions.")}))
(defn delete-transactions [context args _]
(let [_ (assert-admin (:id context))
args (assoc args :id (:id context))
ids (some-> (:filters args)
(<-graphql)
(update :approval-status enum->keyword "transaction-approval-status")
(assoc :per-page Integer/MAX_VALUE)
(d-transactions/raw-graphql-ids )
:ids)
specific-ids (d-transactions/filter-ids (:ids args))
all-ids (into (set ids) specific-ids)
all-ids (get-ids-matching-filters args)
db (d/db conn)]
(log/info "Deleting " (count all-ids) args)
@@ -119,32 +170,29 @@
(:id context))
{:message (str "Succesfully deleted " (count all-ids) " transactions.")}))
(defn get-potential-autopay-invoices-matches [context args value]
(defn get-potential-autopay-invoices-matches [context args _]
(assert-power-user (:id context))
(let [transaction (d-transactions/get-by-id (:transaction_id args))
_ (assert-can-see-client (:id context) (:transaction/client transaction) )]
(let [matches-set (i-transactions/match-transaction-to-unfulfilled-autopayments (:transaction/amount transaction)
(:db/id (:transaction/client transaction)))]
(->graphql (for [matches matches-set]
(for [[_ invoice-id ] matches]
(d-invoices/get-by-id invoice-id)))))))
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
matches-set (i-transactions/match-transaction-to-unfulfilled-autopayments (:transaction/amount transaction)
(:db/id (:transaction/client transaction)))]
(->graphql (for [matches matches-set]
(for [[_ invoice-id ] matches]
(d-invoices/get-by-id invoice-id))))))
(defn get-potential-unpaid-invoices-matches [context args value]
(defn get-potential-unpaid-invoices-matches [context args _]
(assert-power-user (:id context))
(let [transaction (d-transactions/get-by-id (:transaction_id args))
_ (assert-can-see-client (:id context) (:transaction/client transaction) )]
(let [matches-set (i-transactions/match-transaction-to-unpaid-invoices (:transaction/amount transaction)
(:db/id (:transaction/client transaction)))]
(->graphql (for [matches matches-set]
(for [[_ invoice-id ] matches]
(d-invoices/get-by-id invoice-id)))))))
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
matches-set (i-transactions/match-transaction-to-unpaid-invoices (:transaction/amount transaction)
(:db/id (:transaction/client transaction)))]
(->graphql (for [matches matches-set]
(for [[_ invoice-id ] matches]
(d-invoices/get-by-id invoice-id))))))
(defn unlink-transaction [context args value]
(defn unlink-transaction [context args _]
(let [_ (assert-power-user (:id context))
args (assoc args :id (:id context))
transaction-id (:transaction_id args)
transaction (d/pull (d/db conn)
@@ -154,7 +202,6 @@
:transaction/vendor
:transaction/accounts
:transaction/client [:db/id]
{:transaction/payment [{:payment/status [:db/ident]} :db/id]} ]
transaction-id)
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
@@ -241,7 +288,7 @@
(when (empty? (:location trans-account))
(throw (ex-info "Account is missing location" {:validation-error "Account is missing location"})))
(when (and (not (empty? (:account/location account)))
(when (and (seq (:account/location account))
(not= (:location trans-account)
(:account/location account)))
(let [err (str "Account uses location '" (:location trans-account) "' but expects '" (:account/location account) "'")]
@@ -258,7 +305,7 @@
(when (nil? (:account_id trans-account))
(throw (ex-info "Account is missing account" {:validation-error "Account is missing account"})))))
(defn edit-transaction [context {{:keys [id accounts vendor_id approval_status forecast_match] :as transaction} :transaction} value]
(defn edit-transaction [context {{:keys [id accounts vendor_id approval_status forecast_match]} :transaction} _]
(let [existing-transaction (d-transactions/get-by-id id)
_ (assert-can-see-client (:id context) (:transaction/client existing-transaction) )
_ (assert-valid-expense-accounts accounts)
@@ -306,7 +353,7 @@
approval-status->graphql
->graphql)))
(defn match-transaction [context {:keys [transaction_id payment_id]} value]
(defn match-transaction [context {:keys [transaction_id payment_id]} _]
(let [transaction (d-transactions/get-by-id transaction_id)
payment (d-checks/get-by-id payment_id)
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
@@ -339,7 +386,7 @@
approval-status->graphql
->graphql))
(defn match-transaction-autopay-invoices [context {:keys [transaction_id autopay_invoice_ids]} value]
(defn match-transaction-autopay-invoices [context {:keys [transaction_id autopay_invoice_ids]} _]
(let [_ (assert-power-user (:id context))
transaction (d-transactions/get-by-id transaction_id)
_ (assert-can-see-client (:id context) (:transaction/client transaction) )
@@ -379,7 +426,7 @@
approval-status->graphql
->graphql)))
(defn match-transaction-unpaid-invoices [context {:keys [transaction_id unpaid_invoice_ids]} value]
(defn match-transaction-unpaid-invoices [context {:keys [transaction_id unpaid_invoice_ids]} _]
(let [_ (assert-power-user (:id context))
transaction (d-transactions/get-by-id transaction_id)
@@ -415,7 +462,7 @@
approval-status->graphql
->graphql)))
(defn match-transaction-rules [context {:keys [transaction_ids transaction_rule_id all]} value]
(defn match-transaction-rules [context {:keys [transaction_ids transaction_rule_id all]} _]
(let [_ (assert-admin (:id context))
transaction_ids (if all
(->> (g-tr/run-transaction-rule context {:transaction_rule_id transaction_rule_id
@@ -509,8 +556,15 @@
:status {:type :transaction_approval_status}
:ids {:type '(list :id)}}
:resolve :mutation/bulk-change-transaction-status}
:delete_transactions {:type :message
:args {:filters {:type :transaction_filters}
:bulk_code_transactions {:type :message
:args {:filters {:type :transaction_filters}
:vendor {:type :id}
:approval_status {:type :transaction_approval_status}
:accounts {:type '(list :edit_percentage_account)}
:ids {:type '(list :id)}}
:resolve :mutation/bulk-code-transactions}
:delete_transactions {:type :message
:args {:filters {:type :transaction_filters}
:ids {:type '(list :id)}
:suppress {:type 'Boolean}}
:resolve :mutation/delete-transactions}
@@ -583,6 +637,7 @@
:mutation/unlink-transaction unlink-transaction
:mutation/bulk-change-transaction-status bulk-change-status
:mutation/delete-transactions delete-transactions
:mutation/bulk-code-transactions bulk-code-transactions
:mutation/match-transaction match-transaction
:mutation/match-transaction-autopay-invoices match-transaction-autopay-invoices
:mutation/match-transaction-unpaid-invoices match-transaction-unpaid-invoices

View File

@@ -1,17 +1,4 @@
(ns auto-ap.rule-matching
(:require [auto-ap.yodlee.core :as client]
[auto-ap.utils :refer [by]]
[datomic.api :as d]
[auto-ap.datomic :refer [uri remove-nils]]
[auto-ap.datomic.accounts :as a]
[clj-time.coerce :as coerce]
[digest :refer [sha-256]]
[auto-ap.datomic.checks :as d-checks]
[auto-ap.datomic.transactions :as d-transactions]
[auto-ap.datomic.clients :as d-clients]
[auto-ap.time :as time]
[auto-ap.datomic.transaction-rules :as tr]
[clojure.tools.logging :as log]))
(ns auto-ap.rule-matching)
(defn ->pattern [x]
(. java.util.regex.Pattern (compile x java.util.regex.Pattern/CASE_INSENSITIVE)))
@@ -101,7 +88,7 @@
(filter #(rule-applies? transaction %))))
(defn spread-cents [cents n]
(let [default-spread (for [x (range n)]
(let [default-spread (for [_ (range n)]
(int (* cents (/ 1.0 n))))
short-by (- cents (reduce + 0 default-spread)) ;; amount that was lost in the differenc
adjusted-spread (map
@@ -157,5 +144,4 @@
(let [matching-rules (get-matching-rules-by-priority rules-by-priority transaction )]
(if-let [top-match (and (= (count matching-rules) 1) (first matching-rules))]
(apply-rule transaction top-match valid-locations)
transaction))))))