(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.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) :clients [{:db/id test-client-id}]} {:filters {} :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) :clients [{:db/id test-client-id}]} {:filters {: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))) (testing "for more than one client" (let [{:strs [transaction-id-1 transaction-id-2 test-client-id-2 test-client-id]} (setup-test-data [ (test-transaction :db/id "transaction-id-1" :transaction/client "test-client-id" :transaction/bank-account "test-bank-account-id" :transaction/amount 40.0) (test-transaction :db/id "transaction-id-2" :transaction/client "test-client-id-2" :transaction/bank-account "test-bank-account-id-2" :transaction/amount 40.0) (test-client :db/id "test-client-id-2" :client/locations ["GR"]) (test-bank-account :db/id "test-bank-account-id-2")])] (is (= "Successfully coded 2 transactions." (:message (sut/bulk-code-transactions {:id (admin-token) :clients [{:db/id test-client-id} {:db/id test-client-id-2}]} {:filters {} :vendor test-vendor-id :approval_status :unapproved :accounts [{:account_id test-account-id :location "Shared" :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-1))) (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 "GR" :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-2)))) (testing "should reject a location that doesnt exist" (let [{:strs [test-client-id-1 test-client-id-2]} (setup-test-data [ (test-transaction :db/id "transaction-id-1" :transaction/client "test-client-id-1" :transaction/bank-account "test-bank-account-id" :transaction/amount 40.0) (test-transaction :db/id "transaction-id-2" :transaction/client "test-client-id-2" :transaction/bank-account "test-bank-account-id-2" :transaction/amount 40.0) (test-client :db/id "test-client-id-1" :client/locations ["DT" "BOTH"]) (test-client :db/id "test-client-id-2" :client/locations ["GR" "BOTH"]) (test-bank-account :db/id "test-bank-account-id-2")])] (is (thrown? Exception (sut/bulk-code-transactions {:id (admin-token) :clients [{:db/id test-client-id} {:db/id test-client-id-2}]} {:filters {} :vendor test-vendor-id :approval_status :unapproved :accounts [{:account_id test-account-id :location "OG" :percentage 1.0}]} nil))) (is (thrown? Exception (sut/bulk-code-transactions {:id (admin-token) :clients [{:db/id test-client-id} {:db/id test-client-id-2}]} {:filters {} :vendor test-vendor-id :approval_status :unapproved :accounts [{:account_id test-account-id :location "DT" :percentage 1.0}]} nil))) (is (sut/bulk-code-transactions {:id (admin-token) :clients [{:db/id test-client-id-1} {:db/id test-client-id-2}]} {:filters {} :vendor test-vendor-id :approval_status :unapproved :accounts [{:account_id test-account-id :location "BOTH" :percentage 1.0}]} nil)))))))) (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 {:account/name "Accounts Payable"} :location "A" :amount 50.0}]} (dc/pull (dc/db conn) '[:transaction/vendor :transaction/payment {:transaction/approval-status [:db/ident] :transaction/accounts [{:transaction-account/account [:account/name]} :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)))))