(cloud) experimental approach to ensure the ledgers do not get out of sync
This commit is contained in:
@@ -220,6 +220,82 @@
|
|||||||
result
|
result
|
||||||
nil)))
|
nil)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn invoice->journal-entry
|
||||||
|
([db invoice-id]
|
||||||
|
(invoice->journal-entry db invoice-id invoice-id))
|
||||||
|
;; the 3-arity version allows you to pass a potential tempid in instead of the invoice-id,
|
||||||
|
;; which would be a temporary value after the transaction
|
||||||
|
([db invoice-id raw-invoice-id]
|
||||||
|
(let [entity (dc/pull db
|
||||||
|
'[:invoice/total
|
||||||
|
:invoice/exclude-from-ledger
|
||||||
|
:invoice/outstanding-balance
|
||||||
|
:invoice/date
|
||||||
|
{:invoice/vendor [:db/id :vendor/name]
|
||||||
|
:invoice/client [:db/id :client/code]
|
||||||
|
:invoice/payment [:db/id {:payment/status [:db/ident]}]
|
||||||
|
:invoice/status [:db/ident]
|
||||||
|
:invoice/import-status [:db/ident]
|
||||||
|
:invoice/expense-accounts [:invoice-expense-account/account
|
||||||
|
:invoice-expense-account/amount
|
||||||
|
:invoice-expense-account/location]}]
|
||||||
|
invoice-id)
|
||||||
|
credit-invoice? (< (:invoice/total entity 0.0) 0.0)]
|
||||||
|
(when-not (or
|
||||||
|
(not (:invoice/total entity))
|
||||||
|
(= true (:invoice/exclude-from-ledger entity))
|
||||||
|
(= :import-status/pending (:db/ident (:invoice/import-status entity)))
|
||||||
|
(= :invoice-status/voided (:db/ident (:invoice/status entity)))
|
||||||
|
(< -0.001 (:invoice/total entity) 0.001))
|
||||||
|
|
||||||
|
(remove-nils
|
||||||
|
{:journal-entry/source "invoice"
|
||||||
|
:journal-entry/client (:db/id (:invoice/client entity))
|
||||||
|
:journal-entry/date (:invoice/date entity)
|
||||||
|
:journal-entry/original-entity raw-invoice-id
|
||||||
|
:journal-entry/vendor (:db/id (:invoice/vendor entity))
|
||||||
|
:journal-entry/amount (Math/abs (:invoice/total entity))
|
||||||
|
|
||||||
|
:journal-entry/line-items (into [(cond-> {:db/id (str (:db/id entity) "-" 0)
|
||||||
|
:journal-entry-line/account :account/accounts-payable
|
||||||
|
:journal-entry-line/location "A"
|
||||||
|
}
|
||||||
|
credit-invoice? (assoc :journal-entry-line/debit (Math/abs (:invoice/total entity)))
|
||||||
|
(not credit-invoice?) (assoc :journal-entry-line/credit (Math/abs (:invoice/total entity))))]
|
||||||
|
(map-indexed (fn [i ea]
|
||||||
|
(cond->
|
||||||
|
{:db/id (str (:db/id entity) "-" (inc i))
|
||||||
|
:journal-entry-line/account (:db/id (:invoice-expense-account/account ea))
|
||||||
|
:journal-entry-line/location (or (:invoice-expense-account/location ea) "HQ")
|
||||||
|
}
|
||||||
|
credit-invoice? (assoc :journal-entry-line/credit (Math/abs (:invoice-expense-account/amount ea)))
|
||||||
|
(not credit-invoice?) (assoc :journal-entry-line/debit (Math/abs (:invoice-expense-account/amount ea)))))
|
||||||
|
(:invoice/expense-accounts entity)))
|
||||||
|
:journal-entry/cleared (and (< (:invoice/outstanding-balance entity) 0.01)
|
||||||
|
(every? #(= :payment-status/cleared (:payment/status %)) (:invoice/payments entity))
|
||||||
|
)})))))
|
||||||
|
|
||||||
|
(defn upsert-invoice [db invoice]
|
||||||
|
(let [
|
||||||
|
upserted-entity (upsert-entity db invoice)
|
||||||
|
with-invoice (try (dc/with db {:tx-data upserted-entity})
|
||||||
|
(catch ClassCastException e
|
||||||
|
(println "Dev local does not support with in tx functions. :(")
|
||||||
|
(dc/with (dc/with-db @(resolve 'auto-ap.datomic/conn)) {:tx-data upserted-entity})
|
||||||
|
))
|
||||||
|
invoice-id (or (-> with-invoice :tempids (get (:db/id invoice)))
|
||||||
|
(:db/id invoice))
|
||||||
|
journal-entry (invoice->journal-entry (:db-after with-invoice)
|
||||||
|
invoice-id
|
||||||
|
(:db/id invoice))]
|
||||||
|
(into upserted-entity
|
||||||
|
(if journal-entry
|
||||||
|
(upsert-ledger db journal-entry)
|
||||||
|
[[:db/retractEntity [:journal-entry/original-entity (:db/id invoice)]]]))))
|
||||||
|
|
||||||
(defn propose-invoice [db invoice]
|
(defn propose-invoice [db invoice]
|
||||||
(let [existing? (boolean (seq (dc/q '[:find ?i
|
(let [existing? (boolean (seq (dc/q '[:find ?i
|
||||||
:in $ ?invoice-number ?client ?vendor
|
:in $ ?invoice-number ?client ?vendor
|
||||||
@@ -228,10 +304,10 @@
|
|||||||
[?i :invoice/client ?client]
|
[?i :invoice/client ?client]
|
||||||
[?i :invoice/vendor ?vendor]
|
[?i :invoice/vendor ?vendor]
|
||||||
(not [?i :invoice/status :invoice-status/voided])]
|
(not [?i :invoice/status :invoice-status/voided])]
|
||||||
db
|
db
|
||||||
(:invoice/invoice-number invoice)
|
(:invoice/invoice-number invoice)
|
||||||
(:invoice/client invoice)
|
(:invoice/client invoice)
|
||||||
(:invoice/vendor invoice))))]
|
(:invoice/vendor invoice))))]
|
||||||
(if existing?
|
(if existing?
|
||||||
[]
|
[]
|
||||||
[(remove-nils invoice)])))
|
(upsert-invoice db invoice))))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
iol-ion.tx/upsert-ledger
|
iol-ion.tx/upsert-ledger
|
||||||
iol-ion.tx/min-by
|
iol-ion.tx/min-by
|
||||||
iol-ion.tx/propose-invoice
|
iol-ion.tx/propose-invoice
|
||||||
|
iol-ion.tx/upsert-invoice
|
||||||
|
|
||||||
iol-ion.query/dollars=
|
iol-ion.query/dollars=
|
||||||
iol-ion.query/dollars-0?]
|
iol-ion.query/dollars-0?]
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
(ns auto-ap.graphql.checks
|
(ns auto-ap.graphql.checks
|
||||||
(:require
|
(:require
|
||||||
[amazonica.aws.s3 :as s3]
|
[amazonica.aws.s3 :as s3]
|
||||||
[auto-ap.datomic :refer [conn remove-nils plus pull-many]]
|
[auto-ap.datomic :refer [conn remove-nils plus pull-many audit-transact]]
|
||||||
[auto-ap.datomic.accounts :as a]
|
[auto-ap.datomic.accounts :as a]
|
||||||
|
[iol-ion.tx :refer [upsert-invoice]]
|
||||||
[auto-ap.datomic.bank-accounts :as d-bank-accounts]
|
[auto-ap.datomic.bank-accounts :as d-bank-accounts]
|
||||||
[auto-ap.datomic.checks :as d-checks]
|
[auto-ap.datomic.checks :as d-checks]
|
||||||
[auto-ap.datomic.clients :as d-clients]
|
[auto-ap.datomic.clients :as d-clients]
|
||||||
@@ -18,7 +19,6 @@
|
|||||||
assert-failure
|
assert-failure
|
||||||
assert-not-locked
|
assert-not-locked
|
||||||
enum->keyword]]
|
enum->keyword]]
|
||||||
[auto-ap.ledger :refer [transact-with-ledger]]
|
|
||||||
[auto-ap.numeric :refer [num->words]]
|
[auto-ap.numeric :refer [num->words]]
|
||||||
[auto-ap.time :refer [iso-date local-now parse]]
|
[auto-ap.time :refer [iso-date local-now parse]]
|
||||||
[auto-ap.utils :refer [by dollars-0?]]
|
[auto-ap.utils :refer [by dollars-0?]]
|
||||||
@@ -415,7 +415,7 @@
|
|||||||
(make-pdfs (filter #(and (= :payment-type/check (:payment/type %))
|
(make-pdfs (filter #(and (= :payment-type/check (:payment/type %))
|
||||||
(> (:payment/amount %) 0.0))
|
(> (:payment/amount %) 0.0))
|
||||||
checks))))
|
checks))))
|
||||||
(transact-with-ledger (map #(if (map? %)
|
(audit-transact (map #(if (map? %)
|
||||||
(dissoc % :payment/pdf-data)
|
(dissoc % :payment/pdf-data)
|
||||||
%) checks ) id)
|
%) checks ) id)
|
||||||
|
|
||||||
@@ -476,7 +476,7 @@
|
|||||||
0
|
0
|
||||||
invoice-payment-lookup)]
|
invoice-payment-lookup)]
|
||||||
|
|
||||||
(transact-with-ledger
|
(audit-transact
|
||||||
(into [(assoc base-payment
|
(into [(assoc base-payment
|
||||||
:payment/type :payment-type/check
|
:payment/type :payment-type/check
|
||||||
:payment/status :payment-status/pending
|
:payment/status :payment-status/pending
|
||||||
@@ -501,60 +501,60 @@
|
|||||||
new-balance (+ (:invoice/outstanding-balance invoice)
|
new-balance (+ (:invoice/outstanding-balance invoice)
|
||||||
(:invoice-payment/amount x))]
|
(:invoice-payment/amount x))]
|
||||||
[[:db.fn/retractEntity (:db/id x)]
|
[[:db.fn/retractEntity (:db/id x)]
|
||||||
{:db/id (:db/id invoice)
|
`(upsert-invoice ~{:db/id (:db/id invoice)
|
||||||
:invoice/outstanding-balance new-balance
|
:invoice/outstanding-balance new-balance
|
||||||
:invoice/status (if (dollars-0? new-balance)
|
:invoice/status (if (dollars-0? new-balance)
|
||||||
(:invoice/status invoice)
|
(:invoice/status invoice)
|
||||||
:invoice-status/unpaid)}]))
|
:invoice-status/unpaid)})]))
|
||||||
(:payment/invoices check))
|
(:payment/invoices check))
|
||||||
updated-payment {:db/id id
|
updated-payment {:db/id id
|
||||||
:payment/amount 0.0
|
:payment/amount 0.0
|
||||||
:payment/status :payment-status/voided}]
|
:payment/status :payment-status/voided}]
|
||||||
(transact-with-ledger (conj removing-payments updated-payment)
|
(audit-transact (conj removing-payments updated-payment)
|
||||||
(:id context)))
|
(:id context)))
|
||||||
(-> (d-checks/get-by-id id)
|
(-> (d-checks/get-by-id id)
|
||||||
(->graphql))))
|
(->graphql))))
|
||||||
|
|
||||||
(defn void-payments-internal [all-ids id]
|
(defn void-payments-internal [all-ids id]
|
||||||
(transact-with-ledger (->> all-ids
|
(audit-transact (->> all-ids
|
||||||
(dc/q '[:find (pull ?p [:db/id
|
(dc/q '[:find (pull ?p [:db/id
|
||||||
{:invoice-payment/_payment [:invoice-payment/amount
|
{:invoice-payment/_payment [:invoice-payment/amount
|
||||||
:db/id
|
:db/id
|
||||||
{:invoice-payment/invoice [:db/id :invoice/outstanding-balance]}]}])
|
{:invoice-payment/invoice [:db/id :invoice/outstanding-balance]}]}])
|
||||||
:in $ [?p ...]
|
:in $ [?p ...]
|
||||||
:where
|
:where
|
||||||
(not [_ :transaction/payment ?p])
|
(not [_ :transaction/payment ?p])
|
||||||
(not [?p :payment/status :payment-status/voided])
|
(not [?p :payment/status :payment-status/voided])
|
||||||
[?p :payment/client ?c]
|
[?p :payment/client ?c]
|
||||||
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
|
[(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
|
||||||
[?p :payment/date ?d]
|
[?p :payment/date ?d]
|
||||||
[(>= ?d ?lu)]
|
[(>= ?d ?lu)]
|
||||||
]
|
]
|
||||||
(dc/db conn))
|
(dc/db conn))
|
||||||
(map first)
|
(map first)
|
||||||
(mapcat (fn [{:keys [:db/id]
|
(mapcat (fn [{:keys [:db/id]
|
||||||
invoices :invoice-payment/_payment}]
|
invoices :invoice-payment/_payment}]
|
||||||
(into
|
(into
|
||||||
[{:db/id id
|
[{:db/id id
|
||||||
:payment/amount 0.0
|
:payment/amount 0.0
|
||||||
:payment/status :payment-status/voided}]
|
:payment/status :payment-status/voided}]
|
||||||
(->> invoices
|
(->> invoices
|
||||||
(mapcat (fn [{:keys [:invoice-payment/invoice :db/id :invoice-payment/amount]}]
|
(mapcat (fn [{:keys [:invoice-payment/invoice :db/id :invoice-payment/amount]}]
|
||||||
(let [new-balance (+ (:invoice/outstanding-balance invoice)
|
(let [new-balance (+ (:invoice/outstanding-balance invoice)
|
||||||
amount)]
|
amount)]
|
||||||
[[:db.fn/retractEntity id]
|
[[:db.fn/retractEntity id]
|
||||||
{:db/id (:db/id invoice)
|
`(upsert-invoice ~{:db/id (:db/id invoice)
|
||||||
:invoice/outstanding-balance new-balance
|
:invoice/outstanding-balance new-balance
|
||||||
:invoice/status (if (dollars-0? new-balance)
|
:invoice/status (if (dollars-0? new-balance)
|
||||||
(:invoice/status invoice)
|
(:invoice/status invoice)
|
||||||
:invoice-status/unpaid)}]))))))))
|
:invoice-status/unpaid)})]))))))))
|
||||||
id))
|
id))
|
||||||
|
|
||||||
|
|
||||||
(defn void-payments [context args _]
|
(defn void-payments [context args _]
|
||||||
(assert-admin (:id context))
|
(assert-admin (:id context))
|
||||||
(let [args (assoc args :id (:id context))
|
(let [args (assoc args :id (:id context))
|
||||||
ids (some-> args
|
ids (some-> args
|
||||||
:filters
|
:filters
|
||||||
(assoc :id (:id context))
|
(assoc :id (:id context))
|
||||||
(<-graphql)
|
(<-graphql)
|
||||||
@@ -649,10 +649,7 @@
|
|||||||
:payment/type :payment-type/balance-credit
|
:payment/type :payment-type/balance-credit
|
||||||
:payment/status :payment-status/cleared}]
|
:payment/status :payment-status/cleared}]
|
||||||
|
|
||||||
|
(audit-transact (-> []
|
||||||
|
|
||||||
|
|
||||||
(transact-with-ledger (-> []
|
|
||||||
(conj payment)
|
(conj payment)
|
||||||
(into (invoice-payments invoices invoice-amounts))) (:id context))
|
(into (invoice-payments invoices invoice-amounts))) (:id context))
|
||||||
(->graphql {:invoices (d-invoices/get-multi (map :db/id invoices))})))
|
(->graphql {:invoices (d-invoices/get-multi (map :db/id invoices))})))
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
(ns auto-ap.graphql.invoices
|
(ns auto-ap.graphql.invoices
|
||||||
(:require
|
(:require
|
||||||
[auto-ap.datomic
|
[auto-ap.datomic
|
||||||
:refer [conn pull-attr pull-many pull-ref random-tempid ]]
|
:refer [conn pull-attr pull-many pull-ref random-tempid audit-transact audit-transact-batch]]
|
||||||
[iol-ion.tx :refer [upsert-entity]]
|
[iol-ion.tx :refer [upsert-entity upsert-invoice]]
|
||||||
[auto-ap.datomic.clients :as d-clients]
|
[auto-ap.datomic.clients :as d-clients]
|
||||||
[auto-ap.datomic.invoices :as d-invoices]
|
[auto-ap.datomic.invoices :as d-invoices]
|
||||||
[auto-ap.datomic.vendors :as d-vendors]
|
[auto-ap.datomic.vendors :as d-vendors]
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
attach-tracing-resolvers
|
attach-tracing-resolvers
|
||||||
enum->keyword]]
|
enum->keyword]]
|
||||||
[auto-ap.ledger
|
[auto-ap.ledger
|
||||||
:refer [transact-batch-with-ledger transact-with-ledger]]
|
:refer [transact-batch-with-ledger]]
|
||||||
[auto-ap.rule-matching :as rm]
|
[auto-ap.rule-matching :as rm]
|
||||||
[auto-ap.utils :refer [dollars=]]
|
[auto-ap.utils :refer [dollars=]]
|
||||||
[clj-time.coerce :as coerce]
|
[clj-time.coerce :as coerce]
|
||||||
@@ -62,8 +62,10 @@
|
|||||||
(assert-power-user (:id context))
|
(assert-power-user (:id context))
|
||||||
(doseq [i invoices]
|
(doseq [i invoices]
|
||||||
(assert-can-see-client (:id context) (:db/id (:invoice/client (dc/pull (dc/db conn) [{:invoice/client [:db/id]}] i)))))
|
(assert-can-see-client (:id context) (:db/id (:invoice/client (dc/pull (dc/db conn) [{:invoice/client [:db/id]}] i)))))
|
||||||
(let [transactions (map (fn [i] [:db/retractEntity i]) invoices)]
|
(let [transactions (mapcat (fn [i] [[:db/retractEntity i]
|
||||||
(transact-with-ledger transactions (:id context))
|
[:db/retractEntity [:journal-entry/original-invoice i]]])
|
||||||
|
invoices)]
|
||||||
|
(audit-transact transactions (:id context))
|
||||||
invoices))
|
invoices))
|
||||||
|
|
||||||
(defn approve-invoices [context {:keys [invoices]} _]
|
(defn approve-invoices [context {:keys [invoices]} _]
|
||||||
@@ -74,8 +76,8 @@
|
|||||||
i)]]
|
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] {: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)]
|
||||||
(transact-with-ledger 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]}]
|
||||||
@@ -109,19 +111,19 @@
|
|||||||
_ (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-entity ~{: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
|
||||||
:invoice/vendor vendor_id
|
:invoice/vendor vendor_id
|
||||||
:invoice/import-status :import-status/imported
|
:invoice/import-status :import-status/imported
|
||||||
:invoice/total total
|
:invoice/total total
|
||||||
:invoice/outstanding-balance total
|
:invoice/outstanding-balance total
|
||||||
:invoice/status :invoice-status/unpaid
|
:invoice/status :invoice-status/unpaid
|
||||||
:invoice/date (coerce/to-date date)
|
:invoice/date (coerce/to-date date)
|
||||||
: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)})))
|
||||||
|
|
||||||
(defn assert-valid-expense-accounts [expense_accounts vendor_id]
|
(defn assert-valid-expense-accounts [expense_accounts vendor_id]
|
||||||
(doseq [expense-account expense_accounts
|
(doseq [expense-account expense_accounts
|
||||||
@@ -176,7 +178,7 @@
|
|||||||
(assert-valid-expense-accounts expense_accounts vendor_id)
|
(assert-valid-expense-accounts expense_accounts vendor_id)
|
||||||
(assert-invoice-amounts-add-up in)
|
(assert-invoice-amounts-add-up in)
|
||||||
|
|
||||||
(let [transaction-result (transact-with-ledger [(add-invoice-transaction in)] (:id context))]
|
(let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))]
|
||||||
(-> (d-invoices/get-by-id (get-in transaction-result [:tempids "invoice"]))
|
(-> (d-invoices/get-by-id (get-in transaction-result [:tempids "invoice"]))
|
||||||
(->graphql (:id context)))))
|
(->graphql (:id context)))))
|
||||||
|
|
||||||
@@ -195,7 +197,7 @@
|
|||||||
(assert-not-locked client_id (:date in))
|
(assert-not-locked client_id (:date in))
|
||||||
(assert-valid-expense-accounts (:expense_accounts in) vendor_id)
|
(assert-valid-expense-accounts (:expense_accounts in) vendor_id)
|
||||||
(assert-invoice-amounts-add-up in)))
|
(assert-invoice-amounts-add-up in)))
|
||||||
(let [transaction-result (transact-with-ledger [(add-invoice-transaction in)] (:id context))]
|
(let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))]
|
||||||
(mu/trace ::printing-checks
|
(mu/trace ::printing-checks
|
||||||
[]
|
[]
|
||||||
(-> (gq-checks/print-checks-internal [{:invoice-id (get-in transaction-result [:tempids "invoice"])
|
(-> (gq-checks/print-checks-internal [{:invoice-id (get-in transaction-result [:tempids "invoice"])
|
||||||
@@ -230,7 +232,7 @@
|
|||||||
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)}]
|
||||||
(transact-with-ledger [`(upsert-entity ~updated-invoice)]
|
(audit-transact [`(upsert-invoice ~updated-invoice)]
|
||||||
(:id context))
|
(:id context))
|
||||||
(-> (d-invoices/get-by-id id)
|
(-> (d-invoices/get-by-id id)
|
||||||
(->graphql (:id context)))))
|
(->graphql (:id context)))))
|
||||||
@@ -239,13 +241,13 @@
|
|||||||
(let [invoice (d-invoices/get-by-id id)]
|
(let [invoice (d-invoices/get-by-id id)]
|
||||||
(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))
|
||||||
(transact-with-ledger [{: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)))))
|
||||||
@@ -296,7 +298,7 @@
|
|||||||
(gq-checks/void-payments-internal voidable-cash-payments (:id context))
|
(gq-checks/void-payments-internal voidable-cash-payments (:id context))
|
||||||
|
|
||||||
(log/info "Voiding " (count all-ids) args)
|
(log/info "Voiding " (count all-ids) args)
|
||||||
(transact-with-ledger
|
(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 ...]
|
||||||
@@ -306,20 +308,17 @@
|
|||||||
[?i :invoice/date ?d]
|
[?i :invoice/date ?d]
|
||||||
[(>= ?d ?lu)]]
|
[(>= ?d ?lu)]]
|
||||||
(dc/db conn))
|
(dc/db conn))
|
||||||
(map first)
|
(map
|
||||||
(mapcat
|
(fn [[i]]
|
||||||
(fn [i]
|
`(upsert-invoice ~{:db/id (:db/id i)
|
||||||
(into
|
:invoice/total 0.0
|
||||||
[{:db/id (:db/id i)
|
:invoice/outstanding-balance 0.0
|
||||||
:invoice/total 0.0
|
:invoice/status :invoice-status/voided
|
||||||
:invoice/outstanding-balance 0.0
|
:invoice/expense-accounts (map
|
||||||
:invoice/status :invoice-status/voided
|
(fn [iea]
|
||||||
}]
|
{:db/id (:db/id iea)
|
||||||
(map
|
:invoice-expense-account/amount 0.0})
|
||||||
(fn [iea]
|
(:invoice/expense-accounts i))}))))
|
||||||
{:db/id (:db/id iea)
|
|
||||||
:invoice-expense-account/amount 0.0})
|
|
||||||
(:invoice/expense-accounts i))))))
|
|
||||||
(:id context))
|
(:id context))
|
||||||
{:message (str "Succesfully voided " (count all-ids))}))
|
{:message (str "Succesfully voided " (count all-ids))}))
|
||||||
|
|
||||||
@@ -337,17 +336,17 @@
|
|||||||
:in ['$ '?e]}
|
:in ['$ '?e]}
|
||||||
:args [history id]})
|
:args [history id]})
|
||||||
[last-transaction] (->> txs (sort-by first) (last))]
|
[last-transaction] (->> txs (sort-by first) (last))]
|
||||||
(mu/log ::here
|
(audit-transact [`(upsert-invoice
|
||||||
:txes txs)
|
~(->> txs
|
||||||
(transact-with-ledger [(->> txs
|
(filter (fn [[tx]] (= tx last-transaction)))
|
||||||
(filter (fn [[tx]] (= tx last-transaction)))
|
(reduce (fn [new-transaction [_ entity original-status original-outstanding total expense-account expense-account-amount]]
|
||||||
(reduce (fn [new-transaction [_ entity original-status original-outstanding total expense-account expense-account-amount]]
|
(-> new-transaction
|
||||||
(-> new-transaction
|
(assoc :db/id entity
|
||||||
(assoc :db/id entity
|
:invoice/total total
|
||||||
:invoice/total total
|
:invoice/status original-status
|
||||||
:invoice/status original-status
|
:invoice/outstanding-balance original-outstanding)
|
||||||
:invoice/outstanding-balance original-outstanding)
|
(update :invoice/expense-accounts conj {:db/id expense-account :invoice-expense-account/amount expense-account-amount})))
|
||||||
(update :invoice/expense-accounts conj {:db/id expense-account :invoice-expense-account/amount expense-account-amount}))) {}))]
|
{})))]
|
||||||
(:id context))
|
(:id context))
|
||||||
|
|
||||||
(-> (d-invoices/get-by-id id)
|
(-> (d-invoices/get-by-id id)
|
||||||
@@ -363,9 +362,11 @@
|
|||||||
(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))
|
||||||
(assert (not (seq (:invoice-payment/_invoice invoice))))
|
(assert (not (seq (:invoice-payment/_invoice invoice))))
|
||||||
(transact-with-ledger [[:db/add id :invoice/status :invoice-status/unpaid]
|
(audit-transact [`(upsert-invoice
|
||||||
[:db/add id :invoice/outstanding-balance (:invoice/total invoice)]
|
~{:db/id id
|
||||||
[:db/retract id :invoice/scheduled-payment (:invoice/scheduled-payment invoice)]]
|
:invoice/status :invoice-status/unpaid
|
||||||
|
:invoice/outstanding-balance (:invoice/total invoice)
|
||||||
|
:invoice/scheduled-payment nil})]
|
||||||
(:id context))
|
(:id context))
|
||||||
|
|
||||||
(-> (d-invoices/get-by-id id)
|
(-> (d-invoices/get-by-id id)
|
||||||
@@ -378,11 +379,11 @@
|
|||||||
_ (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 )))]
|
||||||
|
|
||||||
(transact-with-ledger [`(upsert-entity ~{: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))))
|
||||||
@@ -468,9 +469,9 @@
|
|||||||
(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}) ))))
|
||||||
(log/info "Bulk coding " (count all-ids) args)
|
(log/info "Bulk coding " (count all-ids) args)
|
||||||
(transact-batch-with-ledger
|
(audit-transact-batch
|
||||||
(map (fn [i]
|
(map (fn [i]
|
||||||
`(upsert-entity ~{: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))
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
(:gen-class)
|
(:gen-class)
|
||||||
(:require
|
(:require
|
||||||
[amazonica.aws.s3 :as s3]
|
[amazonica.aws.s3 :as s3]
|
||||||
[auto-ap.datomic :refer [conn pull-attr]]
|
[auto-ap.datomic :refer [audit-transact conn pull-attr]]
|
||||||
[auto-ap.jobs.core :refer [execute]]
|
[auto-ap.jobs.core :refer [execute]]
|
||||||
[auto-ap.ledger :refer [transact-with-ledger]]
|
|
||||||
[auto-ap.time :as atime]
|
[auto-ap.time :as atime]
|
||||||
[auto-ap.utils :refer [dollars=]]
|
[auto-ap.utils :refer [dollars=]]
|
||||||
[clj-time.coerce :as coerce]
|
[clj-time.coerce :as coerce]
|
||||||
@@ -14,7 +13,8 @@
|
|||||||
[clojure.tools.logging :as log]
|
[clojure.tools.logging :as log]
|
||||||
[config.core :refer [env]]
|
[config.core :refer [env]]
|
||||||
[datomic.client.api :as dc])
|
[datomic.client.api :as dc])
|
||||||
(:import [java.util UUID]))
|
(:import
|
||||||
|
(java.util UUID)))
|
||||||
|
|
||||||
(def bucket (:data-bucket env))
|
(def bucket (:data-bucket env))
|
||||||
|
|
||||||
@@ -141,8 +141,8 @@
|
|||||||
(log/info "contains " (count data) " rows")
|
(log/info "contains " (count data) " rows")
|
||||||
(doseq [n (partition-all 50 (register-invoice-import* data))]
|
(doseq [n (partition-all 50 (register-invoice-import* data))]
|
||||||
(log/info "transacting" n)
|
(log/info "transacting" n)
|
||||||
(transact-with-ledger n {:user/name "register-invoice-import"
|
(audit-transact n {:user/name "register-invoice-import"
|
||||||
:user/role "admin"}))))
|
:user/role "admin"}))))
|
||||||
|
|
||||||
(defn -main [& _]
|
(defn -main [& _]
|
||||||
(execute "register-invoice-import" #(register-invoice-import (:args env))))
|
(execute "register-invoice-import" #(register-invoice-import (:args env))))
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
(ns auto-ap.routes.invoices
|
(ns auto-ap.routes.invoices
|
||||||
(:require
|
(:require
|
||||||
[amazonica.aws.s3 :as s3]
|
[amazonica.aws.s3 :as s3]
|
||||||
[auto-ap.datomic :refer [conn remove-nils uri]]
|
[auto-ap.datomic :refer [audit-transact conn remove-nils]]
|
||||||
[iol-ion.tx :refer [propose-invoice]]
|
|
||||||
[auto-ap.datomic.accounts :as a]
|
[auto-ap.datomic.accounts :as a]
|
||||||
[auto-ap.datomic.clients :as d-clients]
|
[auto-ap.datomic.clients :as d-clients]
|
||||||
[auto-ap.datomic.invoices :as d-invoices]
|
[auto-ap.datomic.invoices :as d-invoices]
|
||||||
@@ -10,7 +9,6 @@
|
|||||||
[auto-ap.graphql.utils :refer [assert-admin assert-can-see-client]]
|
[auto-ap.graphql.utils :refer [assert-admin assert-can-see-client]]
|
||||||
[auto-ap.import.manual :as manual]
|
[auto-ap.import.manual :as manual]
|
||||||
[auto-ap.import.manual.common :as c]
|
[auto-ap.import.manual.common :as c]
|
||||||
[auto-ap.ledger :refer [transact-with-ledger]]
|
|
||||||
[auto-ap.parse :as parse]
|
[auto-ap.parse :as parse]
|
||||||
[auto-ap.routes.utils :refer [wrap-secure]]
|
[auto-ap.routes.utils :refer [wrap-secure]]
|
||||||
[auto-ap.utils :refer [by]]
|
[auto-ap.utils :refer [by]]
|
||||||
@@ -22,6 +20,7 @@
|
|||||||
[config.core :refer [env]]
|
[config.core :refer [env]]
|
||||||
[datomic.client.api :as dc]
|
[datomic.client.api :as dc]
|
||||||
[digest]
|
[digest]
|
||||||
|
[iol-ion.tx :refer [propose-invoice]]
|
||||||
[ring.middleware.json :refer [wrap-json-response]]
|
[ring.middleware.json :refer [wrap-json-response]]
|
||||||
[unilog.context :as lc])
|
[unilog.context :as lc])
|
||||||
(:import
|
(:import
|
||||||
@@ -278,7 +277,7 @@
|
|||||||
(mapv (fn [i] `(propose-invoice ~i))))]
|
(mapv (fn [i] `(propose-invoice ~i))))]
|
||||||
|
|
||||||
(log/info "creating invoice" potential-invoices)
|
(log/info "creating invoice" potential-invoices)
|
||||||
(let [tx (transact-with-ledger potential-invoices user)]
|
(let [tx (audit-transact potential-invoices user)]
|
||||||
(when-not (seq (dc/q '[:find ?i
|
(when-not (seq (dc/q '[:find ?i
|
||||||
:in $ [?i ...]
|
:in $ [?i ...]
|
||||||
:where [?i :invoice/invoice-number]]
|
:where [?i :invoice/invoice-number]]
|
||||||
@@ -395,7 +394,7 @@
|
|||||||
conj
|
conj
|
||||||
[]
|
[]
|
||||||
rows)]
|
rows)]
|
||||||
(transact-with-ledger txes nil)))
|
(audit-transact txes nil)))
|
||||||
|
|
||||||
(defn batch-upload-transactions [{{:keys [data]} :edn-params user :identity}]
|
(defn batch-upload-transactions [{{:keys [data]} :edn-params user :identity}]
|
||||||
(assert-admin user)
|
(assert-admin user)
|
||||||
@@ -479,7 +478,7 @@
|
|||||||
(not= "Cash" (:check %))))
|
(not= "Cash" (:check %))))
|
||||||
(map :vendor-name)
|
(map :vendor-name)
|
||||||
set)
|
set)
|
||||||
_ (transact-with-ledger (invoice-rows->transaction (:new grouped-rows)
|
_ (audit-transact (invoice-rows->transaction (:new grouped-rows)
|
||||||
user)
|
user)
|
||||||
user)]
|
user)]
|
||||||
{:status 200
|
{:status 200
|
||||||
|
|||||||
@@ -146,13 +146,13 @@
|
|||||||
(is (= #:transaction{:vendor {:db/id test-vendor-id}
|
(is (= #:transaction{:vendor {:db/id test-vendor-id}
|
||||||
:approval-status {:db/ident :transaction-approval-status/approved}
|
:approval-status {:db/ident :transaction-approval-status/approved}
|
||||||
:payment {:db/id payment-id}
|
:payment {:db/id payment-id}
|
||||||
:accounts [#:transaction-account{:account {:db/id accounts-payable-id}
|
:accounts [#:transaction-account{:account {:account/name "Accounts Payable"}
|
||||||
:location "A"
|
:location "A"
|
||||||
:amount 50.0}]}
|
:amount 50.0}]}
|
||||||
(dc/pull (dc/db conn) '[:transaction/vendor
|
(dc/pull (dc/db conn) '[:transaction/vendor
|
||||||
:transaction/payment
|
:transaction/payment
|
||||||
{:transaction/approval-status [:db/ident]
|
{:transaction/approval-status [:db/ident]
|
||||||
:transaction/accounts [:transaction-account/account
|
:transaction/accounts [{:transaction-account/account [:account/name]}
|
||||||
:transaction-account/location
|
:transaction-account/location
|
||||||
:transaction-account/amount]}]
|
:transaction-account/amount]}]
|
||||||
transaction-id)))))
|
transaction-id)))))
|
||||||
|
|||||||
@@ -108,5 +108,10 @@
|
|||||||
:client/bank-accounts [(test-bank-account :db/id "test-bank-account-id")])
|
:client/bank-accounts [(test-bank-account :db/id "test-bank-account-id")])
|
||||||
(test-vendor :db/id "test-vendor-id")
|
(test-vendor :db/id "test-vendor-id")
|
||||||
{:db/id "accounts-payable-id"
|
{:db/id "accounts-payable-id"
|
||||||
|
:account/name "Accounts Payable"
|
||||||
|
:db/ident :account/accounts-payable
|
||||||
:account/numeric-code 21000
|
:account/numeric-code 21000
|
||||||
:account/account-set "default"}])})))
|
:account/account-set "default"}])})))
|
||||||
|
|
||||||
|
(defn apply-tx [data]
|
||||||
|
(:db-after (dc/transact conn {:tx-data data})))
|
||||||
|
|||||||
84
test/clj/iol_ion/integration/tx.clj
Normal file
84
test/clj/iol_ion/integration/tx.clj
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
(ns iol-ion.integration.tx
|
||||||
|
(:require
|
||||||
|
[auto-ap.datomic :refer [conn]]
|
||||||
|
[auto-ap.integration.util
|
||||||
|
:refer [setup-test-data test-invoice wrap-setup apply-tx]]
|
||||||
|
[clojure.test :as t :refer [deftest is use-fixtures testing]]
|
||||||
|
[datomic.client.api :as dc]
|
||||||
|
[iol-ion.tx :as sut]))
|
||||||
|
|
||||||
|
(use-fixtures :each wrap-setup)
|
||||||
|
|
||||||
|
(def journal-pull [:journal-entry/date
|
||||||
|
:journal-entry/original-entity
|
||||||
|
:journal-entry/client
|
||||||
|
:journal-entry/source
|
||||||
|
:journal-entry/cleared
|
||||||
|
:journal-entry/amount
|
||||||
|
:journal-entry/vendor
|
||||||
|
{:journal-entry/line-items [{:journal-entry-line/account '[:account/name]}
|
||||||
|
:journal-entry-line/location
|
||||||
|
:journal-entry-line/credit
|
||||||
|
:journal-entry-line/dirty
|
||||||
|
:journal-entry-line/debit]}])
|
||||||
|
|
||||||
|
|
||||||
|
(deftest upsert-invoice
|
||||||
|
(testing "Importing should create a journal entry"
|
||||||
|
(let [{:strs [invoice-id
|
||||||
|
test-client-id
|
||||||
|
test-vendor-id
|
||||||
|
]} (setup-test-data
|
||||||
|
[(test-invoice :db/id "invoice-id"
|
||||||
|
:invoice/import-status :import-status/pending
|
||||||
|
:invoice/total 200.0
|
||||||
|
)])]
|
||||||
|
|
||||||
|
(is (nil? (:db/id (dc/pull (dc/db conn) journal-pull
|
||||||
|
[:journal-entry/original-entity invoice-id]))))
|
||||||
|
(let [db-after (apply-tx (sut/upsert-invoice
|
||||||
|
(dc/with-db conn)
|
||||||
|
{:db/id invoice-id
|
||||||
|
:invoice/import-status :import-status/imported}))]
|
||||||
|
|
||||||
|
(is (= #:journal-entry{:date #inst "2022-01-01T00:00:00.000-00:00",
|
||||||
|
:original-entity #:db{:id invoice-id},
|
||||||
|
:client #:db{:id test-client-id},
|
||||||
|
:line-items
|
||||||
|
[#:journal-entry-line{:account
|
||||||
|
#:account{:name
|
||||||
|
"Accounts Payable"},
|
||||||
|
:credit 200.0,
|
||||||
|
:location "A",
|
||||||
|
:dirty true}
|
||||||
|
#:journal-entry-line{:account
|
||||||
|
#:account{:name "Account"},
|
||||||
|
:location "DT",
|
||||||
|
:dirty true,
|
||||||
|
:debit 100.0}],
|
||||||
|
:source "invoice",
|
||||||
|
:cleared false,
|
||||||
|
:amount 200.0,
|
||||||
|
:vendor #:db{:id test-vendor-id}}
|
||||||
|
(dc/pull db-after journal-pull
|
||||||
|
[:journal-entry/original-entity invoice-id])))
|
||||||
|
|
||||||
|
(testing "voiding an invoice should remove the journal entry"
|
||||||
|
(let [db-after (apply-tx (sut/upsert-invoice
|
||||||
|
(dc/with-db conn)
|
||||||
|
{:db/id invoice-id
|
||||||
|
:invoice/status :invoice-status/voided}))]
|
||||||
|
|
||||||
|
(is (= nil
|
||||||
|
(dc/pull db-after journal-pull
|
||||||
|
[:journal-entry/original-entity invoice-id])))))
|
||||||
|
(testing "invoice should remove the journal entry"
|
||||||
|
(let [db-after (apply-tx (sut/upsert-invoice
|
||||||
|
(dc/db conn)
|
||||||
|
{:db/id invoice-id
|
||||||
|
:invoice/status :invoice-status/unpaid
|
||||||
|
:invoice/import-status :import-status/pending}))]
|
||||||
|
|
||||||
|
(is (= nil
|
||||||
|
(dc/pull db-after journal-pull
|
||||||
|
[:journal-entry/original-entity invoice-id])))))))))
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
|
* try again to see if we can get upsert-ledger into the same transaction, making it all or nothing
|
||||||
|
* look for all usages of transact-with-ledger
|
||||||
|
* propose-invoice should use upsert invoice as well
|
||||||
|
|
||||||
it looks like there are a bbunch of orrphaned customizations for accounts, breaking indexes
|
it looks like there are a bbunch of orrphaned customizations for accounts, breaking indexes
|
||||||
upsertledger - matching transaction rule might not assign an account. Other things might not assign accounts. This is an assertion that is commented out. Determine consequence of disabling
|
upsertledger - matching transaction rule might not assign an account. Other things might not assign accounts. This is an assertion that is commented out. Determine consequence of disabling
|
||||||
Double check each job still functions in the new system
|
Double check each job still functions in the new system
|
||||||
Make reports just be based on running-balances
|
Make reports just be based on running-balances
|
||||||
Test exports
|
Test exports
|
||||||
|
Move pay into iol-ion.tx, make sure to use upsert-invoice
|
||||||
|
|
||||||
Some jobs just aren't so big they need to be jobs anymore:
|
Some jobs just aren't so big they need to be jobs anymore:
|
||||||
Refreshing running balance for journal entry lines
|
Refreshing running balance for journal entry lines
|
||||||
@@ -12,7 +17,6 @@ Closing auto invoices
|
|||||||
|
|
||||||
Running Balance Cache
|
Running Balance Cache
|
||||||
* Add tests for upsert-ledger
|
* Add tests for upsert-ledger
|
||||||
* try again to see if we can get upsert-ledger into the same transaction, making it all or nothing
|
|
||||||
* Make a new way to reset the entire cache for a client
|
* Make a new way to reset the entire cache for a client
|
||||||
|
|
||||||
Address memory
|
Address memory
|
||||||
|
|||||||
Reference in New Issue
Block a user