This commit is contained in:
2023-03-26 20:43:46 -07:00
5 changed files with 433 additions and 3 deletions

View File

@@ -0,0 +1,351 @@
(ns auto-ap.integration.graphql.transactions
(:require
[auto-ap.datomic :refer [conn]]
[auto-ap.graphql.transactions :as sut]
[auto-ap.integration.util
:refer [admin-token
setup-test-data
test-bank-account
test-client
test-payment
test-transaction
test-transaction-rule
test-invoice
user-token
wrap-setup]]
[clojure.test :as t :refer [deftest is testing use-fixtures]]
[datomic.client.api :as dc]))
(use-fixtures :each wrap-setup)
(deftest get-transaction-page
(testing "Should list transactions"
(let [{:strs [transaction-id
test-client-id]} (setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/client "test-client-id"
:transaction/bank-account "test-bank-account-id")])]
(is (= 1 (:total (sut/get-transaction-page {:id (admin-token)} {} nil))))
(is (= transaction-id (:id (first (:data (sut/get-transaction-page {:id (admin-token)} {} nil))))))
(testing "Should only show transactions you have access to"
(is (= 1 (:total (sut/get-transaction-page {:id (admin-token)} {} nil))))
(is (= 1 (:total (sut/get-transaction-page {:id (user-token test-client-id)} {} nil))))
(is (= 0 (:total (sut/get-transaction-page {:id (user-token 1)} {} nil))))
(is (= 1 (:total (sut/get-transaction-page {:id (admin-token)} {:filters {:client_id test-client-id}} nil))))
(is (= 0 (:total (sut/get-transaction-page {:id (admin-token)} {:filters {:client_id 1}} nil))))
(is (= 1 (:total (sut/get-transaction-page {:id (user-token test-client-id)} {:filters {:client_id test-client-id}} nil))))
(is (= 0 (:total (sut/get-transaction-page {:id (user-token 1)} {:filters {:client_id test-client-id}} nil)))))
(testing "Should only show potential duplicates if filtered enough"
(is (thrown? Exception (:total (sut/get-transaction-page {:id (admin-token)} {:filters {:potential_duplicates true}} nil))))))))
(deftest bulk-change-status
(testing "Should change status of multiple transactions"
(let [{:strs [transaction-id
test-client-id]} (setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/client "test-client-id"
:transaction/approval-status :transaction-approval-status/approved
:transaction/bank-account "test-bank-account-id")])]
(is (= "Succesfully changed 1 transactions to be unapproved."
(:message (sut/bulk-change-status {:id (admin-token)} {:filters {:client_id test-client-id}
:status :unapproved} nil))))
(is (= :transaction-approval-status/unapproved
(:db/ident (:transaction/approval-status (dc/pull (dc/db conn) '[{:transaction/approval-status [:db/ident]}] transaction-id)))))
(testing "Only admins should be able to change the status"
(is (thrown? Exception (sut/bulk-change-status {:id (user-token test-client-id)}
{:filters {:client_id test-client-id}
:status :unapproved} nil)))))))
(deftest bulk-code-transactions
(testing "Should code transactions"
(let [{:strs [transaction-id
test-client-id
test-account-id
test-vendor-id]} (setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/client "test-client-id"
:transaction/bank-account "test-bank-account-id"
:transaction/amount 40.0)])]
(is (= "Successfully coded 1 transactions."
(:message (sut/bulk-code-transactions {:id (admin-token)}
{:filters {:client_id test-client-id}
:client_id test-client-id
:vendor test-vendor-id
:approval_status :unapproved
:accounts [{:account_id test-account-id
:location "DT"
:percentage 1.0}]} nil))))
(is (= #:transaction{:vendor {:db/id test-vendor-id}
:approval-status {:db/ident :transaction-approval-status/unapproved}
:accounts [#:transaction-account{:account {:db/id test-account-id}
:location "DT"
:amount 40.0}]}
(dc/pull (dc/db conn) '[:transaction/vendor
{:transaction/approval-status [:db/ident]
:transaction/accounts [:transaction-account/account
:transaction-account/location
:transaction-account/amount]}]
transaction-id))))))
(deftest edit-transactions
(testing "Should edit transactions"
(let [{:strs [transaction-id
test-account-id
test-vendor-id]} (setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/client "test-client-id"
:transaction/bank-account "test-bank-account-id"
:transaction/amount 40.0)])]
(sut/edit-transaction {:id (admin-token)}
{:transaction {:id transaction-id
:vendor_id test-vendor-id
:approval_status :approved
:accounts [{:account_id test-account-id
:location "DT"
:amount 40.0}]}} nil)
(is (= #:transaction{:vendor {:db/id test-vendor-id}
:approval-status {:db/ident :transaction-approval-status/approved}
:accounts [#:transaction-account{:account {:db/id test-account-id}
:location "DT"
:amount 40.0}]}
(dc/pull (dc/db conn) '[:transaction/vendor
{:transaction/approval-status [:db/ident]
:transaction/accounts [:transaction-account/account
:transaction-account/location
:transaction-account/amount]}]
transaction-id)))
(testing "Should prevent saves with bad accounts"
(is (thrown? Exception
(sut/edit-transaction {:id (admin-token)}
{:transaction {:id transaction-id
:vendor_id test-vendor-id
:approval_status :approved
:accounts [{:account_id test-account-id
:location "DT"
:amount 20.0}]}} nil)))))))
(deftest match-transaction
(testing "Should link a transaction to a payment, mark it as accounts payable"
(let [{:strs [transaction-id
test-vendor-id
accounts-payable-id
payment-id]} (setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/client "test-client-id"
:transaction/bank-account "test-bank-account-id"
:transaction/amount -50.0)
(test-payment :db/id "payment-id"
:payment/client "test-client-id"
:payment/vendor "test-vendor-id"
:payment/bank-account "test-bank-account-id"
:payment/amount 50.0)])]
(sut/match-transaction {:id (admin-token)} {:transaction_id transaction-id :payment_id payment-id} nil)
(is (= #:transaction{:vendor {:db/id test-vendor-id}
:approval-status {:db/ident :transaction-approval-status/approved}
:payment {:db/id payment-id}
:accounts [#:transaction-account{:account {:db/id accounts-payable-id}
:location "A"
:amount 50.0}]}
(dc/pull (dc/db conn) '[:transaction/vendor
:transaction/payment
{:transaction/approval-status [:db/ident]
:transaction/accounts [:transaction-account/account
:transaction-account/location
:transaction-account/amount]}]
transaction-id)))))
(testing "Should prevent linking a payment if they don't match"
(let [{:strs [transaction-id
mismatched-amount-payment-id
mismatched-bank-account-payment-id]}
(setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/client "test-client-id"
:transaction/bank-account "test-bank-account-id"
:transaction/amount -50.0)
(test-payment :db/id "mismatched-amount-payment-id"
:payment/client "test-client-id"
:payment/vendor "test-vendor-id"
:payment/bank-account "test-bank-account-id"
:payment/amount 30.0)
(test-client :db/id "mismatched-client-id"
:client/bank-accounts [(test-bank-account :db/id "mismatched-bank-account-id")])
(test-payment :db/id "mismatched-bank-account-payment-id"
:payment/client "mismatched-client-id"
:payment/vendor "test-vendor-id"
:payment/bank-account "mismatched-bank-account-id"
:payment/amount 50.0)])]
(is (thrown? Exception (sut/match-transaction {:id (admin-token)} {:transaction_id transaction-id :payment_id mismatched-amount-payment-id} nil)))
(is (thrown? Exception (sut/match-transaction {:id (admin-token)} {:transaction_id transaction-id :payment_id mismatched-bank-account-payment-id} nil)))
)))
(deftest match-transaction-autopay-invoices
(testing "Should link transaction to a set of autopaid invoices"
(let [{:strs [transaction-id
test-vendor-id
invoice-1
invoice-2
]} (setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/amount -50.0)
(test-invoice :db/id "invoice-1"
:invoice/total 30.0)
(test-invoice :db/id "invoice-2"
:invoice/total 20.0)])]
(sut/match-transaction-autopay-invoices {:id (admin-token)} {:transaction_id transaction-id :autopay_invoice_ids [invoice-1 invoice-2]} nil)
(let [result (dc/pull (dc/db conn) '[:transaction/vendor
{:transaction/payment [:db/id {:payment/status [:db/ident]}]}
{:transaction/approval-status [:db/ident]
:transaction/accounts [:transaction-account/account
:transaction-account/location
:transaction-account/amount]}
]
transaction-id)]
(testing "should have created a payment"
(is (some? (:transaction/payment result)))
(is (= :payment-status/cleared (-> result
:transaction/payment
:payment/status
:db/ident)))
(is (= :transaction-approval-status/approved (-> result :transaction/approval-status :db/ident))))
(testing "Should have completed the invoice"
(is (= :invoice-status/paid (->> invoice-1
(dc/pull (dc/db conn) '[{:invoice/status [:db/ident]}])
:invoice/status
:db/ident)))
(is (= :invoice-status/paid (->> invoice-2
(dc/pull (dc/db conn) '[{:invoice/status [:db/ident]}])
:invoice/status
:db/ident)))))))
(testing "Should prevent a transaction from linking to an incorrectly balanced invoice"
(let [{:strs [transaction-id
test-vendor-id
invoice-1
invoice-2
]} (setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/amount -50.0)
(test-invoice :db/id "invoice-1"
:invoice/total 30.0)])]
(is (thrown? Exception (sut/match-transaction-autopay-invoices {:id (admin-token)} {:transaction_id transaction-id :autopay_invoice_ids [invoice-1 invoice-2]} nil))))))
(deftest match-transaction-unpaid-invoices
(testing "TODO exact same as above, no need for two endpaints"
(let [{:strs [transaction-id
test-vendor-id
invoice-1
invoice-2
]} (setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/amount -50.0)
(test-invoice :db/id "invoice-1"
:invoice/outstanding-balance 30.0 ;; TODO this part is a little different
:invoice/total 30.0)
(test-invoice :db/id "invoice-2"
:invoice/outstanding-balance 20.0 ;; TODO this part is a little different
:invoice/total 20.0)])]
(sut/match-transaction-unpaid-invoices {:id (admin-token)} {:transaction_id transaction-id :unpaid_invoice_ids [invoice-1 invoice-2]} nil)
(let [result (dc/pull (dc/db conn) '[:transaction/vendor
{:transaction/payment [:db/id {:payment/status [:db/ident]}]}
{:transaction/approval-status [:db/ident]
:transaction/accounts [:transaction-account/account
:transaction-account/location
:transaction-account/amount]}
]
transaction-id)]
(testing "should have created a payment"
(is (some? (:transaction/payment result)))
(is (= :payment-status/cleared (-> result
:transaction/payment
:payment/status
:db/ident)))
(is (= :transaction-approval-status/approved (-> result :transaction/approval-status :db/ident))))
(testing "Should have completed the invoice"
(is (= :invoice-status/paid (->> invoice-1
(dc/pull (dc/db conn) '[{:invoice/status [:db/ident]}])
:invoice/status
:db/ident)))
(is (= :invoice-status/paid (->> invoice-2
(dc/pull (dc/db conn) '[{:invoice/status [:db/ident]}])
:invoice/status
:db/ident)))))))
(testing "Should prevent a transaction from linking to an incorrectly balanced invoice"
(let [{:strs [transaction-id
test-vendor-id
invoice-1
invoice-2
]} (setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/amount -50.0)
(test-invoice :db/id "invoice-1"
:invoice/total 30.0)])]
(is (thrown? Exception (sut/match-transaction-autopay-invoices {:id (admin-token)} {:transaction_id transaction-id :autopay_invoice_ids [invoice-1 invoice-2]} nil))))))
(deftest match-transaction-rules
(testing "Should match transactions without linked payments"
(let [{:strs [transaction-id
transaction-rule-id
]} (setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/amount -50.0)
(test-transaction-rule :db/id "transaction-rule-id"
:transaction-rule/client "test-client-id"
:transaction-rule/transaction-approval-status :transaction-approval-status/excluded
:transaction-rule/description ".*"
)])]
(is (= transaction-rule-id (-> (sut/match-transaction-rules {:id (admin-token)} {:transaction_ids [transaction-id] :transaction_rule_id transaction-rule-id} nil)
first
:matched_rule
:id)))
(testing "Should apply statuses"
(is (= :excluded
(-> (sut/match-transaction-rules {:id (admin-token)}
{:transaction_ids [transaction-id] :transaction_rule_id transaction-rule-id}
nil)
first
:approval_status
))))))
(testing "Should not apply to transactions if they don't match"
(let [{:strs [transaction-id
transaction-rule-id]}
(setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/amount -50.0)
(test-transaction-rule :db/id "transaction-rule-id"
:transaction-rule/description "NOMATCH"
)])]
(is (thrown? Exception (-> (sut/match-transaction-rules {:id (admin-token)} {:transaction_ids [transaction-id] :transaction_rule_id transaction-rule-id} nil)
first
:matched_rule
:id)))))
(testing "Should not apply to transactions if they are already matched"
(let [{:strs [transaction-id
transaction-rule-id]}
(setup-test-data [(test-payment :db/id "extant-payment-id")
(test-transaction :db/id "transaction-id"
:transaction/payment {:db/id "extant-payment-id"}
:transaction/amount -50.0)
(test-transaction-rule :db/id "transaction-rule-id"
:transaction-rule/description ".*"
)])]
(is (thrown? Exception (-> (sut/match-transaction-rules {:id (admin-token)} {:transaction_ids [transaction-id] :transaction_rule_id transaction-rule-id} nil)
first
:matched_rule
:id)))))
(testing "Should apply to all transactions even without a transaction id"
(let [{:strs [transaction-id
transaction-rule-id]}
(setup-test-data [(test-transaction :db/id "transaction-id"
:transaction/description-original "MATCH"
:transaction/amount -50.0)
(test-transaction-rule :db/id "transaction-rule-id"
:transaction-rule/description ".*"
)])]
(sut/match-transaction-rules {:id (admin-token)} {:all true
:transaction_rule_id transaction-rule-id} nil)
(= {:transaction/matched-rule {:db/id transaction-rule-id}}
(dc/pull (dc/db conn) '[:transaction/matched-rule] transaction-id)))))

View File

@@ -28,3 +28,77 @@
:user/role "user"
:user/name "TEST USER"
:user/clients [{:db/id client-id}]}))
(defn test-client [& kwargs]
(apply assoc {:db/id "client-id"
:client/code (str "CLIENT" (rand-int 100000))
:client/locations ["DT"]}
kwargs))
(defn test-vendor [& kwargs]
(apply assoc {:db/id "vendor-id"
:vendor/name "Vendorson"}
kwargs))
(defn test-bank-account [& kwargs]
(apply assoc {:db/id "bank-account-id"
:bank-account/code (str "CLIENT-" (rand-int 100000))
:bank-account/type :bank-account-type/check}
kwargs))
(defn test-transaction [& kwargs]
(apply assoc {:db/id "transaction-id"
:transaction/date #inst "2022-01-01"
:transaction/client "test-client-id"
:transaction/bank-account "test-bank-account-id"
:transaction/id (str (java.util.UUID/randomUUID))
:transaction/amount 100.0
:transaction/description-original "original description"} kwargs))
(defn test-payment [& kwargs]
(apply assoc {:db/id "test-payment-id"
:payment/date #inst "2022-01-01"
:payment/client "test-client-id"
:payment/bank-account "test-bank-account-id"
:payment/type :payment-type/check
:payment/vendor "test-vendor-id"
:payment/amount 100.0}
kwargs))
(defn test-invoice [& kwargs]
(apply assoc {:db/id "test-invoice-id"
:invoice/date #inst "2022-01-01"
:invoice/client "test-client-id"
:invoice/total 100.0
:invoice/vendor "test-vendor-id"
:invoice/invoice-number (str "INVOICE " (rand-int 1000000))
:invoice/expense-accounts [{:invoice-expense-account/account "test-account-id"
:invoice-expense-account/amount 100.0
:invoice-expense-account/location "DT"}]}
kwargs))
(defn test-account [& kwargs]
(apply assoc {:db/id "account-id"
:account/name "Account"
:account/type :account-type/asset}
kwargs))
(defn test-transaction-rule [& kwargs]
(apply assoc {:db/id "test-transaction-rule-id"
:transaction-rule/client "test-client-id"
:transaction-rule/note "Test"}
kwargs))
(defn setup-test-data [data]
(:tempids (dc/transact conn {:tx-data (into data
[(test-account :db/id "test-account-id")
(test-client :db/id "test-client-id"
:client/bank-accounts [(test-bank-account :db/id "test-bank-account-id")])
(test-vendor :db/id "test-vendor-id")
{:db/id "accounts-payable-id"
:account/numeric-code 21000
:account/account-set "default"}])})))