Supports ledger ignore

This commit is contained in:
Bryce Covert
2020-10-06 07:25:46 -07:00
parent cdccaf0572
commit 71fc8f69eb
6 changed files with 114 additions and 83 deletions

View File

@@ -420,6 +420,7 @@
:import_ledger_result {:fields {:successful {:type '(list :import_ledger_entry_result)} :import_ledger_result {:fields {:successful {:type '(list :import_ledger_entry_result)}
:existing {:type '(list :import_ledger_entry_result)} :existing {:type '(list :import_ledger_entry_result)}
:ignored {:type '(list :import_ledger_entry_result)}
:errors {:type '(list :import_ledger_entry_result)} :errors {:type '(list :import_ledger_entry_result)}
}} }}

View File

@@ -201,7 +201,9 @@
(try (try
(f entry) (f entry)
(catch Exception e (catch Exception e
(assoc entry :error (.getMessage e)))))) (assoc entry :error (.getMessage e)
:status (or (:status (ex-data e))
:error))))))
(defn delete-external-ledger [context args value] (defn delete-external-ledger [context args value]
(let [_ (assert-admin (:id context)) (let [_ (assert-admin (:id context))
@@ -274,91 +276,104 @@
lis))))] lis))))]
(let [vendor (all-vendors (:vendor_name entry))] (let [vendor (all-vendors (:vendor_name entry))]
(when-not (all-clients (:client_code entry)) (when-not (all-clients (:client_code entry))
(throw (Exception. (str "Client '" (:client_code entry )"' not found.")) )) (throw (ex-info (str "Client '" (:client_code entry )"' not found.") {:status :error}) ))
(when-not vendor (when-not vendor
(throw (Exception. (str "Vendor '" (:vendor_name entry) "' not found.")))) (throw (ex-info (str "Vendor '" (:vendor_name entry) "' not found.") {:status :error})))
(when-not (re-find #"\d{1,2}/\d{1,2}/\d{4}" (:date entry)) (when-not (re-find #"\d{1,2}/\d{1,2}/\d{4}" (:date entry))
(throw (Exception. (str "Date must be MM/dd/yyyy")))) (throw (ex-info (str "Date must be MM/dd/yyyy") {:status :error})))
(when-not (dollars= (reduce + 0.0 (map :debit (:line_items entry))) (when-not (dollars= (reduce + 0.0 (map :debit (:line_items entry)))
(reduce + 0.0 (map :credit (:line_items entry)))) (reduce + 0.0 (map :credit (:line_items entry))))
(throw (Exception. (str "Debits '" (throw (ex-info (str "Debits '"
(reduce + 0 (map :debit (:line_items entry))) (reduce + 0 (map :debit (:line_items entry)))
"' and credits '" "' and credits '"
(reduce + 0 (map :credit (:line_items entry))) (reduce + 0 (map :credit (:line_items entry)))
"' do not add up.")))) "' do not add up.")
{:status :error})))
(when (dollars= (reduce + 0.0 (map :debit (:line_items entry))) (when (dollars= (reduce + 0.0 (map :debit (:line_items entry)))
0.0) 0.0)
(throw (Exception. (str "Cannot have ledger entries that total $0.00")))) (throw (ex-info (str "Cannot have ledger entries that total $0.00")
(remove-nils {:status :ignored})))
{:journal-entry/source (:source entry) (assoc entry
:journal-entry/client [:client/code (:client_code entry)] :status :success
:journal-entry/date (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry))) :tx
:journal-entry/external-id (:external_id entry) (remove-nils
:journal-entry/vendor (:db/id (doto (all-vendors (:vendor_name entry)) {:journal-entry/source (:source entry)
println)) :journal-entry/client [:client/code (:client_code entry)]
:journal-entry/amount (:amount entry) :journal-entry/date (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry)))
:journal-entry/note (:note entry) :journal-entry/external-id (:external_id entry)
:journal-entry/cleared-against (:cleared_against entry) :journal-entry/vendor (:db/id (all-vendors (:vendor_name entry)))
:journal-entry/amount (:amount entry)
:journal-entry/note (:note entry)
:journal-entry/cleared-against (:cleared_against entry)
:journal-entry/line-items :journal-entry/line-items
(mapv (fn [ea] (mapv (fn [ea]
(when-not (get (when-not (get
(get all-client-locations (:client_code entry)) (get all-client-locations (:client_code entry))
(:location ea)) (:location ea))
(throw (Exception. (str "Location '" (:location ea) "' not found.")))) (throw (ex-info (str "Location '" (:location ea) "' not found.")
(when (and (<= (:debit ea 0.0) 0.0) {:status :error})))
(<= (:credit ea 0.0) 0.0)) (when (and (<= (:debit ea 0.0) 0.0)
(throw (Exception. (str "Line item amount " (or (:debit ea) (:credit ea)) " must be greater than 0.")))) (<= (:credit ea 0.0) 0.0))
(when (and (not (all-accounts (:account_identifier ea))) (throw (ex-info (str "Line item amount " (or (:debit ea) (:credit ea)) " must be greater than 0.")
(not (get {:status :error})))
(get all-client-bank-accounts (:client_code entry)) (when (and (not (all-accounts (:account_identifier ea)))
(:account_identifier ea)))) (not (get
(throw (Exception. (str "Account '" (:account_identifier ea) "' not found.")))) (get all-client-bank-accounts (:client_code entry))
(let [matching-account (when (re-matches #"^[0-9]+$" (:account_identifier ea)) (:account_identifier ea))))
(a/get-account-by-numeric-code-and-sets (Integer/parseInt (:account_identifier ea)) ["default"]))] (throw (ex-info (str "Account '" (:account_identifier ea) "' not found.")
(when (and matching-account {:status :error})))
(:account/location matching-account) (let [matching-account (when (re-matches #"^[0-9]+$" (:account_identifier ea))
(not= (:account/location matching-account) (a/get-account-by-numeric-code-and-sets (Integer/parseInt (:account_identifier ea)) ["default"]))]
(:location ea))) (when (and matching-account
(throw (Exception. (str "Account '" (:account/location matching-account)
(:account/numeric-code matching-account) (not= (:account/location matching-account)
"' requires location '" (:location ea)))
(:account/location matching-account) (throw (ex-info (str "Account '"
"' but got '" (:account/numeric-code matching-account)
(:location ea) "' requires location '"
"'" (:account/location matching-account)
)))) "' but got '"
(:location ea)
"'")
{:status :error})))
(when (and matching-account (when (and matching-account
(not (:account/location matching-account)) (not (:account/location matching-account))
(= "A" (:location ea))) (= "A" (:location ea)))
(throw (Exception. (str "Account '" (throw (ex-info (str "Account '"
(:account/numeric-code matching-account) (:account/numeric-code matching-account)
"' cannot use location '" "' cannot use location '"
(:location ea) (:location ea)
"'")))) "'")
(remove-nils (cond-> {:journal-entry-line/location (:location ea) {:status :error})))
:journal-entry-line/debit (when (> (:debit ea) 0) (remove-nils (cond-> {:journal-entry-line/location (:location ea)
(:debit ea)) :journal-entry-line/debit (when (> (:debit ea) 0)
:journal-entry-line/credit (when (> (:credit ea) 0) (:debit ea))
(:credit ea))} :journal-entry-line/credit (when (> (:credit ea) 0)
matching-account (assoc :journal-entry-line/account (:db/id matching-account)) (:credit ea))}
(not matching-account) (assoc :journal-entry-line/account [:bank-account/code (:account_identifier ea)]))))) matching-account (assoc :journal-entry-line/account (:db/id matching-account))
(:line_items entry)) (not matching-account) (assoc :journal-entry-line/account [:bank-account/code (:account_identifier ea)])))))
(:line_items entry))
:journal-entry/cleared true})))))
:journal-entry/cleared true}))))))
(:entries args))) (:entries args)))
errors (filter :error transaction) errors (filter #(= (:status %) :error) transaction)
success (filter (comp not :error) transaction) ignored (filter #(= (:status %) :ignored) transaction)
retraction (mapv (fn [x] [:db/retractEntity [:journal-entry/external-id (:journal-entry/external-id x)]]) success (filter #(= (:status %) :success) transaction)
retraction (mapv (fn [x] [:db/retractEntity [:journal-entry/external-id (:external_id x)]])
success)] success)]
(log/info "manual ledger import has " (count success) " new rows") (log/info "manual ledger import has " (count success) " new rows")
(audit-transact-batch retraction (:id context)) (audit-transact-batch retraction (:id context))
(audit-transact-batch success (:id context)) (log/info (map :tx success))
(audit-transact-batch (map :tx success) (:id context))
{:successful (map (fn [x] {:external_id (:journal-entry/external-id x)}) success) {:successful (map (fn [x] {:external_id (:external_id x)}) success)
:ignored (map (fn [x]
{:external_id (:external_id x)})
ignored)
:existing [] :existing []
:errors (map (fn [x] {:external_id (:external_id x) :errors (map (fn [x] {:external_id (:external_id x)
:error (:error x)}) errors)}))) :error (:error x)}) errors)})))

View File

@@ -82,9 +82,10 @@
(let [err (str "Account " name " uses location " (:location a) ", but is supposed to be " location)] (let [err (str "Account " name " uses location " (:location a) ", but is supposed to be " location)]
(throw (ex-info err {:validation-error err}) ))) (throw (ex-info err {:validation-error err}) )))
(when (not (get (into #{"Shared"} (:client/locations client)) (when (and (not location)
(:location a))) (not (get (into #{"Shared"} (:client/locations client))
(let [err (str "Account " name " uses location " (:location a) ", but doesn't belong to the client " location)] (:location a))))
(let [err (str "Account " name " uses location " (:location a) ", but doesn't belong to the client.")]
(throw (ex-info err {:validation-error err}) )))) (throw (ex-info err {:validation-error err}) ))))
rule-id (if id rule-id (if id
id id

View File

@@ -62,10 +62,11 @@
(fn [{:keys [db]} [_ result]] (fn [{:keys [db]} [_ result]]
(let [successful-set (set (map :external-id (:successful (:import-ledger result)))) (let [successful-set (set (map :external-id (:successful (:import-ledger result))))
error-set (into {} (map (juxt :external-id :error) (:errors (:import-ledger result)))) error-set (into {} (map (juxt :external-id :error) (:errors (:import-ledger result))))
existing-set (set (map :external-id (:existing (:import-ledger result))))] existing-set (set (map :external-id (:existing (:import-ledger result))))
ignored-set (set (map :external-id (:ignored (:import-ledger result))))]
{:db (-> (forms/save-succeeded db ::form ) {:db (-> (forms/save-succeeded db ::form )
(assoc-in [::forms/forms ::form :result] {:errors error-set :success successful-set :existing existing-set}) (assoc-in [::forms/forms ::form :result] {:errors error-set :success successful-set :existing existing-set :ignored ignored-set})
(update-in [::forms/forms ::form :data :line-items] (update-in [::forms/forms ::form :data :line-items]
(fn [lis] (fn [lis]
(mapv (mapv
@@ -73,6 +74,9 @@
(cond (successful-set (line->id %)) (cond (successful-set (line->id %))
[:span.icon [:i.fa.fa-check]] [:span.icon [:i.fa.fa-check]]
(ignored-set (line->id %))
[:span.icon [:i.fa.fa-minus-circle]]
(existing-set (line->id %)) (existing-set (line->id %))
"" ""
@@ -91,6 +95,10 @@
(cond (successful-set (line->id %)) (cond (successful-set (line->id %))
:success :success
(ignored-set (line->id %))
:ignored
(existing-set (line->id %)) (existing-set (line->id %))
:existing :existing
@@ -111,6 +119,7 @@
{:entries @(re-frame/subscribe [::request])} {:entries @(re-frame/subscribe [::request])}
[[:successful [:external-id]] [[:successful [:external-id]]
[:existing [:external-id]] [:existing [:external-id]]
[:ignored [:external-id]]
[:errors [:external-id :error]]]]}]} [:errors [:external-id :error]]]]}]}
:on-success [::imported] :on-success [::imported]
:on-error [::forms/save-error ::form]}}))) :on-error [::forms/save-error ::form]}})))
@@ -191,7 +200,10 @@
:on-click (dispatch-event [::importing])} "Import"]]] :on-click (dispatch-event [::importing])} "Import"]]]
(when result (when result
[:div.notification [:div.notification
"Imported with " (count (:errors result)) " errors, " (count (:success result)) " successful."]) "Imported with "
(count (:errors result)) " errors, "
(count (:ignored result)) " ignored, "
(count (:success result)) " successful."])
(if @(re-frame/subscribe [::forms/is-loading? ::form]) (if @(re-frame/subscribe [::forms/is-loading? ::form])
[:div [:i.icon.fa.fa-spin.fa-spinner]] [:div [:i.icon.fa.fa-spin.fa-spinner]]
[:div [:div

View File

@@ -38,7 +38,8 @@
[grid/cell {} ] [grid/cell {} ]
[grid/cell {} ] [grid/cell {} ]
[grid/cell {} ] [grid/cell {} ]
[grid/cell {} (if (:name account) [grid/cell {}
(if (:name account)
[:span.has-tooltip-arrow.has-tooltip-right {:data-tooltip (str "Balance as of this entry: " (nf running-balance ))} [:span.has-tooltip-arrow.has-tooltip-right {:data-tooltip (str "Balance as of this entry: " (nf running-balance ))}
(str location ": " (:name account)) ] (str location ": " (:name account)) ]
[:i "unknown"])] [:i "unknown"])]

View File

@@ -5,17 +5,18 @@
[clojure.test :as t :refer [deftest is testing use-fixtures]] [clojure.test :as t :refer [deftest is testing use-fixtures]]
[clj-time.core :as time] [clj-time.core :as time]
[datomic.api :as d] [datomic.api :as d]
[auto-ap.datomic :refer [uri]] [auto-ap.datomic :refer [uri conn]]
[buddy.sign.jwt :as jwt] [buddy.sign.jwt :as jwt]
[config.core :refer [env]])) [config.core :refer [env]]))
(defn wrap-setup (defn wrap-setup
[f] [f]
(with-redefs [auto-ap.datomic/uri "datomic:mem://datomic-transactor:4334/invoice"] (with-redefs [auto-ap.datomic/uri "datomic:mem://datomic-transactor:4334/invoice"]
(d/create-database uri) (d/create-database uri)
(m/-main false) (with-redefs [auto-ap.datomic/conn (d/connect uri)]
(f) (m/migrate conn)
(d/release (d/connect uri)) (f)
(d/delete-database uri))) (d/release conn)
(d/delete-database uri))))
(defn admin-token [] (defn admin-token []
{:user "TEST ADMIN" {:user "TEST ADMIN"