Fixes issue with stepping on each others toes when importing invoices

This commit is contained in:
2024-02-07 21:51:01 -08:00
parent 8f3eff4b01
commit 56215621c9
2 changed files with 100 additions and 94 deletions

View File

@@ -149,6 +149,7 @@
:uberjar :uberjar
{:java-cmd "/usr/lib/jvm/java-11-openjdk/bin/java" {:java-cmd "/usr/lib/jvm/java-11-openjdk/bin/java"
:prep-tasks ["fig:min" ] :prep-tasks ["fig:min" ]
:aot [auto-ap.server auto-ap.time clj-time.core clj-time.coerce clj-time.format clojure.tools.logging.impl ]
:dependencies [[com.bhauman/figwheel-main "0.2.18" :exclusions [org.clojure/clojurescript :dependencies [[com.bhauman/figwheel-main "0.2.18" :exclusions [org.clojure/clojurescript
ring ring
ring/ring-core ring/ring-core
@@ -180,9 +181,6 @@
} }
:main auto-ap.server :main auto-ap.server
#_#_:aot [auto-ap.server auto-ap.time clj-time.core clj-time.coerce clj-time.format clojure.tools.logging.impl]
:uberjar-name "auto-ap.jar" :uberjar-name "auto-ap.jar"
:test-paths ["test/clj"] :test-paths ["test/clj"]
:test-selectors {:integration (fn [m] :test-selectors {:integration (fn [m]

View File

@@ -25,7 +25,7 @@
[datomic.api :as dc] [datomic.api :as dc]
[auto-ap.solr :as solr])) [auto-ap.solr :as solr]))
(defn ->graphql [invoice user ] (defn ->graphql [invoice user]
(if (= "admin" (:user/role user)) (if (= "admin" (:user/role user))
(u/->graphql invoice) (u/->graphql invoice)
(u/->graphql (if (:invoice/source-url-admin-only invoice) (u/->graphql (if (:invoice/source-url-admin-only invoice)
@@ -56,26 +56,35 @@
(first (d-invoices/get-graphql (assoc (<-graphql args) (first (d-invoices/get-graphql (assoc (<-graphql args)
:count Integer/MAX_VALUE))))) :count Integer/MAX_VALUE)))))
(defn pending-invoices [invoices]
(->> (dc/q '[:find ?i
:in $ [?i ...]
:where [?i :invoice/import-status :import-status/pending]]
(dc/db conn)
invoices)
(map first)))
(defn reject-invoices [context {:keys [invoices]} _] (defn reject-invoices [context {:keys [invoices]} _]
(assert-power-user (:id context)) (assert-power-user (:id context))
(doseq [i invoices] (let [invoices (pending-invoices invoices)]
(assert-can-see-client (:id context) (:db/id (:invoice/client (dc/pull (dc/db conn) [{:invoice/client [:db/id]}] i))))) (doseq [i invoices]
(let [transactions (mapcat (fn [i] [[:db/retractEntity i]]) (assert-can-see-client (:id context) (:db/id (:invoice/client (dc/pull (dc/db conn) [{:invoice/client [:db/id]}] i)))))
invoices)] (let [transactions (mapcat (fn [i] [[:db/retractEntity i]])
(audit-transact transactions (:id context)) invoices)]
invoices)) (audit-transact transactions (:id context))
invoices)))
(defn approve-invoices [context {:keys [invoices]} _] (defn approve-invoices [context {:keys [invoices]} _]
(assert-power-user (:id context)) (assert-power-user (:id context))
(doseq [i invoices (let [invoices (pending-invoices invoices)]
:let [invoice (dc/pull (dc/db conn) [{:invoice/client [:db/id]} (doseq [i invoices
:invoice/date] :let [invoice (dc/pull (dc/db conn) '[{:invoice/client [:db/id]}
i)]] :invoice/date] i)]]
(assert-can-see-client (:id context) (-> invoice :invoice/client :db/id)) (assert-can-see-client (:id context) (-> invoice :invoice/client :db/id))
(assert-not-locked (-> invoice :invoice/client :db/id) (-> invoice :invoice/date))) (assert-not-locked (-> invoice :invoice/client :db/id) (-> invoice :invoice/date)))
(let [transactions (map (fn [i] [:upsert-invoice {:db/id i :invoice/import-status :import-status/imported}]) invoices)] (let [transactions (map (fn [i] [:upsert-invoice {:db/id i :invoice/import-status :import-status/imported}]) invoices)]
(audit-transact transactions (:id context)) (audit-transact transactions (:id context))
invoices)) invoices)))
(defn assert-no-conflicting [{:keys [invoice_number client_id vendor_id]}] (defn assert-no-conflicting [{:keys [invoice_number client_id vendor_id]}]
(when-not vendor_id (when-not vendor_id
@@ -141,7 +150,7 @@
(when (and (= :allowance/denied (when (and (= :allowance/denied
(:db/ident (:account/invoice-allowance account))) (:db/ident (:account/invoice-allowance account)))
(not= (pull-ref (dc/db conn) :vendor/default-account vendor_id ) (not= (pull-ref (dc/db conn) :vendor/default-account vendor_id)
(:db/id account))) (:db/id account)))
(let [err (str "Account isn't allowed for invoice use.")] (let [err (str "Account isn't allowed for invoice use.")]
(throw (ex-info err (throw (ex-info err
@@ -227,7 +236,7 @@
:invoice/total total :invoice/total total
:invoice/outstanding-balance (- total paid-amount) :invoice/outstanding-balance (- total paid-amount)
:invoice/expense-accounts (map expense-account->entity :invoice/expense-accounts (map expense-account->entity
expense_accounts) expense_accounts)
:invoice/due (coerce/to-date due) :invoice/due (coerce/to-date due)
:invoice/scheduled-payment (coerce/to-date scheduled_payment)}] :invoice/scheduled-payment (coerce/to-date scheduled_payment)}]
(audit-transact [[:upsert-invoice updated-invoice]] (audit-transact [[:upsert-invoice updated-invoice]]
@@ -241,12 +250,12 @@
(assert-can-see-client (:id context) (:db/id (:invoice/client invoice))) (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
(assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice)) (assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice))
(audit-transact [[:upsert-invoice {:db/id id (audit-transact [[:upsert-invoice {:db/id id
:invoice/total 0.0 :invoice/total 0.0
:invoice/outstanding-balance 0.0 :invoice/outstanding-balance 0.0
:invoice/status :invoice-status/voided :invoice/status :invoice-status/voided
:invoice/expense-accounts (map (fn [ea] {:db/id (:db/id ea) :invoice/expense-accounts (map (fn [ea] {:db/id (:db/id ea)
:invoice-expense-account/amount 0.0}) :invoice-expense-account/amount 0.0})
(:invoice/expense-accounts invoice))}]] (:invoice/expense-accounts invoice))}]]
(:id context)) (:id context))
(-> (d-invoices/get-by-id id) (->graphql (:id context))))) (-> (d-invoices/get-by-id id) (->graphql (:id context)))))
@@ -267,13 +276,13 @@
(defn all-ids-not-locked [all-ids] (defn all-ids-not-locked [all-ids]
(->> all-ids (->> all-ids
(dc/q '[:find ?i (dc/q '[:find ?i
:in $ [?i ...] :in $ [?i ...]
:where :where
[?i :invoice/client ?c] [?i :invoice/client ?c]
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu] [(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
[?i :invoice/date ?d] [?i :invoice/date ?d]
[(>= ?d ?lu)]] [(>= ?d ?lu)]]
(dc/db conn)) (dc/db conn))
(map first))) (map first)))
(defn void-invoices [context args _] (defn void-invoices [context args _]
@@ -291,15 +300,14 @@
[(>= ?d ?lu)]] [(>= ?d ?lu)]]
(dc/db conn) (dc/db conn)
all-ids) all-ids)
(map first)) (map first))]
]
(alog/info ::void-payments :count (count voidable-cash-payments)) (alog/info ::void-payments :count (count voidable-cash-payments))
(gq-checks/void-payments-internal voidable-cash-payments (:id context)) (gq-checks/void-payments-internal voidable-cash-payments (:id context))
(alog/info ::voiding-invoices :count (count all-ids)) (alog/info ::voiding-invoices :count (count all-ids))
(audit-transact (audit-transact
(->> all-ids (->> all-ids
(dc/q '[:find (pull ?i [:db/id :invoice/date {:invoice/expense-accounts [:db/id]}]) (dc/q '[:find (pull ?i [:db/id :invoice/date {:invoice/expense-accounts [:db/id]}])
:in $ [?i ...] :in $ [?i ...]
:where (not [_ :invoice-payment/invoice ?i]) :where (not [_ :invoice-payment/invoice ?i])
[?i :invoice/client ?c] [?i :invoice/client ?c]
@@ -307,18 +315,18 @@
[?i :invoice/date ?d] [?i :invoice/date ?d]
[(>= ?d ?lu)]] [(>= ?d ?lu)]]
(dc/db conn)) (dc/db conn))
(map (map
(fn [[i]] (fn [[i]]
[:upsert-invoice {:db/id (:db/id i) [:upsert-invoice {:db/id (:db/id i)
:invoice/total 0.0 :invoice/total 0.0
:invoice/outstanding-balance 0.0 :invoice/outstanding-balance 0.0
:invoice/status :invoice-status/voided :invoice/status :invoice-status/voided
:invoice/expense-accounts (mapv :invoice/expense-accounts (mapv
(fn [iea] (fn [iea]
{:db/id (:db/id iea) {:db/id (:db/id iea)
:invoice-expense-account/amount 0.0}) :invoice-expense-account/amount 0.0})
(:invoice/expense-accounts i))}]))) (:invoice/expense-accounts i))}])))
(:id context)) (:id context))
{:message (str "Succesfully voided " (count all-ids))})) {:message (str "Succesfully voided " (count all-ids))}))
(defn unvoid-invoice [context {id :invoice_id} _] (defn unvoid-invoice [context {id :invoice_id} _]
@@ -377,16 +385,16 @@
(let [invoice-id (:invoice_id args) (let [invoice-id (:invoice_id args)
invoice (d-invoices/get-by-id invoice-id) invoice (d-invoices/get-by-id invoice-id)
_ (assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice)) _ (assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice))
_ (assert-valid-expense-accounts (:expense_accounts args) (:db/id (:invoice/vendor invoice )))] _ (assert-valid-expense-accounts (:expense_accounts args) (:db/id (:invoice/vendor invoice)))]
(audit-transact [[:upsert-invoice {:db/id invoice-id (audit-transact [[:upsert-invoice {:db/id invoice-id
:invoice/expense-accounts (map :invoice/expense-accounts (map
expense-account->entity expense-account->entity
(:expense_accounts args))}]] (:expense_accounts args))}]]
(:id context)) (:id context))
(->graphql (->graphql
(d-invoices/get-by-id (:invoice_id args)) (d-invoices/get-by-id (:invoice_id args))
(:id context)))) (:id context))))
;; TODO - multiple versions of this now exist. fix in datomic migration? ;; TODO - multiple versions of this now exist. fix in datomic migration?
;; Approach could be something like this: (thanks chatgpt) ;; Approach could be something like this: (thanks chatgpt)
@@ -406,31 +414,31 @@
(defn maybe-code-accounts [invoice account-rules valid-locations] (defn maybe-code-accounts [invoice account-rules valid-locations]
(with-precision 2 (with-precision 2
(let [accounts (vec (mapcat (let [accounts (vec (mapcat
(fn [ar] (fn [ar]
(let [cents-to-distribute (int (Math/round (Math/abs (* (:percentage ar) (let [cents-to-distribute (int (Math/round (Math/abs (* (:percentage ar)
(:invoice/total invoice) (:invoice/total invoice)
100))))] 100))))]
(if (= "Shared" (:location ar)) (if (= "Shared" (:location ar))
(do (do
(->> valid-locations (->> valid-locations
(map (map
(fn [cents location] (fn [cents location]
{:db/id (random-tempid) {:db/id (random-tempid)
:invoice-expense-account/account (:account_id ar) :invoice-expense-account/account (:account_id ar)
:invoice-expense-account/amount (* 0.01 cents) :invoice-expense-account/amount (* 0.01 cents)
:invoice-expense-account/location location}) :invoice-expense-account/location location})
(rm/spread-cents cents-to-distribute (count valid-locations))))) (rm/spread-cents cents-to-distribute (count valid-locations)))))
[(cond-> {:db/id (random-tempid) [(cond-> {:db/id (random-tempid)
:invoice-expense-account/account (:account_id ar) :invoice-expense-account/account (:account_id ar)
:invoice-expense-account/amount (* 0.01 cents-to-distribute)} :invoice-expense-account/amount (* 0.01 cents-to-distribute)}
(:location ar) (assoc :invoice-expense-account/location (:location ar)))]))) (:location ar) (assoc :invoice-expense-account/location (:location ar)))])))
account-rules)) account-rules))
accounts (mapv accounts (mapv
(fn [a] (fn [a]
(update a :invoice-expense-account/amount (update a :invoice-expense-account/amount
#(with-precision 2 #(with-precision 2
(double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP))))) (double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP)))))
accounts) accounts)
leftover (with-precision 2 (.round (bigdec (- (Math/abs (:invoice/total invoice)) leftover (with-precision 2 (.round (bigdec (- (Math/abs (:invoice/total invoice))
(Math/abs (reduce + 0.0 (map #(:invoice-expense-account/amount %) accounts))))) (Math/abs (reduce + 0.0 (map #(:invoice-expense-account/amount %) accounts)))))
*math-context*)) *math-context*))
@@ -454,26 +462,26 @@
invoices (pull-many (dc/db conn) '[:db/id :invoice/total] (vec all-ids)) invoices (pull-many (dc/db conn) '[:db/id :invoice/total] (vec all-ids))
account-total (reduce + 0 (map (fn [x] (:percentage x)) (:accounts args)))] account-total (reduce + 0 (map (fn [x] (:percentage x)) (:accounts args)))]
(when (when
(not (dollars= 1.0 account-total)) (not (dollars= 1.0 account-total))
(let [error (str "Account total (" account-total ") does not reach 100%")] (let [error (str "Account total (" account-total ") does not reach 100%")]
(throw (ex-info error {:validation-error error})))) (throw (ex-info error {:validation-error error}))))
(doseq [a (:accounts args) (doseq [a (:accounts args)
:let [{:keys [:account/location :account/name]} (dc/pull (dc/db conn) [:account/location :account/name] (:account_id a))]] :let [{:keys [:account/location :account/name]} (dc/pull (dc/db conn) [:account/location :account/name] (:account_id a))]]
(when (and location (not= location (:location a))) (when (and location (not= location (:location a)))
(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 (and (not location) (when (and (not location)
(not (get (into #{"Shared"} locations) (not (get (into #{"Shared"} locations)
(:location a)))) (:location a))))
(let [err (str "Account " name " uses location " (:location a) ", but doesn't belong to the client.")] (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})))))
(alog/info ::bulk-code :count (count all-ids)) (alog/info ::bulk-code :count (count all-ids))
(audit-transact-batch (audit-transact-batch
(map (fn [i] (map (fn [i]
[:upsert-invoice {:db/id (:db/id i) [:upsert-invoice {:db/id (:db/id i)
:invoice/expense-accounts (maybe-code-accounts i (:accounts args) locations)}]) :invoice/expense-accounts (maybe-code-accounts i (:accounts args) locations)}])
invoices) invoices)
(:id context)) (:id context))
{:message (str "Successfully coded " (count all-ids) " invoices.")})) {:message (str "Successfully coded " (count all-ids) " invoices.")}))
(def objects (def objects