Bank balances are visible + import invoices should be open

This commit is contained in:
2021-01-29 07:35:05 -08:00
parent 0dd633c6bf
commit e920c48773
10 changed files with 123 additions and 15 deletions

View File

@@ -3,4 +3,11 @@
{:txes [[{:db/ident :bank-account/start-date
:db/doc "Setting this date prevents older transactions from being imported"
:db/valueType :db.type/instant
:db/cardinality :db.cardinality/one}]]}})
:db/cardinality :db.cardinality/one}]]}
::add-bank-account-current-balance
{:txes [[{:db/ident :bank-account/current-balance
:db/doc "A precomputed balance for the account"
:db/valueType :db.type/double
:db/cardinality :db.cardinality/one
:db/noHistory true}]]}})

View File

@@ -151,7 +151,7 @@
(defn graphql-results [ids db args]
(let [results (->> (d/pull-many db '[* {:transaction/client [:client/name :db/id :client/code]
:transaction/approval-status [:db/ident :db/id]
:transaction/bank-account [:bank-account/name :bank-account/code :bank-account/yodlee-account-id :db/id :bank-account/locations]
:transaction/bank-account [:bank-account/name :bank-account/code :bank-account/yodlee-account-id :db/id :bank-account/locations :bank-account/current-balance]
:transaction/forecast-match [:db/id :forecasted-transaction/identifier]
:transaction/vendor [:db/id :vendor/name]
:transaction/matched-rule [:db/id :transaction-rule/note]
@@ -198,7 +198,7 @@
(d/pull (d/db (d/connect uri))
'[* {:transaction/client [:client/name :db/id :client/code :client/locations]
:transaction/approval-status [:db/ident :db/id]
:transaction/bank-account [:bank-account/name :bank-account/code :bank-account/yodlee-account-id :db/id :bank-account/locations]
:transaction/bank-account [:bank-account/name :bank-account/code :bank-account/yodlee-account-id :db/id :bank-account/locations :bank-account/current-balance]
:transaction/vendor [:db/id :vendor/name]
:transaction/matched-rule [:db/id :transaction-rule/note]
:transaction/accounts [:transaction-account/amount

View File

@@ -139,6 +139,7 @@
:name {:type 'String}
:bank_code {:type 'String}
:bank_name {:type 'String}
:current_balance {:type :money}
:yodlee_account_id {:type 'Int}
:yodlee_account {:type :yodlee_account}
:locations {:type '(list String)}}}

View File

@@ -5,10 +5,13 @@
[clj-time.coerce :as coerce]
[config.core :refer [env]]
[clojure.string :as str]
[unilog.context :as lc]
[clojure.tools.logging :as log]
[datomic.api :as d]
[clojure.java.io :as io]
[amazonica.aws.s3 :as s3])
[amazonica.aws.s3 :as s3]
[yang.scheduler :as scheduler]
[mount.core :as mount])
(:import [org.apache.commons.codec.binary Base64]
java.util.UUID))
@@ -122,6 +125,86 @@
lms)))
->graphql)))
(defn refresh-bank-account-current-balance [bank-account-id]
(let [all-transactions (d/query
{:query {:find ['?e '?debit '?credit]
:in ['$ '?bank-account-id]
:where '[[?e :journal-entry-line/account ?bank-account-id]
[(get-else $ ?e :journal-entry-line/debit 0.0) ?debit]
[(get-else $ ?e :journal-entry-line/credit 0.0) ?credit]]}
:args [(d/db conn) bank-account-id]})
debits (->> all-transactions
(map (fn [[e debit credit]]
debit))
(reduce + 0.0))
credits (->> all-transactions
(map (fn [[e debit credit]]
credit))
(reduce + 0.0))
current-balance (if (= :bank-account-type/check (:bank-account/type (d/entity (d/db conn) bank-account-id)))
(- debits credits)
(- credits debits))]
@(d/transact conn [{:db/id bank-account-id
:bank-account/current-balance current-balance}])))
(defn bank-accounts-needing-refresh []
(let [last-refreshed (->> (d/query
{:query {:find ['?ba '?tx]
:in ['$ ]
:where ['[?ba :bank-account/current-balance _ ?tx true]]}
:args [(d/history (d/db conn))]})
(group-by first)
(map (fn [[ba balance-txes]]
[ba (->> balance-txes
(map second)
(sort-by #(- %))
first)])))
has-newer-transaction (->> (d/query
{:query {:find ['?ba]
:in '[$ [[?ba ?last-refreshed] ...] ]
:where ['[_ :journal-entry-line/account ?ba ?tx]
'[(>= ?tx ?last-refreshed)]]}
:args [(d/history (d/db conn))
last-refreshed]})
(map first)
(set))
no-current-balance (->> (d/query {:query {:find ['?ba]
:in '[$]
:where ['[?ba :bank-account/code]
'(not [?ba :bank-account/current-balance])
]}
:args [(d/db conn)]})
(map first))]
(into has-newer-transaction no-current-balance)))
(defn build-current-balance [bank-accounts]
(doseq [bank-account bank-accounts]
(log/info "Refreshing bank account" (-> (d/db conn) (d/entity bank-account) :bank-account/code))
(try
(refresh-bank-account-current-balance bank-account)
(catch Exception e
(log/error "Can't refresh current balance" e)))))
(defn refresh-all-current-balance []
(lc/with-context {:source "current-balance-cache"}
(build-current-balance (->> (d/query {:query {:find ['?ba]
:in '[$]
:where ['[?ba :bank-account/code]]}
:args [(d/db conn)]})
(map first)))))
(defn refresh-current-balance []
(lc/with-context {:source "current-balance-cache"}
(try
(log/info "Refreshing running balance cache")
(build-current-balance (bank-accounts-needing-refresh))
(catch Exception e
(log/error e)))))
(mount/defstate current-balance-worker
:start (scheduler/every (* 17 60 1000) refresh-current-balance)
:stop (scheduler/stop current-balance-worker))
(defn get-client [context args value]
(->graphql

View File

@@ -1,5 +1,5 @@
(ns auto-ap.graphql.invoices
(:require [auto-ap.graphql.utils :refer [->graphql <-graphql assert-can-see-client assert-admin enum->keyword]]
(:require [auto-ap.graphql.utils :refer [->graphql <-graphql assert-can-see-client assert-admin assert-power-user enum->keyword]]
[auto-ap.datomic.vendors :as d-vendors]
[auto-ap.datomic.clients :as d-clients]
@@ -38,13 +38,17 @@
:count Integer/MAX_VALUE)))))
(defn reject-invoices [context {:keys [invoices] :as in} value]
(assert-admin (:id context))
(assert-power-user (:id context))
(doseq [i invoices]
(assert-can-see-client (:id context) (:db/id (:invoice/client (d/entity (d/db conn) i)))))
(let [transactions (map (fn [i] [:db/retractEntity i ]) invoices)
transaction-result (audit-transact transactions (:id context))]
invoices))
(defn approve-invoices [context {:keys [invoices] :as in} value]
(assert-admin (:id context))
(assert-power-user (:id context))
(doseq [i invoices]
(assert-can-see-client (:id context) (:db/id (:invoice/client (d/entity (d/db conn) i)))))
(let [transactions (map (fn [i] {:db/id i :invoice/import-status :import-status/imported}) invoices)
transaction-result (audit-transact transactions (:id context))]
invoices))

View File

@@ -4,7 +4,7 @@
[auto-ap.datomic.clients :as d-clients]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.datomic.vendors :as d-vendors]
[auto-ap.graphql.utils :refer [assert-admin]]
[auto-ap.graphql.utils :refer [assert-admin assert-can-see-client]]
[auto-ap.logging :refer [info-event]]
[auto-ap.parse :as parse]
[auto-ap.parse.util :as parse-u]
@@ -214,7 +214,7 @@
(throw (ex-info (str "No vendor with the name " vendor-code " was found.")
{:vendor-code vendor-code})))))
(defn import-uploaded-invoice [client forced-location forced-vendor imports]
(defn import-uploaded-invoice [user client forced-location forced-vendor imports]
(lc/with-context {:area "upload-invoice"}
(log/info "Number of invoices to import is" (count imports) "sample: " (first imports))
(let [clients (d-clients/get-all)
@@ -229,6 +229,7 @@
(first (filter (fn [c]
(= (:db/id c) (Long/parseLong client)))
clients))))
_ (assert-can-see-client user (:db/id matching-client))
_ (when-not matching-client
(throw (ex-info (str "Searched clients for '" customer-identifier "'. No client found in file. Select a client first.")
{:invoice-number invoice-number
@@ -485,7 +486,8 @@
location :location
location-2 "location"
vendor :vendor
vendor-2 "vendor"} :params :as params}
vendor-2 "vendor"} :params :as params
user :identity}
(let [files (or files files-2)
client (or client client-2)
location (or location location-2)
@@ -494,7 +496,7 @@
{:keys [filename tempfile]} files]
(lc/with-context {:parsing-file filename}
(try
(import-uploaded-invoice client location vendor (parse/parse-file (.getPath tempfile) filename))
(import-uploaded-invoice user client location vendor (parse/parse-file (.getPath tempfile) filename))
{:status 200
:body (pr-str {})
:headers {"Content-Type" "application/edn"}}

View File

@@ -3,6 +3,7 @@
[auto-ap.ledger :as ledger]
[auto-ap.yodlee.core]
[auto-ap.yodlee.core2 :as yodlee2]
[auto-ap.graphql.clients :as gq-clients]
[auto-ap.background.invoices]
[auto-ap.square.core :as square]
[auto-ap.datomic.migrate :as migrate]
@@ -35,6 +36,7 @@
#'ledger/touch-broken-ledger-worker
#'ledger/process-txes-worker
#'ledger/ledger-reconciliation-worker
#'gq-clients/current-balance-worker
#'yodlee/import-transaction-worker
#'yodlee2/yodlee-sync-worker
#'migrate/migrate-start]))]

View File

@@ -46,7 +46,8 @@
[:span {:class "icon icon-bin-2" :style {:font-size "25px"}}]
[:span {:class "name"} "Voided Invoices"]]]
(when (= "admin" (:user/role user))
(when (or (= "admin" (:user/role user))
(= "power-user" (:user/role user)))
[:li.menu-item
[:a.item {:href (bidi/path-for routes/routes :import-invoices)
:class [(active-when ap = :import-invoices)]}

View File

@@ -16,4 +16,4 @@
:description_original
[:payment [:check_number :s3_url :id :date]]
[:client [:name :id]]
[:bank-account [:name :yodlee-account-id]]])
[:bank-account [:name :yodlee-account-id :current-balance]]])

View File

@@ -67,7 +67,9 @@
(defn table [{:keys [id data-page check-boxes?]}]
(let [selected-client @(re-frame/subscribe [::subs/client])
{:keys [data status params]} @(re-frame/subscribe [::data-page/page data-page])
states @(re-frame/subscribe [::status/multi ::edits])]
states @(re-frame/subscribe [::status/multi ::edits])
is-power-user? @(re-frame/subscribe [::subs/is-power-user?])
is-admin? @(re-frame/subscribe [::subs/is-admin?])]
[grid/grid {:data-page data-page
:column-count (if selected-client 6 7)
:check-boxes? check-boxes?}
@@ -92,7 +94,13 @@
[grid/cell {} (:name client)])
#_[:td description-original]
[grid/cell {} (:name bank-account )]
[grid/cell {}
(if (and (:current-balance bank-account)
(or is-power-user?
is-admin?))
[:span.has-tooltip-arrow.has-tooltip-right {:data-tooltip (str "Current Balance: " (nf (:current-balance bank-account) ))}
(:name bank-account ) ]
(:name bank-account ))]
[grid/cell {} (cond vendor
(:name vendor)
yodlee-merchant