Bank balances are visible + import invoices should be open
This commit is contained in:
@@ -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}]]}})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)}}}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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"}}
|
||||
|
||||
@@ -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]))]
|
||||
|
||||
@@ -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)]}
|
||||
|
||||
@@ -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]]])
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user