Fixes issue with stepping on each others toes when importing invoices
This commit is contained in:
@@ -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]
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -107,7 +116,7 @@
|
|||||||
nil)
|
nil)
|
||||||
_ (when-not (:db/id account)
|
_ (when-not (:db/id account)
|
||||||
(throw (ex-info (str "Vendor '" (:vendor/name vendor) "' does not have a default expense acount.") {:vendor-id vendor_id})))]
|
(throw (ex-info (str "Vendor '" (:vendor/name vendor) "' does not have a default expense acount.") {:vendor-id vendor_id})))]
|
||||||
|
|
||||||
[:upsert-invoice {:db/id "invoice"
|
[:upsert-invoice {:db/id "invoice"
|
||||||
:invoice/invoice-number invoice_number
|
:invoice/invoice-number invoice_number
|
||||||
:invoice/client client_id
|
:invoice/client client_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
|
||||||
@@ -188,7 +197,7 @@
|
|||||||
|
|
||||||
(defn add-and-print-invoice [context {{:keys [total client_id vendor_id] :as in} :invoice bank-account-id :bank_account_id type :type} _]
|
(defn add-and-print-invoice [context {{:keys [total client_id vendor_id] :as in} :invoice bank-account-id :bank_account_id type :type} _]
|
||||||
(mu/trace ::validating-invoice [:invoice in]
|
(mu/trace ::validating-invoice [:invoice in]
|
||||||
(do
|
(do
|
||||||
(assert-no-conflicting in)
|
(assert-no-conflicting in)
|
||||||
(assert-can-see-client (:id context) client_id)
|
(assert-can-see-client (:id context) client_id)
|
||||||
(assert-bank-account-belongs client_id bank-account-id)
|
(assert-bank-account-belongs client_id bank-account-id)
|
||||||
@@ -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} _]
|
||||||
@@ -363,10 +371,10 @@
|
|||||||
(assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice))
|
(assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice))
|
||||||
(assert (not (seq (:invoice-payment/_invoice invoice))))
|
(assert (not (seq (:invoice-payment/_invoice invoice))))
|
||||||
(audit-transact [[:upsert-invoice
|
(audit-transact [[:upsert-invoice
|
||||||
{:db/id id
|
{:db/id id
|
||||||
:invoice/status :invoice-status/unpaid
|
:invoice/status :invoice-status/unpaid
|
||||||
:invoice/outstanding-balance (:invoice/total invoice)
|
:invoice/outstanding-balance (:invoice/total invoice)
|
||||||
:invoice/scheduled-payment nil}]]
|
:invoice/scheduled-payment nil}]]
|
||||||
(:id context))
|
(:id context))
|
||||||
|
|
||||||
(-> (d-invoices/get-by-id id)
|
(-> (d-invoices/get-by-id 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*))
|
||||||
@@ -446,7 +454,7 @@
|
|||||||
(when-not (:client_id args)
|
(when-not (:client_id args)
|
||||||
(throw (ex-info "Client is required"
|
(throw (ex-info "Client is required"
|
||||||
{:validation-error "client is required"})))
|
{:validation-error "client is required"})))
|
||||||
(let [args (assoc args :clients [{:db/id (:client_id args)}])
|
(let [args (assoc args :clients [{:db/id (:client_id args)}])
|
||||||
locations (pull-attr (dc/db conn)
|
locations (pull-attr (dc/db conn)
|
||||||
:client/locations
|
:client/locations
|
||||||
(:client_id args))
|
(:client_id args))
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user