diff --git a/src/clj/auto_ap/datomic/transactions.clj b/src/clj/auto_ap/datomic/transactions.clj index 04c7d976..31cd3330 100644 --- a/src/clj/auto_ap/datomic/transactions.clj +++ b/src/clj/auto_ap/datomic/transactions.clj @@ -1,6 +1,6 @@ (ns auto-ap.datomic.transactions (:require [datomic.api :as d] - [auto-ap.datomic :refer [uri merge-query apply-sort-3 apply-pagination add-sorter-fields]] + [auto-ap.datomic :refer [uri merge-query apply-sort-3 apply-pagination add-sorter-fields conn]] [auto-ap.graphql.utils :refer [limited-clients]] [clj-time.coerce :as c] [clj-time.coerce :as coerce])) @@ -149,6 +149,17 @@ [(->> (graphql-results ids-to-retrieve db args)) matching-count])) +(defn filter-ids [ids] + (if ids + (->> {:query {:find ['?e] + :in ['$ '[?e ...]] + :where ['[?e :transaction/date]]} + :args [(d/db conn) ids]} + (d/query) + (map first) + vec) + [])) + (defn get-by-id [id] (-> (d/pull (d/db (d/connect uri)) @@ -168,9 +179,17 @@ (dissoc :transaction/id))) (defn unapprove [ids] - (doseq [x (partition-all 1000 ids)] + (doseq [x (partition-all 100 ids)] @(d/transact (d/connect uri) (mapv (fn [i] {:db/id i :transaction/approval-status :transaction-approval-status/unapproved}) x)))) + +(defn delete [ids] + (doseq [x (partition-all 100 ids)] + @(d/transact (d/connect uri) + (mapcat (fn [i] + [[:db/retractEntity i] + [:db/retractEntity [:journal-entry/original-entity i]]]) + x)))) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index 5b738050..4149bf6d 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -64,6 +64,8 @@ %))}} :objects { + :message + {:fields {:message {:type 'String}}} :location_match {:fields {:location {:type 'String} :match {:type 'String} @@ -554,6 +556,7 @@ :resolve :get-vendor} :user {:type '(list :user) :resolve :get-user} + } :input-objects @@ -781,19 +784,14 @@ :args {:invoices {:type '(list :id)}} :resolve :mutation/approve-invoices} - :unapprove_transactions {:type '(list :transaction_page) - :args {:client_id {:type :id} - :vendor_id {:type :id} - :bank_account_id {:type :id} - :date_range {:type :date_range} - :amount_lte {:type :money} - :amount_gte {:type :money} - :description {:type 'String} - :start {:type 'Int} - :sort {:type '(list :sort_item)} - :approval_status {:type :transaction_approval_status}} - + :unapprove_transactions {:type :message + :args {:filters {:type :transaction_filters} + :ids {:type '(list :id)}} :resolve :mutation/unapprove-transactions} + :delete_transactions {:type :message + :args {:filters {:type :transaction_filters} + :ids {:type '(list :id)}} + :resolve :mutation/delete-transactions} :delete_transaction_rule {:type :id :args {:transaction_rule_id {:type :id}} :resolve :mutation/delete-transaction-rule} @@ -1129,6 +1127,7 @@ :mutation/edit-invoice gq-invoices/edit-invoice :mutation/edit-transaction gq-transactions/edit-transaction :mutation/unapprove-transactions gq-transactions/unapprove-transactions + :mutation/delete-transactions gq-transactions/delete-transactions :mutation/upsert-transaction-rule gq-transaction-rules/upsert-transaction-rule :test-transaction-rule gq-transaction-rules/test-transaction-rule :run-transaction-rule gq-transaction-rules/run-transaction-rule diff --git a/src/clj/auto_ap/graphql/transactions.clj b/src/clj/auto_ap/graphql/transactions.clj index 53bbce4d..fd659ce5 100644 --- a/src/clj/auto_ap/graphql/transactions.clj +++ b/src/clj/auto_ap/graphql/transactions.clj @@ -17,7 +17,8 @@ [auto-ap.datomic.accounts :as a] [auto-ap.datomic.transaction-rules :as tr] [auto-ap.rule-matching :as rm] - [clj-time.coerce :as coerce])) + [clj-time.coerce :as coerce] + [clojure.tools.logging :as log])) (def approval-status->graphql (ident->enum-f :transaction/approval-status)) @@ -32,10 +33,37 @@ :end (+ (:start (:filters args) 0) (count transactions))})) (defn unapprove-transactions [context args value] - (let [args (assoc args :id (:id context)) - ids (:ids (d-transactions/raw-graphql-ids (update (<-graphql args) :approval-status enum->keyword "transaction-approval-status")))] - (d-transactions/unapprove ids) - (get-transaction-page context args value))) + (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)] + + (log/info "Unapproving " (count all-ids) args) + (d-transactions/unapprove all-ids) + {:message (str "Succesfully unapproved " (count all-ids) " transactions.")})) + + +(defn delete-transactions [context args value] + (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)] + + (log/info "Deleting " (count all-ids) args) + (d-transactions/delete all-ids) + {:message (str "Succesfully deleted " (count all-ids) " transactions.")})) (defn transaction-account->entity [{:keys [id account_id amount location]}] (remove-nils #:transaction-account {:amount (Double/parseDouble amount) diff --git a/src/cljs/auto_ap/views/pages/transactions.cljs b/src/cljs/auto_ap/views/pages/transactions.cljs index b16d504e..56585f45 100644 --- a/src/cljs/auto_ap/views/pages/transactions.cljs +++ b/src/cljs/auto_ap/views/pages/transactions.cljs @@ -54,29 +54,53 @@ :query/alias :result}]} :on-success (fn [result] + [::data-page/received ::page (:result result)])}})) (re-frame/reg-event-fx - ::unapprove-all + ::unapprove-selected (fn [cofx [_ params]] - - {:db (-> (:db cofx) - (assoc-in [:status :loading] true)) - :graphql {:token (-> cofx :db :user) - :owns-state {:single ::unapprove-all} - :query-obj - {:venia/operation {:operation/type :mutation - :operation/name "UnapproveTransactions"} - :venia/queries [{:query/data - [:unapprove-transactions - (data-params->query-params params) - [[:transactions transaction-read] - :total - :start - :end]]}]} - :on-success (fn [result] - [::data-page/received ::page (set/rename-keys (first (:unapprove-transactions result)) - {:transactions :data})])}})) + (let [checked @(re-frame/subscribe [::data-page/checked ::page]) + checked-params (get checked "header") + specific-transactions (map :id (vals (dissoc checked "header")))] + (println checked-params) + {:db (-> (:db cofx) + (assoc-in [:status :loading] true)) + :graphql {:token (-> cofx :db :user) + :owns-state {:single ::unapprove-selected} + :query-obj + {:venia/operation {:operation/type :mutation + :operation/name "UnapproveTransactions"} + :venia/queries [{:query/data + [:unapprove-transactions + {:filters (some-> checked-params data-params->query-params) + :ids specific-transactions} + [:message]]}]} + :on-success (fn [result] + [::params-change params] + #_[::data-page/received ::page (:result result)])}}))) + +(re-frame/reg-event-fx + ::delete-selected + (fn [cofx [_ params]] + (let [checked @(re-frame/subscribe [::data-page/checked ::page]) + checked-params (get checked "header") + specific-transactions (map :id (vals (dissoc checked "header")))] + (println checked-params) + {:db (-> (:db cofx) + (assoc-in [:status :loading] true)) + :graphql {:token (-> cofx :db :user) + :owns-state {:single ::delete-selected} + :query-obj + {:venia/operation {:operation/type :mutation + :operation/name "UnapproveTransactions"} + :venia/queries [{:query/data + [:delete-transactions + {:filters (some-> checked-params data-params->query-params) + :ids specific-transactions} + [:message]]}]} + :on-success (fn [result] + [::params-change params])}}))) (re-frame/reg-event-fx ::unmounted @@ -105,22 +129,41 @@ (defn content [] - (let [user @(re-frame/subscribe [::subs/user]) - params @(re-frame/subscribe [::data-page/params ::page])] + (let [is-admin? @(re-frame/subscribe [::subs/is-admin?]) + params @(re-frame/subscribe [::data-page/params ::page]) + checked @(re-frame/subscribe [::data-page/checked ::page])] [:div [:h1.title "Transactions"] - [status/status-notification {:statuses [[::status/single ::unapprove-all] + [status/status-notification {:statuses [[::status/single ::unapprove-selected] + [::status/single ::delete-selected] [::status/single ::manual-import]]}] - (when (= "admin" (:user/role user)) + (when is-admin? [:div.is-pulled-right [:div.buttons + + (into [:div.tags ] (map (fn [[z {:keys [id]}]] + (if (= "header" z) + [:span.tag.is-medium {:on-click + (dispatch-event [::data-page/remove-check ::page "header"])} + "All visible transactions"] + [:span.tag.is-medium id + [:button.delete.is-small {:on-click + (dispatch-event [::data-page/remove-check ::page id])}]])) + checked)) [:button.button.is-outlined.is-primary {:on-click (dispatch-event [::manual/opening])} "Manual Yodlee Import"] - [:button.button.is-warning {:on-click (dispatch-event [::unapprove-all params]) - :class (status/class-for @(re-frame/subscribe [::status/single ::unapprove-all])) - :disabled (status/disabled-for @(re-frame/subscribe [::status/single ::unapprove-all]))} - "Unapprove all"]]]) + [:button.button.is-warning {:on-click (dispatch-event [::unapprove-selected params]) + :class (status/class-for @(re-frame/subscribe [::status/single ::unapprove-selected])) + :disabled (or (status/disabled-for @(re-frame/subscribe [::status/single ::unapprove-selected])) + (not (seq checked)))} + "Unapprove selected"] + [:button.button.is-danger {:on-click (dispatch-event [::delete-selected params]) + :class (status/class-for @(re-frame/subscribe [::status/single ::delete-selected])) + :disabled (or (status/disabled-for @(re-frame/subscribe [::status/single ::delete-selected])) + (not (seq checked)))} + "Delete selected"]]]) [table/table {:id :transactions + :check-boxes? is-admin? :data-page ::page}]])) diff --git a/src/cljs/auto_ap/views/pages/transactions/table.cljs b/src/cljs/auto_ap/views/pages/transactions/table.cljs index bc1af759..71bb8da4 100644 --- a/src/cljs/auto_ap/views/pages/transactions/table.cljs +++ b/src/cljs/auto_ap/views/pages/transactions/table.cljs @@ -55,16 +55,18 @@ (fn [{table-params :db} [_ params :as z]] {:db (merge table-params params)})) -(defn table [{:keys [id data-page ]}] +(defn table [{:keys [id data-page check-boxes?]}] (let [selected-client @(re-frame/subscribe [::subs/client]) - {:keys [data status]} @(re-frame/subscribe [::data-page/page data-page]) + {:keys [data status params]} @(re-frame/subscribe [::data-page/page data-page]) states @(re-frame/subscribe [::status/multi ::edits])] [grid/grid {:data-page data-page - :column-count (if selected-client 6 7)} + :column-count (if selected-client 6 7) + :check-boxes? check-boxes?} [grid/controls data] [grid/table {:fullwidth true} [grid/header {} - [grid/row {} + [grid/row {:id "header" + :entity params} (when-not selected-client [grid/sortable-header-cell {:sort-key "client" :sort-name "Client"} "Client"]) [grid/sortable-header-cell {:sort-key "account" :sort-name "Bank Account"} "Bank Account"] @@ -76,7 +78,7 @@ [grid/body (for [{:keys [client account vendor approval-status payment status bank-account description-original date amount id yodlee-merchant ] :as i} (:data data)] ^{:key id} - [grid/row {:class (:class i) :id id} + [grid/row {:class (:class i) :id id :entity i} (when-not selected-client [grid/cell {} (:name client)])