From 183c74f128577c1e75afefa22be343a96ba3be9c Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Thu, 27 Aug 2020 07:24:12 -0700 Subject: [PATCH] added auditing. --- src/clj/auto_ap/datomic.clj | 26 +++ src/clj/auto_ap/datomic/invoices.clj | 175 +++++++++--------- src/clj/auto_ap/datomic/migrate.clj | 4 +- src/clj/auto_ap/datomic/migrate/audit.clj | 12 ++ src/clj/auto_ap/datomic/transactions.clj | 1 + src/clj/auto_ap/graphql.clj | 3 +- src/clj/auto_ap/graphql/checks.clj | 55 +++--- src/clj/auto_ap/graphql/invoices.clj | 65 +++---- src/clj/auto_ap/graphql/transactions.clj | 116 ++++++------ src/clj/user.clj | 10 + src/cljs/auto_ap/views/pages/admin/rules.cljs | 2 +- .../views/pages/admin/rules/side_bar.cljs | 51 +---- test/clj/auto_ap/graphql.clj | 60 ++++++ 13 files changed, 337 insertions(+), 243 deletions(-) create mode 100644 src/clj/auto_ap/datomic/migrate/audit.clj diff --git a/src/clj/auto_ap/datomic.clj b/src/clj/auto_ap/datomic.clj index abb3baff..5158a6be 100644 --- a/src/clj/auto_ap/datomic.clj +++ b/src/clj/auto_ap/datomic.clj @@ -2,6 +2,7 @@ (:require [auto-ap.utils :refer [default-pagination-size]] [clj-time.coerce :as coerce] [datomic.api :as d] + [clojure.tools.logging :as log] [mount.core :as mount])) (def uri "datomic:sql://invoices?jdbc:postgresql://database:5432/datomic?user=datomic&password=datomic") @@ -776,6 +777,7 @@ (update-in [:args] into (get-in query-part-2 [:args])))) (defn add-sorter-fields [q sort-map args] + (log/info "sort-map" (pr-str sort-map)) (reduce (fn [q {:keys [sort-key asc]}] (merge-query q @@ -803,9 +805,33 @@ (sort comparator results ))) (defn apply-pagination [args results] + (log/info (take 4 results)) {:ids (->> results (drop (:start args 0)) (take (:count args (or (:per-page args) default-pagination-size))) (map last)) :count (count results)}) + +(defn audit-transact-batch [txes id] + (let [batch-id (.toString (java.util.UUID/randomUUID))] + (reduce + (fn [full-tx batch] + (let [batch (conj batch {:db/id "datomic.tx" + :audit/user (str (:user/role id) "-" (:user/name id)) + :audit/batch batch-id}) + tx-result @(d/transact conn batch)] + + (cond-> full-tx + (:tx-data full-tx) (update :tx-data #(into % (:tx-data tx-result))) + (not (:tx-data full-tx)) (assoc :tx-data (vec (:tx-data tx-result))) + (not (:db-before full-tx)) (assoc :db-before (:db-before tx-result)) + true (assoc :db-after (:db-after tx-result)) + true (update :tempids merge (:tempids tx-result))))) + + {} + (partition-all 200 txes)))) + +(defn audit-transact [txes id] + @(d/transact conn (conj txes {:db/id "datomic.tx" + :audit/user (str (:user/role id) "-" (:user/name id))}))) diff --git a/src/clj/auto_ap/datomic/invoices.clj b/src/clj/auto_ap/datomic/invoices.clj index 4415302a..909116c7 100644 --- a/src/clj/auto_ap/datomic/invoices.clj +++ b/src/clj/auto_ap/datomic/invoices.clj @@ -5,7 +5,8 @@ [auto-ap.parse :as parse] [clj-time.coerce :as c] [clojure.set :refer [rename-keys]] - [clojure.string :as str])) + [clojure.string :as str] + [clojure.tools.logging :as log])) (def default-read '[* {:invoice/client [:client/name :db/id :client/locations :client/code]} @@ -24,101 +25,104 @@ (rename-keys {:invoice-payment/_invoice :invoice/payments}))) (defn raw-graphql-ids [db args] - (->> (cond-> {:query {:find [] - :in ['$] - :where ['[?e :invoice/invoice-number]]} - :args [(d/db (d/connect uri))]} - - (limited-clients (:id args)) - (merge-query {:query {:in ['[?xx ...]] - :where ['[?e :invoice/client ?xx]]} - :args [ (set (map :db/id (limited-clients (:id args))))]}) - (:client-id args) - (merge-query {:query {:in ['?client-id] - :where ['[?e :invoice/client ?client-id]]} - :args [ (:client-id args)]}) + (->> (doto (cond-> {:query {:find [] + :in ['$] + :where ['[?e :invoice/invoice-number]]} + :args [(d/db (d/connect uri))]} + + (limited-clients (:id args)) + (merge-query {:query {:in ['[?xx ...]] + :where ['[?e :invoice/client ?xx]]} + :args [ (set (map :db/id (limited-clients (:id args))))]}) + (:client-id args) + (merge-query {:query {:in ['?client-id] + :where ['[?e :invoice/client ?client-id]]} + :args [ (:client-id args)]}) - (:client-code args) - (merge-query {:query {:in ['?client-code] - :where ['[?e :invoice/client ?client-id] - '[?client-id :client/code ?client-code]]} - :args [ (:client-code args)]}) + (:client-code args) + (merge-query {:query {:in ['?client-code] + :where ['[?e :invoice/client ?client-id] + '[?client-id :client/code ?client-code]]} + :args [ (:client-code args)]}) - (:original-id args) - (merge-query {:query {:in ['?original-id] - :where [ - '[?e :invoice/client ?c] - '[?c :client/original-id ?original-id]]} - :args [ (cond-> (:original-id args) - (string? (:original-id args)) Long/parseLong )]}) + (:original-id args) + (merge-query {:query {:in ['?original-id] + :where [ + '[?e :invoice/client ?c] + '[?c :client/original-id ?original-id]]} + :args [ (cond-> (:original-id args) + (string? (:original-id args)) Long/parseLong )]}) - (:start (:date-range args)) (merge-query {:query {:in '[?start-date] - :where ['[?e :invoice/date ?date] - '[(>= ?date ?start-date)]]} - :args [(c/to-date (:start (:date-range args)))]}) + (:start (:date-range args)) (merge-query {:query {:in '[?start-date] + :where ['[?e :invoice/date ?date] + '[(>= ?date ?start-date)]]} + :args [(c/to-date (:start (:date-range args)))]}) - (:end (:date-range args)) (merge-query {:query {:in '[?end-date] - :where ['[?e :invoice/date ?date] - '[(<= ?date ?end-date)]]} - :args [(c/to-date (:end (:date-range args)))]}) + (:end (:date-range args)) (merge-query {:query {:in '[?end-date] + :where ['[?e :invoice/date ?date] + '[(<= ?date ?end-date)]]} + :args [(c/to-date (:end (:date-range args)))]}) - (:start (:due-range args)) (merge-query {:query {:in '[?start-due] - :where ['[?e :invoice/due ?due] - '[(>= ?due ?start-due)]]} - :args [(c/to-date (:start (:due-range args)))]}) + (:start (:due-range args)) (merge-query {:query {:in '[?start-due] + :where ['[?e :invoice/due ?due] + '[(>= ?due ?start-due)]]} + :args [(c/to-date (:start (:due-range args)))]}) - (:end (:due-range args)) (merge-query {:query {:in '[?end-due] - :where ['[?e :invoice/due ?due] - '[(<= ?due ?end-due)]]} - :args [(c/to-date (:end (:due-range args)))]}) + (:end (:due-range args)) (merge-query {:query {:in '[?end-due] + :where ['[?e :invoice/due ?due] + '[(<= ?due ?end-due)]]} + :args [(c/to-date (:end (:due-range args)))]}) - (:import-status args) - (merge-query {:query {:in ['?import-status] - :where ['[?e :invoice/import-status ?import-status]]} - :args [ (keyword "import-status" (:import-status args))]}) - (:status args) - (merge-query {:query {:in ['?status] - :where ['[?e :invoice/status ?status]]} - :args [ (:status args)]}) - (:vendor-id args) - (merge-query {:query {:in ['?vendor-id] - :where ['[?e :invoice/vendor ?vendor-id]]} - :args [ (:vendor-id args)]}) + (:import-status args) + (merge-query {:query {:in ['?import-status] + :where ['[?e :invoice/import-status ?import-status]]} + :args [ (keyword "import-status" (:import-status args))]}) + (:status args) + (merge-query {:query {:in ['?status] + :where ['[?e :invoice/status ?status]]} + :args [ (:status args)]}) + (:vendor-id args) + (merge-query {:query {:in ['?vendor-id] + :where ['[?e :invoice/vendor ?vendor-id]]} + :args [ (:vendor-id args)]}) - (:amount-gte args) - (merge-query {:query {:in ['?amount-gte] - :where ['[?e :invoice/total ?total-filter] - '[(>= ?total-filter ?amount-gte)]]} - :args [(:amount-gte args)]}) + (:amount-gte args) + (merge-query {:query {:in ['?amount-gte] + :where ['[?e :invoice/total ?total-filter] + '[(>= ?total-filter ?amount-gte)]]} + :args [(:amount-gte args)]}) - (:amount-lte args) - (merge-query {:query {:in ['?amount-lte] - :where ['[?e :invoice/total ?total-filter] - '[(<= ?total-filter ?amount-lte)]]} - :args [(:amount-lte args)]}) + (:amount-lte args) + (merge-query {:query {:in ['?amount-lte] + :where ['[?e :invoice/total ?total-filter] + '[(<= ?total-filter ?amount-lte)]]} + :args [(:amount-lte args)]}) - (seq (:invoice-number-like args)) - (merge-query {:query {:in ['?invoice-number-like] - :where ['[?e :invoice/invoice-number ?invoice-number] - '[(.contains ^String ?invoice-number ?invoice-number-like)]]} - :args [(:invoice-number-like args)]}) + (seq (:invoice-number-like args)) + (merge-query {:query {:in ['?invoice-number-like] + :where ['[?e :invoice/invoice-number ?invoice-number] + '[(.contains ^String ?invoice-number ?invoice-number-like)]]} + :args [(:invoice-number-like args)]}) + + (:sort args) (add-sorter-fields {"client" ['[?e :invoice/client ?c] + '[?c :client/name ?sort-client]] + "vendor" ['[?e :invoice/vendor ?v] + '[?v :vendor/name ?sort-vendor]] + "description-original" ['[?e :transaction/description-original ?sort-description-original]] + "location" ['[?e :invoice/expense-accounts ?iea] + '[?iea :invoice-expense-account/location ?sort-location]] + "date" ['[?e :invoice/date ?sort-date]] + "due" ['[(get-else $ ?e :invoice/due #inst "2050-01-01") ?sort-due]] + "invoice-number" ['[?e :invoice/invoice-number ?sort-invoice-number]] + "total" ['[?e :invoice/total ?sort-total]] + "outstanding-balance" ['[?e :invoice/outstanding-balance ?sort-outstanding-balance]]} + args) + true + (merge-query {:query {:find ['?e] + :where ['[?e :invoice/client]]}}) ) + (#(log/info %))) + - (:sort args) (add-sorter-fields {"client" ['[?e :invoice/client ?c] - '[?c :client/name ?sort-client]] - "vendor" ['[?e :invoice/vendor ?v] - '[?v :vendor/name ?sort-vendor]] - "description-original" ['[?e :transaction/description-original ?sort-description-original]] - "location" ['[?e :invoice/expense-accounts ?iea] - '[?iea :invoice-expense-account/location ?sort-location]] - "date" ['[?e :invoice/date ?sort-date]] - "due" ['[(get-else $ ?e :invoice/due #inst "2050-01-01") ?sort-due]] - "invoice-number" ['[?e :invoice/invoice-number ?sort-invoice-number]] - "total" ['[?e :invoice/total ?sort-total]] - "outstanding-balance" ['[?e :invoice/outstanding-balance ?sort-outstanding-balance]]} - args) - true - (merge-query {:query {:find ['?e] - :where ['[?e :invoice/client]]}}) ) (d/query) (apply-sort-3 args) (apply-pagination args))) @@ -149,6 +153,7 @@ 0.0))) (defn get-graphql [args] + (let [db (d/db (d/connect uri)) {ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args) outstanding (sum-outstanding ids-to-retrieve)] diff --git a/src/clj/auto_ap/datomic/migrate.clj b/src/clj/auto_ap/datomic/migrate.clj index c3439277..e46b78f0 100644 --- a/src/clj/auto_ap/datomic/migrate.clj +++ b/src/clj/auto_ap/datomic/migrate.clj @@ -7,6 +7,7 @@ [auto-ap.datomic.migrate.add-general-ledger :as add-general-ledger] [auto-ap.datomic.migrate.sales :as sales] [auto-ap.datomic.migrate.clients :as clients] + [auto-ap.datomic.migrate.audit :as audit] [clojure.java.io :as io] [io.rkn.conformity :as c]) @@ -314,7 +315,8 @@ :db/cardinality :db.cardinality/one}]]} :auto-ap/fix-reset-rels {:txes-fn `reset-function}} sales/norms-map - clients/norms-map) + clients/norms-map + audit/norms-map) ] (println "Conforming database...") (c/ensure-conforms conn norms-map) diff --git a/src/clj/auto_ap/datomic/migrate/audit.clj b/src/clj/auto_ap/datomic/migrate/audit.clj new file mode 100644 index 00000000..87316151 --- /dev/null +++ b/src/clj/auto_ap/datomic/migrate/audit.clj @@ -0,0 +1,12 @@ +(ns auto-ap.datomic.migrate.audit) + +(def norms-map {::add-audit-fields + {:txes [[{:db/ident :audit/user + :db/doc "Which user commited this transaction" + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one} + + {:db/ident :audit/batch + :db/doc "Which batch was this part of" + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one}]]}}) diff --git a/src/clj/auto_ap/datomic/transactions.clj b/src/clj/auto_ap/datomic/transactions.clj index 31cd3330..bc6848e1 100644 --- a/src/clj/auto_ap/datomic/transactions.clj +++ b/src/clj/auto_ap/datomic/transactions.clj @@ -116,6 +116,7 @@ args) true (merge-query {:query {:find ['?e] :where ['[?e :transaction/id]]}}))] + (println query) (cond->> query true (d/query) true (apply-sort-3 args) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index 620b45b5..76dd2b10 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -941,7 +941,8 @@ (:invoice_payments args)) (:client_id args) (:bank_account_id args) - (:type args)))) + (:type args) + (:id context)))) (defn get-expense-account-stats [context {:keys [client_id] } value] (let [result (cond-> {:query {:find ['?account '?account-name '(sum ?amount)] diff --git a/src/clj/auto_ap/graphql/checks.clj b/src/clj/auto_ap/graphql/checks.clj index 25d8eb7e..68009188 100644 --- a/src/clj/auto_ap/graphql/checks.clj +++ b/src/clj/auto_ap/graphql/checks.clj @@ -11,7 +11,7 @@ [auto-ap.datomic.transactions :as d-transactions] [auto-ap.datomic.clients :as d-clients] [auto-ap.datomic.bank-accounts :as d-bank-accounts] - [auto-ap.datomic :refer [uri remove-nils]] + [auto-ap.datomic :refer [uri remove-nils audit-transact]] [auto-ap.utils :refer [by dollars-0?]] [auto-ap.numeric :refer [num->words]] [config.core :refer [env]] @@ -246,21 +246,22 @@ :payment/type :payment-type/check :payment/memo memo :payment/status :payment-status/pending - :payment/pdf-data (pr-str {:vendor vendor - :paid-to (or (:vendor/paid-to vendor) (:vendor/name vendor)) - :amount (reduce + 0 (map (comp invoice-amounts :db/id) invoices)) - :check (str (+ index (:bank-account/check-number bank-account))) - :memo memo - :date (date->str (local-now)) - :client client - :bank-account bank-account - #_#_:client {:name (:name client) - :address (:address client) - :signature-file (:signature-file client) - :bank {:name (:bank-account/bank-name bank-account) - :acct (:bank-account/bank-code bank-account) - :routing (:bank-account/routing bank-account) - :acct-number (:bank-account/number bank-account)}}})))] + :payment/pdf-data (doto (pr-str {:vendor vendor + :paid-to (or (:vendor/paid-to vendor) (:vendor/name vendor)) + :amount (reduce + 0 (map (comp invoice-amounts :db/id) invoices)) + :check (str (+ index (:bank-account/check-number bank-account))) + :memo memo + :date (date->str (local-now)) + :client (dissoc client :client/bank-accounts) + :bank-account (dissoc bank-account :bank-account/start-date) + #_#_:client {:name (:name client) + :address (:address client) + :signature-file (:signature-file client) + :bank {:name (:bank-account/bank-name bank-account) + :acct (:bank-account/bank-code bank-account) + :routing (:bank-account/routing bank-account) + :acct-number (:bank-account/number bank-account)}}}) + println)))] (-> [] (conj payment) @@ -306,7 +307,7 @@ :client-id client-id :invoices (map :invoice/invoice-number invoices)})))) -(defn print-checks [invoice-payments client-id bank-account-id type] +(defn print-checks [invoice-payments client-id bank-account-id type id] (let [type (keyword "payment-type" (name type)) invoices (d-invoices/get-multi (map :invoice-id invoice-payments)) client (d-clients/get-by-id client-id) @@ -330,7 +331,7 @@ (> (:payment/amount %) 0.0) ) checks))) - @(d/transact (d/connect uri) checks) + (audit-transact checks id) {:invoices (d-invoices/get-multi (map :invoice-id invoice-payments)) @@ -372,13 +373,14 @@ 0 invoice-payment-lookup)] - @(d/transact (d/connect uri) - (into [(assoc base-payment - :payment/type :payment-type/check - :payment/status :payment-status/pending - :payment/check-number (:check_number args) - :payment/date (c/to-date (parse (:date args) iso-date)))] - (invoice-payments invoices invoice-payment-lookup))) + (audit-transact + (into [(assoc base-payment + :payment/type :payment-type/check + :payment/status :payment-status/pending + :payment/check-number (:check_number args) + :payment/date (c/to-date (parse (:date args) iso-date)))] + (invoice-payments invoices invoice-payment-lookup)) + (:id context)) (->graphql {:s3-url nil :invoices (d-invoices/get-multi (map :invoice_id (:invoice_payments args)))}))) @@ -404,7 +406,8 @@ :payment/amount 0.0 :payment/status :payment-status/voided}] - @(d/transact (d/connect uri) (conj removing-payments updated-payment))) + (audit-transact (conj removing-payments updated-payment) + (:id context))) (-> (d-checks/get-by-id id) (->graphql)))) diff --git a/src/clj/auto_ap/graphql/invoices.clj b/src/clj/auto_ap/graphql/invoices.clj index e10f9c24..1bccf140 100644 --- a/src/clj/auto_ap/graphql/invoices.clj +++ b/src/clj/auto_ap/graphql/invoices.clj @@ -9,7 +9,7 @@ [auto-ap.time :refer [parse iso-date]] [auto-ap.utils :refer [dollars=]] [datomic.api :as d] - [auto-ap.datomic :refer [uri remove-nils]] + [auto-ap.datomic :refer [uri remove-nils audit-transact]] [clj-time.coerce :as coerce] [clj-time.core :as time] [clojure.set :as set])) @@ -34,15 +34,14 @@ (defn reject-invoices [context {:keys [invoices] :as in} value] (assert-admin (:id context)) - (let [transactions (map (fn [i] [:db/retractEntity i ]) invoices) - transaction-result @(d/transact (d/connect uri) transactions)] + transaction-result (audit-transact transactions (:id context))] invoices)) (defn approve-invoices [context {:keys [invoices] :as in} value] (assert-admin (:id context)) (let [transactions (map (fn [i] {:db/id i :invoice/import-status :import-status/imported}) invoices) - transaction-result @(d/transact (d/connect uri) transactions)] + transaction-result (audit-transact transactions (:id context))] invoices)) (defn assert-no-conflicting [{:keys [total invoice_number location client_id vendor_id vendor_name date] :as in}] @@ -95,7 +94,7 @@ (defn add-invoice [context {{:keys [total invoice_number location client_id vendor_id vendor_name date] :as in} :invoice} value] (assert-no-conflicting in) (assert-can-see-client (:id context) client_id) - (let [transaction-result @(d/transact (d/connect uri) [(add-invoice-transaction in)])] + (let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))] (-> (d-invoices/get-by-id (get-in transaction-result [:tempids "invoice"])) (->graphql)))) @@ -107,12 +106,13 @@ (assert-no-conflicting in) (assert-can-see-client (:id context) client_id) (assert-bank-account-belongs client_id bank-account-id) - (let [transaction-result @(d/transact (d/connect uri) [(add-invoice-transaction in)])] + (let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))] (-> (gq-checks/print-checks [{:invoice-id (get-in transaction-result [:tempids "invoice"]) :amount total}] client_id bank-account-id - type) + type + (:id context)) ->graphql))) @@ -142,24 +142,25 @@ expense_accounts)} due (assoc :invoice/due (coerce/to-date due)) (boolean? automatically_paid_when_due) (assoc :invoice/automatically-paid-when-due automatically_paid_when_due))] - @(d/transact (d/connect uri) (concat [updated-invoice] - (map (fn [d] [:db/retract id :invoice/expense-accounts d]) deleted))) + (audit-transact (concat [updated-invoice] + (map (fn [d] [:db/retract id :invoice/expense-accounts d]) deleted)) + (:id context)) (-> (d-invoices/get-by-id id) (->graphql)))) (defn void-invoice [context {id :invoice_id} value] (let [invoice (d-invoices/get-by-id id) - _ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice))) - updated-invoice (d-invoices/update {:db/id id - :invoice/total 0.0 - :invoice/outstanding-balance 0.0 - :invoice/status :invoice-status/voided - :invoice/expense-accounts (map (fn [ea] {:db/id (:db/id ea) - :invoice-expense-account/amount 0.0}) - (:invoice/expense-accounts invoice))})] + _ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))] + (audit-transact [{:db/id id + :invoice/total 0.0 + :invoice/outstanding-balance 0.0 + :invoice/status :invoice-status/voided + :invoice/expense-accounts (map (fn [ea] {:db/id (:db/id ea) + :invoice-expense-account/amount 0.0}) + (:invoice/expense-accounts invoice))}] + (:id context)) - (-> updated-invoice - (->graphql)))) + (-> (d-invoices/get-by-id id) (->graphql)))) (defn unvoid-invoice [context {id :invoice_id} value] @@ -176,17 +177,18 @@ :in ['$ '?e]} :args [history id]}) [last-transaction] (->> txs (sort-by first) (last))] - @(d/transact conn [(->> txs - (filter (fn [[tx]] (= tx last-transaction))) - (reduce (fn [new-transaction [_ entity original-status original-outstanding total expense-account expense-account-amount]] - (-> new-transaction - (assoc :db/id entity - :invoice/total total - :invoice/status original-status - :invoice/outstanding-balance original-outstanding) + (audit-transact [(->> txs + (filter (fn [[tx]] (= tx last-transaction))) + (reduce (fn [new-transaction [_ entity original-status original-outstanding total expense-account expense-account-amount]] + (-> new-transaction + (assoc :db/id entity + :invoice/total total + :invoice/status original-status + :invoice/outstanding-balance original-outstanding) - (update :invoice/expense-accounts conj {:db/id expense-account :invoice-expense-account/amount expense-account-amount})) - ) {}))]) + (update :invoice/expense-accounts conj {:db/id expense-account :invoice-expense-account/amount expense-account-amount})) + ) {}))] + (:id context)) (-> (d-invoices/get-by-id id) (->graphql)))) @@ -201,8 +203,9 @@ expense-account->entity (:expense_accounts args))}] - @(d/transact (d/connect uri) (concat [updated] - (map (fn [d] [:db/retract invoice-id :invoice/expense-accounts d])deleted))) + (audit-transact (concat [updated] + (map (fn [d] [:db/retract invoice-id :invoice/expense-accounts d])deleted)) + (:id context)) (->graphql (d-invoices/get-by-id (:invoice_id args))))) diff --git a/src/clj/auto_ap/graphql/transactions.clj b/src/clj/auto_ap/graphql/transactions.clj index fd659ce5..f98f924d 100644 --- a/src/clj/auto_ap/graphql/transactions.clj +++ b/src/clj/auto_ap/graphql/transactions.clj @@ -5,7 +5,7 @@ [auto-ap.datomic.checks :as d-checks] [auto-ap.graphql.transaction-rules :as g-tr] [datomic.api :as d] - [auto-ap.datomic :refer [uri remove-nils]] + [auto-ap.datomic :refer [uri remove-nils audit-transact audit-transact-batch]] [com.walmartlabs.lacinia :refer [execute]] [com.walmartlabs.lacinia.executor :as executor] [com.walmartlabs.lacinia.resolve :as resolve] @@ -23,14 +23,14 @@ (def approval-status->graphql (ident->enum-f :transaction/approval-status)) (defn get-transaction-page [context args value] - (let [args (assoc args :id (:id context)) - [transactions transactions-count] (d-transactions/get-graphql (update (<-graphql (:filters args)) :approval-status enum->keyword "transaction-approval-status")) + (let [args (assoc (:filters args) :id (:id context)) + [transactions transactions-count] (d-transactions/get-graphql (update (<-graphql args) :approval-status enum->keyword "transaction-approval-status")) transactions (map ->graphql (map approval-status->graphql transactions))] {:data transactions :total transactions-count :count (count transactions) - :start (:start (:filters args) 0) - :end (+ (:start (:filters args) 0) (count transactions))})) + :start (:start args 0) + :end (+ (:start args 0) (count transactions))})) (defn unapprove-transactions [context args value] (let [_ (assert-admin (:id context)) @@ -45,7 +45,13 @@ all-ids (into (set ids) specific-ids)] (log/info "Unapproving " (count all-ids) args) - (d-transactions/unapprove all-ids) + + (audit-transact-batch + (mapv (fn [i] + {:db/id i + :transaction/approval-status :transaction-approval-status/unapproved}) + all-ids) + (:id context)) {:message (str "Succesfully unapproved " (count all-ids) " transactions.")})) @@ -62,7 +68,12 @@ all-ids (into (set ids) specific-ids)] (log/info "Deleting " (count all-ids) args) - (d-transactions/delete all-ids) + (audit-transact-batch + (mapcat (fn [i] + [[:db/retractEntity i] + [:db/retractEntity [:journal-entry/original-entity i]]]) + all-ids) + (:id context)) {:message (str "Succesfully deleted " (count all-ids) " transactions.")})) (defn transaction-account->entity [{:keys [id account_id amount location]}] @@ -103,29 +114,28 @@ (when missing-locations (throw (ex-info (str "Location '" (str/join ", " missing-locations) "' not found on client.") {})) ) - @(d/transact (d/connect uri) - (doto (concat [(remove-nils {:db/id id - :transaction/vendor vendor_id - :transaction/approval-status (some->> approval_status - name - snake->kebab - (keyword "transaction-approval-status")) - :transaction/accounts (map transaction-account->entity accounts) - }) - ] - (cond forecast_match - [[:db/add id :transaction/forecast-match forecast_match]] + (audit-transact (concat [(remove-nils {:db/id id + :transaction/vendor vendor_id + :transaction/approval-status (some->> approval_status + name + snake->kebab + (keyword "transaction-approval-status")) + :transaction/accounts (map transaction-account->entity accounts) + }) + ] + (cond forecast_match + [[:db/add id :transaction/forecast-match forecast_match]] - (:db/id (:transaction/forecast-match existing-transaction)) - [[:db/retract id :transaction/forecast-match (:db/id (:transaction/forecast-match existing-transaction))]] + (:db/id (:transaction/forecast-match existing-transaction)) + [[:db/retract id :transaction/forecast-match (:db/id (:transaction/forecast-match existing-transaction))]] - :else - []) + :else + []) - (map (fn [d] - [:db/retract id :transaction/accounts d]) - deleted)) - clojure.pprint/pprint)) + (map (fn [d] + [:db/retract id :transaction/accounts d]) + deleted)) + (:id context)) (-> (d-transactions/get-by-id id) approval-status->graphql ->graphql))) @@ -142,20 +152,20 @@ (when-not (dollars= (- (:transaction/amount transaction)) (:payment/amount payment)) (throw (ex-info "Amounts don't match" {:validation-error "Amounts don't match"}))) - @(d/transact (d/connect uri) - (into - [{:db/id (:db/id payment) - :payment/status :payment-status/cleared} + (audit-transact (into + [{:db/id (:db/id payment) + :payment/status :payment-status/cleared} - {:db/id (:db/id transaction) - :transaction/payment (:db/id payment) - :transaction/vendor (:db/id (:payment/vendor payment)) - :transaction/location "A" - :transaction/accounts [{:transaction-account/account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"])) - :transaction-account/location "A" - :transaction-account/amount (Math/abs (:transaction/amount transaction))}]}] - (map (fn [x] [:db/retractEntity (:db/id x)] ) - (:transaction/accounts transaction))))) + {:db/id (:db/id transaction) + :transaction/payment (:db/id payment) + :transaction/vendor (:db/id (:payment/vendor payment)) + :transaction/location "A" + :transaction/accounts [{:transaction-account/account (:db/id (a/get-account-by-numeric-code-and-sets 21000 ["default"])) + :transaction-account/location "A" + :transaction-account/amount (Math/abs (:transaction/amount transaction))}]}] + (map (fn [x] [:db/retractEntity (:db/id x)] ) + (:transaction/accounts transaction))) + (:id context))) (-> (d-transactions/get-by-id transaction_id) approval-status->graphql ->graphql)) @@ -188,20 +198,20 @@ (when (:transaction/payment transaction) (throw (ex-info "Transaction already associated with a payment" {:validation-error "Transaction already associated with a payment"})))) - @(d/transact (d/connect uri) - (transduce - (map #(into - [(remove-nils (rm/apply-rule {:db/id (:db/id %) - :transaction/amount (:transaction/amount %)} - transaction-rule + (audit-transact (transduce + (map #(into + [(remove-nils (rm/apply-rule {:db/id (:db/id %) + :transaction/amount (:transaction/amount %)} + transaction-rule - (or (-> % :transaction/bank-account :bank-account/locations) - (-> % :transaction/client :client/locations))))] - (map (fn [x] [:db/retractEntity (:db/id x)] ) - (:transaction/accounts %)))) - into - [] - transactions)) + (or (-> % :transaction/bank-account :bank-account/locations) + (-> % :transaction/client :client/locations))))] + (map (fn [x] [:db/retractEntity (:db/id x)] ) + (:transaction/accounts %)))) + into + [] + transactions) + (:id context)) ) (transduce (comp diff --git a/src/clj/user.clj b/src/clj/user.clj index cd86f1e5..ca9dbfab 100644 --- a/src/clj/user.clj +++ b/src/clj/user.clj @@ -383,3 +383,13 @@ (d/tx-range (d/log (d/connect uri)) i (inc i)))))) +(defn tx-range-detail [i] + (map (juxt :e #(d/ident (d/db (d/connect uri)) (:a %)) :v) + + (mapcat :data (d/tx-range (d/log (d/connect uri)) + (- i 100) + (+ i 100))))) + + +(defn start-db [] + (mount.core/start (mount.core/only #{#'auto-ap.datomic/conn}))) diff --git a/src/cljs/auto_ap/views/pages/admin/rules.cljs b/src/cljs/auto_ap/views/pages/admin/rules.cljs index b2f8b720..c7ae163d 100644 --- a/src/cljs/auto_ap/views/pages/admin/rules.cljs +++ b/src/cljs/auto_ap/views/pages/admin/rules.cljs @@ -100,7 +100,7 @@ (defn admin-rules-page [] (let [{:keys [active?]} @(re-frame/subscribe [::forms/form ::form/form])] [side-bar-layout {:side-bar [admin-side-bar {} - [side-bar/rule-side-bar]] + [side-bar/rule-side-bar {:data-page ::page}]] :main [rules-content] :right-side-bar [appearing-side-bar {:visible? active?} [form/form {:data-page ::page}]] diff --git a/src/cljs/auto_ap/views/pages/admin/rules/side_bar.cljs b/src/cljs/auto_ap/views/pages/admin/rules/side_bar.cljs index ae72bfe6..6a511a8e 100644 --- a/src/cljs/auto_ap/views/pages/admin/rules/side_bar.cljs +++ b/src/cljs/auto_ap/views/pages/admin/rules/side_bar.cljs @@ -2,57 +2,18 @@ (:require [re-frame.core :as re-frame] [auto-ap.subs :as subs] - [auto-ap.views.components.typeahead :refer [typeahead-entity]])) - -(re-frame/reg-sub - ::specific-filters - (fn [db ] - (::filters db nil))) - -(re-frame/reg-sub - ::filters - :<- [::specific-filters] - :<- [::subs/vendors-by-id] - :<- [::subs/query-params] - (fn [[specific-filters vendors-by-id query-params] ] - (let [url-filters (-> query-params - (select-keys #{:vendor-id})) - url-filters {:vendor (when-let [vendor-id (:vendor-id url-filters)] - {:id (str vendor-id) - :name (get-in vendors-by-id [(str vendor-id) :name] "Loading...")})}] - (merge url-filters specific-filters )))) - -(re-frame/reg-sub - ::filter - :<- [::filters] - (fn [filters [_ which]] - (get filters which))) - -(re-frame/reg-sub - ::filter-params - :<- [::filters] - :<- [::subs/active-page] - (fn [[filters ap ]] - {:vendor-id (:id (:vendor filters))})) + [auto-ap.views.components.typeahead :refer [typeahead-entity]] + [auto-ap.views.pages.data-page :as data-page])) -(re-frame/reg-event-fx - ::filter-changed - (fn [{:keys [db]} [_ & params]] - (let [[a b c] params - [which val] (if (= 3 (count params)) - [(into [a] b) c] - [[a] b])] - {:db (assoc-in db (into [::filters] which) val)}))) - - -(defn rule-side-bar [] +(defn rule-side-bar [{:keys [data-page]}] [:div [:p.menu-label "Vendor"] [:div [typeahead-entity {:matches @(re-frame/subscribe [::subs/vendors]) - :on-change #(re-frame/dispatch [::filter-changed :vendor %]) + :on-change #(re-frame/dispatch [::data-page/filter-changed data-page :vendor %]) :match->text :name + :include-keys [:name :id] :type "typeahead-entity" - :value @(re-frame/subscribe [::filter :vendor])}]]]) + :value @(re-frame/subscribe [::data-page/filter data-page :vendor])}]]]) diff --git a/test/clj/auto_ap/graphql.clj b/test/clj/auto_ap/graphql.clj index 1d610093..51bb7fa0 100644 --- a/test/clj/auto_ap/graphql.clj +++ b/test/clj/auto_ap/graphql.clj @@ -23,7 +23,67 @@ :user/role "admin" :user/name "TEST ADMIN"}) +(defn user-token [] + {:user "TEST USER" + :exp (time/plus (time/now) (time/days 1)) + :user/role "user" + :user/name "TEST USER" + :user/clients [{:db/id 1}]}) + +(defn new-client [args] + (merge {:client/name "Test client" + :client/code (.toString (java.util.UUID/randomUUID)) + :client/locations ["AB"]} + args)) + +(defn new-transaction [args] + (merge {:transaction/amount 100.0 + :transaction/id (.toString (java.util.UUID/randomUUID))} + args)) + +(defn new-invoice [args] + (merge {:invoice/total 100.0 + :invoice/invoice-number (.toString (java.util.UUID/randomUUID))} + args)) + (use-fixtures :each wrap-setup) +(deftest transaction-page + (testing "transaction page" + @(d/transact (d/connect uri) + [(new-client {:db/id "client"}) + (new-transaction {:transaction/client "client"})]) + + (testing "It should find all transactions" + (let [result (:transaction-page (:data (sut/query (admin-token) "{ transaction_page(filters: {client_id: null}) { count, start, data { id } }}")))] + (is (= 1 (:count result))) + (is (= 0 (:start result))) + (is (= 1 (count (:data result)))))) + + (testing "Users should not see transactions they don't own" + (let [result (:transaction-page (:data (sut/query (user-token) "{ transaction_page(filters: {client_id: null}) { count, start, data { id } }}")))] + (is (= 0 (:count result))) + (is (= 0 (:start result))) + (is (= 0 (count (:data result)))))))) + + +(deftest invoice-page + (testing "invoice page" + @(d/transact (d/connect uri) + [(new-client {:db/id "client"}) + (new-invoice {:invoice/client "client" + :invoice/status :invoice-status/paid})]) + (testing "It should find all invoices" + (let [result (first (:invoice-page (:data (sut/query (admin-token) "{ invoice_page(client_id: null, status:paid) { count, start, invoices { id } }}"))))] + (is (= 1 (:count result))) + (is (= 0 (:start result))) + (is (= 1 (count (:invoices result)))))) + + (testing "Users should not see transactions they don't own" + (let [result (first (:invoice-page (:data (sut/query (user-token) "{ invoice_page(client_id: null) { count, start, invoices { id } }}"))))] + (is (= 0 (:count result))) + (is (= 0 (:start result))) + (is (= 0 (count (:data result)))))))) + (deftest ledger-page (testing "ledger" (testing "it should find ledger entries"