supports bulk voiding invoices, searches for iol duplicates.

This commit is contained in:
2022-02-17 17:38:58 -08:00
parent 38befab81d
commit ea490c5859
7 changed files with 368 additions and 209 deletions

View File

@@ -25,7 +25,10 @@
(update :invoice/status :db/ident)
(rename-keys {:invoice-payment/_invoice :invoice/payments})))
(defn raw-graphql-ids [db args]
(defn raw-graphql-ids
([args]
(raw-graphql-ids (d/db conn) args))
([db args]
(->> (cond-> {:query {:find []
:in ['$]
:where ['[?e :invoice/client]]}
@@ -147,7 +150,7 @@
'[?e :invoice/date ?sort-default]]}}) )
(d/query)
(apply-sort-3 args)
(apply-pagination args)))
(apply-pagination args))))
(defn graphql-results [ids db args]
@@ -258,7 +261,16 @@
set)]
(into vendored-results vendorless-results)))
(defn filter-ids [ids]
(if ids
(->> {:query {:find ['?e]
:in ['$ '[?e ...]]
:where ['[?e :invoice/date]]}
:args [(d/db conn) ids]}
(d/query)
(map first)
vec)
[]))
(defn code-invoice [invoice]
(let [db (d/db auto-ap.datomic/conn)

View File

@@ -1,17 +1,18 @@
(ns auto-ap.graphql.invoices
(:require
[auto-ap.datomic :refer [audit-transact conn remove-nils uri]]
[auto-ap.datomic
:refer [audit-transact audit-transact-batch conn remove-nils uri]]
[auto-ap.datomic.clients :as d-clients]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.datomic.vendors :as d-vendors]
[auto-ap.graphql.checks :as gq-checks]
[auto-ap.graphql.utils
:as u
:refer [<-graphql
assert-admin
assert-can-see-client
assert-power-user
enum->keyword]
:as u]
enum->keyword]]
[auto-ap.utils :refer [dollars=]]
[clj-time.coerce :as coerce]
[clj-time.core :as time]
@@ -31,6 +32,7 @@
(let [args (assoc args :id (:id context))
[invoices invoice-count outstanding total-amount] (-> args
:filters
(assoc :id (:id context))
(<-graphql)
(update :status enum->keyword "invoice-status")
@@ -220,6 +222,43 @@
(-> (d-invoices/get-by-id id) (->graphql (:id context)))))
(defn void-invoices [context args _]
(let [_ (assert-admin (:id context))
args (assoc args :id (:id context))
ids (some-> args
:filters
(assoc :id (:id context))
(<-graphql)
(update :status enum->keyword "invoice-status")
(assoc :per-page Integer/MAX_VALUE)
d-invoices/raw-graphql-ids
:ids)
specific-ids (d-invoices/filter-ids (:ids args))
all-ids (into (set ids) specific-ids)]
(log/info "Voiding " (count all-ids) args)
(audit-transact
(->> all-ids
(d/q '[:find [(pull ?i [:db/id {:invoice/expense-accounts [:db/id]}]) ...]
:in $ [?i ...]
:where (not [_ :invoice-payment/invoice ?i])]
(d/db conn)
)
(mapcat
(fn [i]
(into
[{:db/id (:db/id i)
:invoice/total 0.0
:invoice/outstanding-balance 0.0
:invoice/status :invoice-status/voided
}]
(map
(fn [iea]
[:db/retract (:db/id i) :invoice/expense-accounts (:db/id iea)])
(:invoice/expense-accounts i))))))
(:id context))
{:message (str "Succesfully voided " (count all-ids))}))
(defn unvoid-invoice [context {id :invoice_id} _]
(let [invoice (d-invoices/get-by-id id)
_ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
@@ -316,22 +355,7 @@
(def queries
{:invoice_page {:type '(list :invoice_page)
:args {:import_status {:type 'String}
:exact_match_id {:type :id}
:date_range {:type :date_range}
:due_range {:type :date_range}
:status {:type :invoice_status}
:unresolved {:type 'Boolean}
:scheduled_payments {:type 'Boolean}
:client_id {:type :id}
:vendor_id {:type :id}
:amount_lte {:type :money}
:amount_gte {:type :money}
:invoice_number_like {:type 'String}
:location {:type 'String}
:start {:type 'Int}
:per_page {:type 'Int}
:sort {:type '(list :sort_item)}}
:args {:filters {:type :invoice_filters}}
:resolve :get-invoice-page}
:all_invoices {:type '(list :invoice)
@@ -351,6 +375,10 @@
:void_invoice {:type :invoice
:args {:invoice_id {:type :id}}
:resolve :mutation/void-invoice}
:void_invoices {:type :message
:args {:filters {:type :invoice_filters}
:ids {:type '(list :id)}}
:resolve :mutation/void-invoices}
:unvoid_invoice {:type :invoice
:args {:invoice_id {:type :id}}
:resolve :mutation/unvoid-invoice}
@@ -400,7 +428,24 @@
{:fields {:id {:type :id}
:account_id {:type :id}
:location {:type 'String}
:amount {:type :money}}}})
:amount {:type :money}}}
:invoice_filters {:fields {:import_status {:type 'String}
:exact_match_id {:type :id}
:date_range {:type :date_range}
:due_range {:type :date_range}
:status {:type :invoice_status}
:unresolved {:type 'Boolean}
:scheduled_payments {:type 'Boolean}
:client_id {:type :id}
:vendor_id {:type :id}
:amount_lte {:type :money}
:amount_gte {:type :money}
:invoice_number_like {:type 'String}
:location {:type 'String}
:start {:type 'Int}
:per_page {:type 'Int}
:sort {:type '(list :sort_item)}}}})
(def enums
{:invoice_status {:values [{:enum-value :paid}
@@ -416,6 +461,7 @@
:mutation/add-and-print-invoice add-and-print-invoice
:mutation/edit-invoice edit-invoice
:mutation/void-invoice void-invoice
:mutation/void-invoices void-invoices
:mutation/unvoid-invoice unvoid-invoice
:mutation/unautopay-invoice unautopay-invoice
:mutation/edit-expense-accounts edit-expense-accounts})

View File

@@ -197,6 +197,7 @@
:journal-entry-line/credit
{:journal-entry-line/account [:bank-account/include-in-reports
:bank-account/bank-name
:bank-account/numeric-code
:bank-account/code
:bank-account/visible
:bank-account/name
@@ -312,6 +313,7 @@
(log/info "Executing raw query " (get query-params "query" ))
(statsd/time! [(str "export.time") {:tags #{"export:raw"}}]
(into (list) (apply d/q (read-string (get query-params "query" )) (into [(d/db conn)] (read-string (get query-params "args" "[]")))))))))
(defroutes export-routes
(routes
(wrap-routes api-key-authed-routes

View File

@@ -11,6 +11,47 @@
[unilog.context :as lc]
[yang.scheduler :as scheduler]))
(def known-bad-yodlee-ids ;; these yodlee ids are ones we wish we could manually delete
(set [
3432269925
3432269925
3440074569
3440074568
3451368964
3453413513
3453413398
3325396245
3451368962
2987488002
3021618678
3021618674
3025625892
3052944039
3052944035
3079067192
3079067193
3079067193
3122287847
3122287842
3189405320
3189405320
3189405321
3189405321
3189405319
3189405319
3248171256
3248171256
3248171257
3248171257
3248171254
3291944265
3291944267
3291944267
3310943251
3387770449
3387770447
3432269929]))
(defn auth-header
([cob-session] (str "{cobSession=" cob-session "}"))
([cob-session user-session] (str "{cobSession=" cob-session ",userSession=" user-session "}")))

View File

@@ -20,10 +20,8 @@
[auto-ap.views.components.expense-accounts-dialog :as expense-accounts-dialog]
[auto-ap.views.pages.data-page :as data-page]))
(defn query [params]
{:venia/queries [[:invoice_page
{
:exact-match-id (some-> params :exact-match-id str)
(defn data-params->query-params [params]
{:exact-match-id (some-> params :exact-match-id str)
:start (:start params 0)
:sort (:sort params)
:per-page (:per-page params)
@@ -44,7 +42,12 @@
:import-invoices nil
:unpaid-invoices :unpaid
:paid-invoices :paid
:voided-invoices :voided)}
:voided-invoices :voided)})
(defn query [params]
{:venia/queries [[:invoice_page
{:filters (data-params->query-params params)}
[[:invoices [:id :total :outstanding-balance :invoice-number :date :due :status :client-identifier :scheduled-payment :source-url :similarity
[:vendor [:name :id]]
[:expense_accounts [:amount :id :location
@@ -229,7 +232,7 @@
(defn invoice-table [{:keys [id check-boxes overrides actions data-page checkable-fn]}]
(let [selected-client @(re-frame/subscribe [::subs/client])
{:keys [data status table-params]} @(re-frame/subscribe [::data-page/page data-page])
{:keys [data status params table-params]} @(re-frame/subscribe [::data-page/page data-page])
selected-client @(re-frame/subscribe [::subs/client])
is-loading? (= :loading (:state status))
@@ -261,7 +264,8 @@
^{:key (or (:id (first invoices)) "init")}
[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 "vendor" :sort-name "Vendor"}

View File

@@ -95,7 +95,6 @@
(for [{:keys [client account vendor approval-status payment expected-deposit status bank-account description-original date amount id yodlee-merchant ] :as i} (:data data)]
^{:key id}
[grid/row {:class (:class i) :id id :entity i}
(println expected-deposit)
(when-not selected-client
[grid/cell {} (:name client)])

View File

@@ -1,38 +1,35 @@
(ns auto-ap.views.pages.unpaid-invoices
(:require [auto-ap.effects.forward :as forward]
(:require
[auto-ap.effects.forward :as forward]
[auto-ap.events :as events]
[auto-ap.forms :as forms]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components.buttons :as buttons]
[auto-ap.views.components.dropdown :refer [drop-down]]
[auto-ap.views.components.expense-accounts-dialog
:as
expense-accounts-dialog]
:as expense-accounts-dialog]
[auto-ap.views.components.invoice-table :as table]
[auto-ap.views.components.invoices.side-bar
:as
side-bar
:refer
[invoices-side-bar]]
:as side-bar
:refer [invoices-side-bar]]
[auto-ap.views.components.layouts
:refer
[appearing-side-bar side-bar-layout]]
:refer [appearing-side-bar side-bar-layout]]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.pages.data-page :as data-page]
[auto-ap.views.pages.invoices.advanced-print-checks
:as
advanced-print-checks]
:as advanced-print-checks]
[auto-ap.views.pages.invoices.common :refer [invoice-read]]
[auto-ap.views.pages.invoices.form :as form]
[auto-ap.views.pages.invoices.handwritten-checks :as handwritten-checks]
[auto-ap.views.utils :refer [dispatch-event dispatch-event-with-propagation with-user]]
[auto-ap.views.utils
:refer [dispatch-event dispatch-event-with-propagation with-user]]
[clojure.set :as set]
[clojure.string :as str]
[goog.string :as gstring]
[re-frame.core :as re-frame]
[reagent.core :as r]
[vimsical.re-frame.fx.track :as track]
[auto-ap.views.components.buttons :as buttons]))
[vimsical.re-frame.fx.track :as track]))
(re-frame/reg-event-fx
::params-change
@@ -131,15 +128,68 @@
:status :unpaid
#_#_:date (date->str (c/now) standard)
:location (first (:locations @(re-frame/subscribe [::subs/client])))}]}))
(re-frame/reg-event-fx
::voided-selected
(fn [cofx [_]]
{:dispatch-n [[::modal/modal-closed]
[::params-change @(re-frame/subscribe [::data-page/params ::page])]]}))
(re-frame/reg-event-fx
::void-selected
(fn [cofx [_ which]]
(let [checked-params (get which "header")
specific-invoices (map :id (vals (dissoc which "header")))]
{:graphql {:token (-> cofx :db :user)
:owns-state {:single ::void-selected}
:query-obj
{:venia/operation {:operation/type :mutation
:operation/name "DeleteTransactions"}
:venia/queries [{:query/data
[:void-invoices
{:filters (some-> checked-params table/data-params->query-params)
:ids specific-invoices}
[:message]]}]}
:on-success (fn [result]
[::voided-selected])}
:dispatch [::data-page/reset-checked ::page]})))
(re-frame/reg-event-fx
::void-selected-requested
(fn [_ [_ which]]
(let [to-delete (if (get which "header")
"all visible invoices"
(str (count which) " invoices"))]
{:dispatch [::modal/modal-requested {:title "Confirmation"
:body [:div (str "Are you sure you want to void " to-delete "?")]
:cancel? true
:confirm {:value "Void"
:class "is-danger"
:status-from [::status/single ::void-selected]
:on-click (dispatch-event [::void-selected which] )}
:close-event [::status/completed ::void-selected]}]})))
(defn void-selected-button []
(let [status @(re-frame/subscribe [::status/single ::void-selected])
checked-invoices @(re-frame/subscribe [::data-page/checked :invoices])
is-admin? @(re-frame/subscribe [::subs/is-admin?])]
(when is-admin?
[:button.button.is-danger {:on-click (dispatch-event [::void-selected-requested checked-invoices])
:class (status/class-for status)
:disabled (or (status/disabled-for status)
(not (seq checked-invoices)))}
"Void"])))
(defn pay-button []
(let [current-client @(re-frame/subscribe [::subs/client])
checked-invoices (vals @(re-frame/subscribe [::data-page/checked :invoices]))
checked-invoices @(re-frame/subscribe [::data-page/checked :invoices])
print-checks-status @(re-frame/subscribe [::status/single ::print-checks])]
[:div
[:div.is-pulled-right
[:div.buttons
[void-selected-button]
[buttons/new-button {:event [::new-invoice-clicked]
:name "Invoice"
:class "is-primary"}]
@@ -172,7 +222,7 @@
(if (> balance 0.001)
(list
^{:key (str id "-check")} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::print-checks id :check])
:disabled (doto (status/disabled-for print-checks-status) println)} "Print checks from " name]
:disabled (status/disabled-for print-checks-status)} "Print checks from " name]
^{:key (str id "-debit")} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::print-checks id :debit])
:disabled (status/disabled-for print-checks-status)} "Debit from " name])
(list
@@ -181,18 +231,23 @@
(when (> balance 0.001)
^{:key "advanced-divider"} [:hr.dropdown-divider])
(when (and (= 1 (count (set (map (comp :id :vendor) checked-invoices))))
(when (and (= 1 (count (set (map (comp :id :vendor) (vals checked-invoices)))))
(> balance 0.001))
^{:key "handwritten"} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::handwritten-checks/show checked-invoices])
^{:key "handwritten"} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::handwritten-checks/show (vals checked-invoices)])
:disabled (status/disabled-for print-checks-status)} "Handwritten Check..."])
(when (> balance 0.001)
^{:key "advanced"} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::advanced-print-checks/show checked-invoices])
^{:key "advanced"} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::advanced-print-checks/show (vals checked-invoices)])
:disabled (status/disabled-for print-checks-status)} "Advanced..."]))]]))]]
[:div.is-pulled-right {:style {:margin-right "0.5rem"}}
(into [:div.tags ] (map (fn [{:keys [id invoice-number]}]
(into [:div.tags ] (map (fn [[z {:keys [id invoice-number]}]]
(if (= z "header")
[:span.tag.is-medium "All visible invoices"
[:button.delete.is-small {:on-click
(dispatch-event [::data-page/remove-check :invoices z])}]]
[:span.tag.is-medium invoice-number
[:button.delete.is-small {:on-click
(dispatch-event [::data-page/remove-check :invoices id])}]]) checked-invoices))]]))
(dispatch-event [::data-page/remove-check :invoices id])}]]))
checked-invoices))]]))
(defn unpaid-invoices-content [{:keys [status]}]