(cloud) makes ledger running balances fast and smooth
This commit is contained in:
@@ -60,11 +60,13 @@
|
|||||||
|
|
||||||
;; TODO unit test this
|
;; TODO unit test this
|
||||||
(defn upsert-entity [db entity]
|
(defn upsert-entity [db entity]
|
||||||
(assert (:db/id entity) "Cannot upsert without :db/id")
|
(assert (or (:db/id entity)
|
||||||
(let [e (:db/id entity)
|
(:db/ident entity))
|
||||||
|
(str "Cannot upsert without :db/id or :db/ident, " entity))
|
||||||
|
(let [e (or (:db/id entity) (:db/ident entity))
|
||||||
is-new? (string? e)
|
is-new? (string? e)
|
||||||
extant-entity (when-not is-new?
|
extant-entity (when-not is-new?
|
||||||
(dc/pull db (keys entity) (:db/id entity)))
|
(dc/pull db (keys entity) (or (:db/id entity) (:db/ident entity))))
|
||||||
ident->value-type (by :db/ident (comp :db/ident
|
ident->value-type (by :db/ident (comp :db/ident
|
||||||
:db/valueType)
|
:db/valueType)
|
||||||
(pull-many
|
(pull-many
|
||||||
@@ -78,6 +80,9 @@
|
|||||||
(= :db/id a)
|
(= :db/id a)
|
||||||
ops
|
ops
|
||||||
|
|
||||||
|
(= :db/ident a)
|
||||||
|
ops
|
||||||
|
|
||||||
(or (= v (a extant-entity))
|
(or (= v (a extant-entity))
|
||||||
(= v (:db/ident (a extant-entity) :nope))
|
(= v (:db/ident (a extant-entity) :nope))
|
||||||
(= v (:db/id (a extant-entity)) :nope))
|
(= v (:db/id (a extant-entity)) :nope))
|
||||||
@@ -92,6 +97,10 @@
|
|||||||
ops
|
ops
|
||||||
|
|
||||||
;; reset relationships if it's refs, and not a lookup (i.e., seq of maps, or empty seq)
|
;; reset relationships if it's refs, and not a lookup (i.e., seq of maps, or empty seq)
|
||||||
|
|
||||||
|
(and (sequential? v) (= :db.type/tuple (ident->value-type a)))
|
||||||
|
(conj ops [:db/add e a v])
|
||||||
|
|
||||||
(and (sequential? v) (= :db.type/ref (ident->value-type a)) (every? map? v))
|
(and (sequential? v) (= :db.type/ref (ident->value-type a)) (every? map? v))
|
||||||
(into ops (reset-rels db e a v))
|
(into ops (reset-rels db e a v))
|
||||||
|
|
||||||
@@ -111,3 +120,101 @@
|
|||||||
[]))]
|
[]))]
|
||||||
ops))
|
ops))
|
||||||
|
|
||||||
|
(defn min-by [sorter]
|
||||||
|
(->> sorter
|
||||||
|
sort
|
||||||
|
last
|
||||||
|
last))
|
||||||
|
|
||||||
|
(defn get-line-items-after [db journal-entry]
|
||||||
|
(for [jel (:journal-entry/line-items journal-entry)
|
||||||
|
:let [next-jel (->> (dc/index-pull db {:index :avet
|
||||||
|
:selector [:db/id :journal-entry-line/client+account+location+date]
|
||||||
|
:start [:journal-entry-line/client+account+location+date
|
||||||
|
(:journal-entry-line/client+account+location+date jel)
|
||||||
|
(:db/id jel)]
|
||||||
|
:limit 3
|
||||||
|
})
|
||||||
|
(filter (fn line-must-match-client-account-location [result]
|
||||||
|
(and
|
||||||
|
(= (take 3 (:journal-entry-line/client+account+location+date result))
|
||||||
|
(take 3 (:journal-entry-line/client+account+location+date jel)))
|
||||||
|
(not= (:db/id jel)
|
||||||
|
(:db/id result)))
|
||||||
|
))
|
||||||
|
first
|
||||||
|
:db/id)]
|
||||||
|
:when next-jel]
|
||||||
|
next-jel))
|
||||||
|
|
||||||
|
(def extant-read '[:db/id :journal-entry/date :journal-entry/client {:journal-entry/line-items [:journal-entry-line/account :journal-entry-line/location :db/id :journal-entry-line/client+account+location+date]}])
|
||||||
|
|
||||||
|
|
||||||
|
(defn calc-client+account+location+date [je jel]
|
||||||
|
[(or
|
||||||
|
(:db/id (:journal-entry/client je))
|
||||||
|
(:journal-entry/client je))
|
||||||
|
(or (:db/id (:journal-entry-line/account jel))
|
||||||
|
(:journal-entry-line/account jel))
|
||||||
|
(-> jel :journal-entry-line/location)
|
||||||
|
(-> je :journal-entry/date)])
|
||||||
|
|
||||||
|
(defn upsert-ledger [db ledger-entry]
|
||||||
|
(assert (:journal-entry/date ledger-entry) "Must at least provide date when updating ledger")
|
||||||
|
(assert (:journal-entry/client ledger-entry) "Must at least provide client when updating ledger")
|
||||||
|
(assert (every? :journal-entry-line/account (:journal-entry/line-items ledger-entry)) "must at least provide account when updating ledger")
|
||||||
|
(assert (every? :journal-entry-line/location (:journal-entry/line-items ledger-entry)) "Must at least provide location when updating ledger")
|
||||||
|
(let [
|
||||||
|
extant-entry (or (when-let [original-entity (:journal-entry/original-entity ledger-entry)]
|
||||||
|
(dc/pull db extant-read [:journal-entry/original-entity original-entity]))
|
||||||
|
(when-let [external-id (:journal-entry/external-id ledger-entry)]
|
||||||
|
(dc/pull db extant-read [:journal-entry/external-id external-id])))
|
||||||
|
extant-entry-exists? (:db/id extant-entry)]
|
||||||
|
|
||||||
|
(cond->
|
||||||
|
(upsert-entity db (into (-> ledger-entry
|
||||||
|
(assoc :db/id (or
|
||||||
|
(:db/id ledger-entry)
|
||||||
|
(:db/id extant-entry)
|
||||||
|
(random-tempid)))
|
||||||
|
(update :journal-entry/line-items
|
||||||
|
(fn [lis]
|
||||||
|
(mapv #(-> %
|
||||||
|
(assoc :journal-entry-line/dirty true)
|
||||||
|
(assoc :journal-entry-line/client+account+location+date
|
||||||
|
(calc-client+account+location+date ledger-entry %)))
|
||||||
|
lis))))
|
||||||
|
))
|
||||||
|
extant-entry-exists? (into (map (fn [li]
|
||||||
|
{:journal-entry-line/dirty true
|
||||||
|
:db/id li})
|
||||||
|
(get-line-items-after db extant-entry))))))
|
||||||
|
|
||||||
|
(defn remove-nils [m]
|
||||||
|
(let [result (reduce-kv
|
||||||
|
(fn [m k v]
|
||||||
|
(if (not (nil? v))
|
||||||
|
(assoc m k v)
|
||||||
|
m
|
||||||
|
))
|
||||||
|
{}
|
||||||
|
m)]
|
||||||
|
(if (seq result)
|
||||||
|
result
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
(defn propose-invoice [db invoice]
|
||||||
|
(let [existing? (boolean (seq (dc/q '[:find ?i
|
||||||
|
:in $ ?invoice-number ?client ?vendor
|
||||||
|
:where
|
||||||
|
[?i :invoice/invoice-number ?invoice-number]
|
||||||
|
[?i :invoice/client ?client]
|
||||||
|
[?i :invoice/vendor ?vendor]
|
||||||
|
(not [?i :invoice/status :invoice-status/voided])]
|
||||||
|
db
|
||||||
|
(:invoice/invoice-number invoice)
|
||||||
|
(:invoice/client invoice)
|
||||||
|
(:invoice/vendor invoice))))]
|
||||||
|
(if existing?
|
||||||
|
[]
|
||||||
|
[(remove-nils invoice)])))
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
[lein-ancient "0.6.15"]]
|
[lein-ancient "0.6.15"]]
|
||||||
:clean-targets ^{:protect false} ["resources/public/js/compiled" "target"]
|
:clean-targets ^{:protect false} ["resources/public/js/compiled" "target"]
|
||||||
:ring {:handler auto-ap.handler/app}
|
:ring {:handler auto-ap.handler/app}
|
||||||
:source-paths ["src/clj" "src/cljc" "src/cljs" "iol_ion/src"]
|
:source-paths ["iol_ion/src" "src/clj" "src/cljc" "src/cljs" ]
|
||||||
:resource-paths ["resources"]
|
:resource-paths ["resources"]
|
||||||
:aliases {"build" ["do" "clean" ["uberjar"]]
|
:aliases {"build" ["do" "clean" ["uberjar"]]
|
||||||
"fig:dev" ["run" "-m" "figwheel.main" "-b" "dev" "-r"]
|
"fig:dev" ["run" "-m" "figwheel.main" "-b" "dev" "-r"]
|
||||||
|
|||||||
@@ -10,4 +10,11 @@
|
|||||||
:db/doc "Whether or not this journal entry line is dirty and needs to recalculate balances",
|
:db/doc "Whether or not this journal entry line is dirty and needs to recalculate balances",
|
||||||
:db/ident :journal-entry-line/dirty,
|
:db/ident :journal-entry-line/dirty,
|
||||||
}
|
}
|
||||||
|
{:db/valueType :db.type/tuple
|
||||||
|
:db/tupleTypes [:db.type/ref :db.type/ref :db.type/string :db.type/instant]
|
||||||
|
:db/cardinality :db.cardinality/one,
|
||||||
|
:db/ident :journal-entry-line/client+account+location+date
|
||||||
|
:db/doc "Used to find accounts and locations quickly",
|
||||||
|
:db/noHistory true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{ :allow [iol-ion.tx/upsert-entity
|
{ :allow [iol-ion.tx/upsert-entity
|
||||||
iol-ion.tx/reset-scalars
|
iol-ion.tx/reset-scalars
|
||||||
iol-ion.tx/reset-rels
|
iol-ion.tx/reset-rels
|
||||||
|
iol-ion.tx/upsert-ledger
|
||||||
|
iol-ion.tx/min-by
|
||||||
iol-ion.tx/propose-invoice]
|
iol-ion.tx/propose-invoice]
|
||||||
:app-name "iol-cloud"}
|
:app-name "iol-cloud"}
|
||||||
|
|||||||
60
scratch-sessions/test_solr.clj
Normal file
60
scratch-sessions/test_solr.clj
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
;; This buffer is for Clojure experiments and evaluation.
|
||||||
|
|
||||||
|
;; Press C-j to evaluate the last expression.
|
||||||
|
|
||||||
|
;; You can also press C-u C-j to evaluate the expression and pretty-print its result.
|
||||||
|
|
||||||
|
(require '[clj-http.client :as client])
|
||||||
|
(require ' [clojure.data.json :as json])
|
||||||
|
|
||||||
|
|
||||||
|
(defn upsert-schema []
|
||||||
|
(client/post
|
||||||
|
"http://localhost:8983/solr/gettingstarted/schema"
|
||||||
|
{:headers {"Content-Type" "application/json"}
|
||||||
|
:method "POST"
|
||||||
|
:body (json/write-str {"add-field" [{"name" "client-id"
|
||||||
|
"type" "string"}
|
||||||
|
{"name" "order-date"
|
||||||
|
"type" "pdate"}]})
|
||||||
|
}))
|
||||||
|
|
||||||
|
(defn load-sales-orders []
|
||||||
|
(clojure.pprint/pprint
|
||||||
|
(doseq [[client code] (dc/q '[:find ?c ?code :where [?c :client/code ?code]] (dc/db conn))
|
||||||
|
:let [_ (println "loading" code)]
|
||||||
|
|
||||||
|
batch (->> (dc/qseq '[:find ?so ?date ?client-id
|
||||||
|
:in $ ?client-id
|
||||||
|
:where
|
||||||
|
[?so :sales-order/client ?client-id]
|
||||||
|
[?so :sales-order/date ?date]
|
||||||
|
]
|
||||||
|
(dc/db conn)
|
||||||
|
client)
|
||||||
|
(map (fn [[so date client-id]]
|
||||||
|
{"id" so
|
||||||
|
"order-date" (str date)
|
||||||
|
"client-id" (str client-id)}))
|
||||||
|
(partition-all 1000)
|
||||||
|
)]
|
||||||
|
(print ".")
|
||||||
|
(flush)
|
||||||
|
(client/post
|
||||||
|
"http://localhost:8983/solr/gettingstarted/update?commitWithin=60000"
|
||||||
|
{:headers {"Content-Type" "application/json"}
|
||||||
|
:method "POST"
|
||||||
|
:body (json/write-str batch)}))))
|
||||||
|
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(try
|
||||||
|
(upsert-schema)
|
||||||
|
(catch Exception e
|
||||||
|
(println e)))
|
||||||
|
|
||||||
|
(load-sales-orders)
|
||||||
|
|
||||||
|
(dc/pull (dc/db conn) '[:client/code :db/id] [:client/code "NGOP"])
|
||||||
|
|
||||||
|
)
|
||||||
@@ -663,6 +663,12 @@
|
|||||||
(defn random-tempid []
|
(defn random-tempid []
|
||||||
(str (UUID/randomUUID)))
|
(str (UUID/randomUUID)))
|
||||||
|
|
||||||
|
(defn pull-id [db id]
|
||||||
|
(ffirst (dc/q '[:find ?i
|
||||||
|
:in $ ?i]
|
||||||
|
db
|
||||||
|
id)))
|
||||||
|
|
||||||
(defn pull-attr [db k id]
|
(defn pull-attr [db k id]
|
||||||
(get (dc/pull db [k] id) k))
|
(get (dc/pull db [k] id) k))
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
(ns auto-ap.graphql.ledger
|
(ns auto-ap.graphql.ledger
|
||||||
(:require
|
(:require
|
||||||
[auto-ap.datomic
|
[auto-ap.datomic
|
||||||
:refer [audit-transact-batch conn pull-attr pull-many remove-nils]]
|
:refer [audit-transact-batch conn pull-many remove-nils]]
|
||||||
[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.ledger :as l]
|
[auto-ap.datomic.ledger :as l]
|
||||||
[auto-ap.time :as atime]
|
|
||||||
[auto-ap.ledger.reports :as l-reports]
|
|
||||||
[auto-ap.graphql.utils
|
[auto-ap.graphql.utils
|
||||||
:refer [->graphql <-graphql assert-admin assert-can-see-client result->page attach-tracing-resolvers]]
|
:refer [->graphql
|
||||||
|
<-graphql
|
||||||
|
assert-admin
|
||||||
|
assert-can-see-client
|
||||||
|
attach-tracing-resolvers
|
||||||
|
result->page]]
|
||||||
|
[auto-ap.ledger :refer [build-account-lookup]]
|
||||||
|
[auto-ap.ledger.reports :as l-reports]
|
||||||
[auto-ap.parse.util :as parse]
|
[auto-ap.parse.util :as parse]
|
||||||
[auto-ap.pdf.ledger :refer [print-balance-sheet print-pnl print-journal-detail-report]]
|
[auto-ap.pdf.ledger
|
||||||
[auto-ap.utils :refer [by dollars= heartbeat]]
|
:refer [print-balance-sheet print-journal-detail-report print-pnl]]
|
||||||
|
[auto-ap.time :as atime]
|
||||||
|
[auto-ap.utils :refer [by dollars=]]
|
||||||
[clj-time.coerce :as coerce]
|
[clj-time.coerce :as coerce]
|
||||||
[clj-time.core :as t]
|
[clj-time.core :as t]
|
||||||
[clojure.tools.logging :as log]
|
|
||||||
[clojure.data.csv :as csv]
|
[clojure.data.csv :as csv]
|
||||||
[datomic.client.api :as dc]
|
[clojure.tools.logging :as log]
|
||||||
[mount.core :as mount]
|
|
||||||
[com.brunobonacci.mulog :as mu]
|
[com.brunobonacci.mulog :as mu]
|
||||||
[yang.scheduler :as scheduler])
|
[datomic.client.api :as dc]
|
||||||
(:import [org.apache.commons.codec.binary Base64]))
|
[iol-ion.tx :refer [upsert-ledger]])
|
||||||
|
(:import
|
||||||
|
(org.apache.commons.codec.binary Base64)))
|
||||||
|
|
||||||
(defn get-ledger-page [context args _]
|
(defn get-ledger-page [context args _]
|
||||||
(let [args (assoc args :id (:id context))
|
(let [args (assoc args :id (:id context))
|
||||||
@@ -88,73 +95,35 @@
|
|||||||
(filter (fn [[d]]
|
(filter (fn [[d]]
|
||||||
(if start-date
|
(if start-date
|
||||||
(and
|
(and
|
||||||
(>= (compare d start-date) 0)
|
(>= (compare d start-date) 0)
|
||||||
(<= (compare d end-date) 0))
|
(<= (compare d end-date) 0))
|
||||||
(<= (compare d end-date) 0))))
|
(<= (compare d end-date) 0))))
|
||||||
(reduce
|
(reduce
|
||||||
(fn [acc [_ _ account location debit credit]]
|
(fn [acc [_ _ account location debit credit]]
|
||||||
(-> acc
|
(-> acc
|
||||||
(update-in [[location account] :debit] (fnil + 0.0) debit)
|
(update-in [[location account] :debit] (fnil + 0.0) debit)
|
||||||
(update-in [[location account] :credit] (fnil + 0.0) credit)
|
(update-in [[location account] :credit] (fnil + 0.0) credit)
|
||||||
(update-in [[location account] :count] (fnil + 0) 1))
|
(update-in [[location account] :count] (fnil + 0) 1))
|
||||||
)
|
)
|
||||||
{})
|
{})
|
||||||
(reduce-kv
|
(reduce-kv
|
||||||
(fn [acc [location account-id] {:keys [debit credit count]}]
|
(fn [acc [location account-id] {:keys [debit credit count]}]
|
||||||
(let [account (lookup-account account-id)
|
(let [account (lookup-account account-id)
|
||||||
account-type (:account_type account)]
|
account-type (:account_type account)]
|
||||||
|
|
||||||
(conj acc (merge {:id (str account-id "-" location)
|
(conj acc (merge {:id (str account-id "-" location)
|
||||||
:location (or location "")
|
:location (or location "")
|
||||||
:count count
|
:count count
|
||||||
:debits debit
|
:debits debit
|
||||||
:credits credit
|
:credits credit
|
||||||
:amount (if account-type (if (#{:account-type/asset
|
:amount (if account-type (if (#{:account-type/asset
|
||||||
:account-type/dividend
|
:account-type/dividend
|
||||||
:account-type/expense} account-type)
|
:account-type/expense} account-type)
|
||||||
(- debit credit)
|
(- debit credit)
|
||||||
(- credit debit))
|
(- credit debit))
|
||||||
0.0)}
|
0.0)}
|
||||||
account)))
|
account))))
|
||||||
)
|
[]))))
|
||||||
|
|
||||||
[]))))
|
|
||||||
|
|
||||||
(defn build-account-lookup [client-id]
|
|
||||||
(let [accounts (by :db/id (map first (dc/q {:query {:find ['(pull ?e [:db/id :account/name
|
|
||||||
:account/numeric-code
|
|
||||||
{:account/type [:db/ident]
|
|
||||||
:account/client-overrides [:account-client-override/client :account-client-override/name]}
|
|
||||||
])]
|
|
||||||
:in ['$]
|
|
||||||
:where ['[?e :account/name]]}
|
|
||||||
:args [(dc/db conn )]})))
|
|
||||||
|
|
||||||
bank-accounts (by :db/id (map first (dc/q {:query {:find ['(pull ?e [:db/id :bank-account/name :bank-account/numeric-code {:bank-account/type [:db/ident]}])]
|
|
||||||
:in ['$]
|
|
||||||
:where ['[?e :bank-account/name]]}
|
|
||||||
:args [(dc/db conn)]})))
|
|
||||||
overrides-by-client (->> accounts
|
|
||||||
vals
|
|
||||||
(mapcat (fn [a]
|
|
||||||
(map (fn [o]
|
|
||||||
[[(:db/id a) (:db/id (:account-client-override/client o))]
|
|
||||||
(:account-client-override/name o)])
|
|
||||||
(:account/client-overrides a))
|
|
||||||
) )
|
|
||||||
(into {} ))]
|
|
||||||
(fn [a]
|
|
||||||
{:name (or (:bank-account/name (bank-accounts a))
|
|
||||||
(overrides-by-client [a client-id])
|
|
||||||
(:account/name (accounts a)))
|
|
||||||
:account_type (or (:db/ident (:account/type (accounts a)))
|
|
||||||
({:bank-account-type/check :account-type/asset
|
|
||||||
:bank-account-type/cash :account-type/asset
|
|
||||||
:bank-account-type/credit :account-type/liability}
|
|
||||||
(:db/ident (:bank-account/type (bank-accounts a)))))
|
|
||||||
:numeric_code (or (:account/numeric-code (accounts a))
|
|
||||||
(:bank-account/numeric-code (bank-accounts a)))
|
|
||||||
:client_id client-id})))
|
|
||||||
|
|
||||||
(defn full-ledger-for-client [client-id]
|
(defn full-ledger-for-client [client-id]
|
||||||
(->> (dc/q
|
(->> (dc/q
|
||||||
@@ -371,69 +340,70 @@
|
|||||||
(assoc entry
|
(assoc entry
|
||||||
:status :success
|
:status :success
|
||||||
:tx
|
:tx
|
||||||
(remove-nils
|
`(upsert-ledger
|
||||||
{:journal-entry/source (:source entry)
|
~(remove-nils
|
||||||
:journal-entry/client [:client/code (:client_code entry)]
|
{:journal-entry/source (:source entry)
|
||||||
:journal-entry/date (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry)))
|
:journal-entry/client [:client/code (:client_code entry)]
|
||||||
:journal-entry/external-id (:external_id entry)
|
:journal-entry/date (coerce/to-date (parse/parse-value :clj-time "MM/dd/yyyy" (:date entry)))
|
||||||
:journal-entry/vendor (:db/id (all-vendors (:vendor_name entry)))
|
:journal-entry/external-id (:external_id entry)
|
||||||
:journal-entry/amount (:amount entry)
|
:journal-entry/vendor (:db/id (all-vendors (:vendor_name entry)))
|
||||||
:journal-entry/note (:note entry)
|
:journal-entry/amount (:amount entry)
|
||||||
:journal-entry/cleared-against (:cleared_against 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]
|
||||||
(let [debit (or (:debit ea) 0.0)
|
(let [debit (or (:debit ea) 0.0)
|
||||||
credit (or (:credit ea) 0.0)]
|
credit (or (:credit ea) 0.0)]
|
||||||
(when (and (not (get
|
(when (and (not (get
|
||||||
(get all-client-locations (:client_code entry))
|
(get all-client-locations (:client_code entry))
|
||||||
(:location ea)))
|
(:location ea)))
|
||||||
(not= "A" (:location ea)))
|
(not= "A" (:location ea)))
|
||||||
(throw (ex-info (str "Location '" (:location ea) "' not found.")
|
(throw (ex-info (str "Location '" (:location ea) "' not found.")
|
||||||
{:status :error})))
|
{:status :error})))
|
||||||
(when (and (<= debit 0.0)
|
(when (and (<= debit 0.0)
|
||||||
(<= credit 0.0))
|
(<= credit 0.0))
|
||||||
(throw (ex-info (str "Line item amount " (or debit credit) " must be greater than 0.")
|
(throw (ex-info (str "Line item amount " (or debit credit) " must be greater than 0.")
|
||||||
{:status :error})))
|
{:status :error})))
|
||||||
(when (and (not (all-accounts (:account_identifier ea)))
|
(when (and (not (all-accounts (:account_identifier ea)))
|
||||||
(not (get
|
(not (get
|
||||||
(get all-client-bank-accounts (:client_code entry))
|
(get all-client-bank-accounts (:client_code entry))
|
||||||
(:account_identifier ea))))
|
(:account_identifier ea))))
|
||||||
(throw (ex-info (str "Account '" (:account_identifier ea) "' not found.")
|
(throw (ex-info (str "Account '" (:account_identifier ea) "' not found.")
|
||||||
{:status :error})))
|
{:status :error})))
|
||||||
(let [matching-account (when (re-matches #"^[0-9]+$" (:account_identifier ea))
|
(let [matching-account (when (re-matches #"^[0-9]+$" (:account_identifier ea))
|
||||||
(a/get-account-by-numeric-code-and-sets (Integer/parseInt (:account_identifier ea)) ["default"]))]
|
(a/get-account-by-numeric-code-and-sets (Integer/parseInt (:account_identifier ea)) ["default"]))]
|
||||||
(when (and matching-account
|
(when (and matching-account
|
||||||
(:account/location matching-account)
|
(:account/location matching-account)
|
||||||
(not= (:account/location matching-account)
|
(not= (:account/location matching-account)
|
||||||
(:location ea)))
|
(:location ea)))
|
||||||
(throw (ex-info (str "Account '"
|
(throw (ex-info (str "Account '"
|
||||||
(:account/numeric-code matching-account)
|
(:account/numeric-code matching-account)
|
||||||
"' requires location '"
|
"' requires location '"
|
||||||
(:account/location matching-account)
|
(:account/location matching-account)
|
||||||
"' but got '"
|
"' but got '"
|
||||||
(:location ea)
|
(:location ea)
|
||||||
"'")
|
"'")
|
||||||
{:status :error})))
|
{: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 (ex-info (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)
|
||||||
"'")
|
"'")
|
||||||
{:status :error})))
|
{:status :error})))
|
||||||
(remove-nils (cond-> {:journal-entry-line/location (:location ea)
|
(remove-nils (cond-> {:journal-entry-line/location (:location ea)
|
||||||
:journal-entry-line/debit (when (> debit 0)
|
:journal-entry-line/debit (when (> debit 0)
|
||||||
debit)
|
debit)
|
||||||
:journal-entry-line/credit (when (> credit 0)
|
:journal-entry-line/credit (when (> credit 0)
|
||||||
credit)}
|
credit)}
|
||||||
matching-account (assoc :journal-entry-line/account (:db/id matching-account))
|
matching-account (assoc :journal-entry-line/account (:db/id matching-account))
|
||||||
(not matching-account) (assoc :journal-entry-line/account [:bank-account/code (:account_identifier ea)]))))))
|
(not matching-account) (assoc :journal-entry-line/account [:bank-account/code (:account_identifier ea)]))))))
|
||||||
(:line_items entry))
|
(:line_items entry))
|
||||||
|
|
||||||
:journal-entry/cleared true})))))
|
:journal-entry/cleared true}))))))
|
||||||
(:entries args))))
|
(:entries args))))
|
||||||
errors (filter #(= (:status %) :error) transaction)
|
errors (filter #(= (:status %) :error) transaction)
|
||||||
ignored (filter #(= (:status %) :ignored) transaction)
|
ignored (filter #(= (:status %) :ignored) transaction)
|
||||||
@@ -473,122 +443,6 @@
|
|||||||
: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)}))
|
||||||
|
|
||||||
(defn accounts-needing-rebuild [ db client]
|
|
||||||
(->> (dc/qseq '[:find ?c ?a ?l (min ?d)
|
|
||||||
:in $ ?c
|
|
||||||
:where [?je :journal-entry/client ?c]
|
|
||||||
[?je :journal-entry/line-items ?jel]
|
|
||||||
(or (not [?jel :journal-entry-line/running-balance])
|
|
||||||
[?jel :journal-entry-line/dirty true])
|
|
||||||
[?jel :journal-entry-line/account ?a]
|
|
||||||
[?jel :journal-entry-line/location ?l]
|
|
||||||
[?je :journal-entry/date ?d]]
|
|
||||||
db
|
|
||||||
client)
|
|
||||||
(map (fn [[client account location starting-at ]]
|
|
||||||
{:client client
|
|
||||||
:account account
|
|
||||||
:starting-at starting-at
|
|
||||||
:location location}))))
|
|
||||||
|
|
||||||
(defn find-running-balance-start [account-needing-rebuild db ]
|
|
||||||
(let [starting-from (or (->> (dc/q '[:find ?d ?je ?jel ?rbs
|
|
||||||
:in $ ?c ?starting-at ?a ?l
|
|
||||||
:where
|
|
||||||
[?je :journal-entry/client ?c]
|
|
||||||
[?je :journal-entry/date ?d]
|
|
||||||
[(< ?d ?starting-at)]
|
|
||||||
[?je :journal-entry/line-items ?jel]
|
|
||||||
[?jel :journal-entry-line/account ?a]
|
|
||||||
[?jel :journal-entry-line/location ?l]
|
|
||||||
[?jel :journal-entry-line/running-balance ?rbs]
|
|
||||||
]
|
|
||||||
db
|
|
||||||
(:client account-needing-rebuild)
|
|
||||||
(:starting-at account-needing-rebuild)
|
|
||||||
(:account account-needing-rebuild)
|
|
||||||
(:location account-needing-rebuild))
|
|
||||||
(sort)
|
|
||||||
(last)
|
|
||||||
(last))
|
|
||||||
0.0)]
|
|
||||||
(mu/log ::starting-rebuild-at
|
|
||||||
:at starting-from)
|
|
||||||
starting-from))
|
|
||||||
|
|
||||||
(defn get-dirty-entries [account-needing-rebuild db ]
|
|
||||||
(->> (dc/q
|
|
||||||
'[:find ?d ?jel ?debit ?credit
|
|
||||||
:in $ ?c ?starting-at ?a ?l
|
|
||||||
:where
|
|
||||||
[?e :journal-entry/client ?c]
|
|
||||||
[?e :journal-entry/date ?d]
|
|
||||||
[(>= ?d ?starting-at)]
|
|
||||||
[?e :journal-entry/line-items ?jel]
|
|
||||||
[?jel :journal-entry-line/account ?a]
|
|
||||||
[?jel :journal-entry-line/location ?l]
|
|
||||||
[(get-else $ ?jel :journal-entry-line/debit 0.0) ?debit ]
|
|
||||||
[(get-else $ ?jel :journal-entry-line/credit 0.0) ?credit]]
|
|
||||||
db
|
|
||||||
(:client account-needing-rebuild)
|
|
||||||
(:starting-at account-needing-rebuild)
|
|
||||||
(:account account-needing-rebuild)
|
|
||||||
(:location account-needing-rebuild))
|
|
||||||
sort
|
|
||||||
(map #(drop 1 %))))
|
|
||||||
|
|
||||||
(defn compute-running-balance [account-needing-refresh]
|
|
||||||
(mu/log ::compute
|
|
||||||
:dirty-count (count (:dirty-entries account-needing-refresh)))
|
|
||||||
(second
|
|
||||||
(reduce
|
|
||||||
(fn [[running-balance rows] [id debit credit] ]
|
|
||||||
(let [new-running-balance (+ running-balance
|
|
||||||
(if (#{:account-type/asset
|
|
||||||
:account-type/dividend
|
|
||||||
:account-type/expense} (:account-type account-needing-refresh))
|
|
||||||
(- debit credit)
|
|
||||||
(- credit debit)))]
|
|
||||||
[new-running-balance
|
|
||||||
(conj rows
|
|
||||||
{:db/id id
|
|
||||||
:journal-entry-line/running-balance new-running-balance
|
|
||||||
:journal-entry-line/dirty false})]))
|
|
||||||
|
|
||||||
[(:build-from account-needing-refresh) []]
|
|
||||||
(:dirty-entries account-needing-refresh))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn refresh-running-balance-cache []
|
|
||||||
(doseq [c (shuffle (map first
|
|
||||||
(dc/q '[:find (pull ?c [:client/code :db/id])
|
|
||||||
:where [?c :client/code]]
|
|
||||||
(dc/db conn))))]
|
|
||||||
(mu/trace ::building-running-balance
|
|
||||||
[:client c]
|
|
||||||
(mu/with-context {:client c}
|
|
||||||
(let [db (dc/db conn)
|
|
||||||
accounts-needing-rebuild (accounts-needing-rebuild db (:db/id c))]
|
|
||||||
(when (seq accounts-needing-rebuild)
|
|
||||||
(mu/log ::found-accounts-needing-rebuild
|
|
||||||
:accounts accounts-needing-rebuild)
|
|
||||||
(audit-transact-batch
|
|
||||||
(->> accounts-needing-rebuild
|
|
||||||
(mapcat (fn [account-needing-rebuild]
|
|
||||||
(mu/with-context {:account account-needing-rebuild}
|
|
||||||
(-> account-needing-rebuild
|
|
||||||
(assoc :build-from (find-running-balance-start account-needing-rebuild db))
|
|
||||||
(assoc :dirty-entries (get-dirty-entries account-needing-rebuild db))
|
|
||||||
(assoc :account-type (:account_type ((build-account-lookup (:client account-needing-rebuild)) (:account account-needing-rebuild))))
|
|
||||||
(compute-running-balance))))))
|
|
||||||
{:user/name "running-balance-cache"})))))))
|
|
||||||
|
|
||||||
|
|
||||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
|
||||||
(mount/defstate running-balance-cache-worker
|
|
||||||
:start (scheduler/every (* 15 60 (+ 500 (rand-int 500))) (heartbeat refresh-running-balance-cache "running-balance-cache"))
|
|
||||||
:stop (scheduler/stop running-balance-cache-worker))
|
|
||||||
|
|
||||||
|
|
||||||
(defn get-journal-detail-report [context input _]
|
(defn get-journal-detail-report [context input _]
|
||||||
(let [category-totals (atom {})
|
(let [category-totals (atom {})
|
||||||
@@ -652,8 +506,8 @@
|
|||||||
(into base-categories
|
(into base-categories
|
||||||
(for [client-id (:client_ids input)
|
(for [client-id (:client_ids input)
|
||||||
:let [_ (assert-can-see-client (:id context) client-id)
|
:let [_ (assert-can-see-client (:id context) client-id)
|
||||||
account-lookup (build-account-lookup client-id)
|
account-lookup (build-account-lookup client-id)
|
||||||
c (dc/pull (dc/db conn) '[:client/locations] client-id)]
|
c (dc/pull (dc/db conn) '[:client/locations] client-id)]
|
||||||
location (:client/locations c)
|
location (:client/locations c)
|
||||||
line [{:client_id client-id
|
line [{:client_id client-id
|
||||||
:location location
|
:location location
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
(ns auto-ap.graphql.transactions
|
(ns auto-ap.graphql.transactions
|
||||||
(:require
|
(:require
|
||||||
[auto-ap.datomic
|
[auto-ap.datomic :refer [conn pull-attr pull-many pull-ref remove-nils]]
|
||||||
:refer [audit-transact
|
|
||||||
audit-transact-batch
|
|
||||||
conn
|
|
||||||
pull-attr
|
|
||||||
pull-many
|
|
||||||
pull-ref
|
|
||||||
remove-nils]]
|
|
||||||
[iol-ion.tx :refer [upsert-entity]]
|
|
||||||
[auto-ap.datomic.accounts :as a]
|
[auto-ap.datomic.accounts :as a]
|
||||||
[auto-ap.datomic.checks :as d-checks]
|
[auto-ap.datomic.checks :as d-checks]
|
||||||
[auto-ap.datomic.invoices :as d-invoices]
|
[auto-ap.datomic.invoices :as d-invoices]
|
||||||
@@ -19,10 +11,10 @@
|
|||||||
:refer [->graphql
|
:refer [->graphql
|
||||||
<-graphql
|
<-graphql
|
||||||
assert-admin
|
assert-admin
|
||||||
attach-tracing-resolvers
|
|
||||||
assert-can-see-client
|
assert-can-see-client
|
||||||
assert-not-locked
|
assert-not-locked
|
||||||
assert-power-user
|
assert-power-user
|
||||||
|
attach-tracing-resolvers
|
||||||
enum->keyword
|
enum->keyword
|
||||||
ident->enum-f
|
ident->enum-f
|
||||||
snake->kebab]]
|
snake->kebab]]
|
||||||
@@ -35,7 +27,8 @@
|
|||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.tools.logging :as log]
|
[clojure.tools.logging :as log]
|
||||||
[datomic.client.api :as dc]))
|
[datomic.client.api :as dc]
|
||||||
|
[iol-ion.tx :refer [random-tempid upsert-entity]]))
|
||||||
|
|
||||||
(def approval-status->graphql (ident->enum-f :transaction/approval-status))
|
(def approval-status->graphql (ident->enum-f :transaction/approval-status))
|
||||||
|
|
||||||
@@ -327,7 +320,7 @@
|
|||||||
|
|
||||||
(defn transaction-account->entity [{:keys [id account_id amount location]}]
|
(defn transaction-account->entity [{:keys [id account_id amount location]}]
|
||||||
#:transaction-account {:amount amount
|
#:transaction-account {:amount amount
|
||||||
:db/id id
|
:db/id (or id (random-tempid))
|
||||||
:account account_id
|
:account account_id
|
||||||
:location location})
|
:location location})
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
(ns auto-ap.ledger
|
(ns auto-ap.ledger
|
||||||
(:require
|
(:require
|
||||||
[auto-ap.datomic :refer [conn remove-nils pull-ref audit-transact]]
|
[auto-ap.datomic
|
||||||
[auto-ap.utils :refer [dollars-0? dollars=]]
|
:refer [audit-transact
|
||||||
|
audit-transact-batch
|
||||||
|
conn
|
||||||
|
pull-id
|
||||||
|
pull-ref
|
||||||
|
remove-nils]]
|
||||||
|
[auto-ap.utils :refer [by dollars-0? dollars=]]
|
||||||
[clj-time.coerce :as c]
|
[clj-time.coerce :as c]
|
||||||
[clj-time.core :as t]
|
[clj-time.core :as t]
|
||||||
[clojure.tools.logging :as log]
|
[clojure.tools.logging :as log]
|
||||||
|
[com.brunobonacci.mulog :as mu]
|
||||||
[com.unbounce.dogstatsd.core :as statsd]
|
[com.unbounce.dogstatsd.core :as statsd]
|
||||||
[datomic.client.api :as dc]))
|
[datomic.client.api :as dc]
|
||||||
|
[iol-ion.tx :refer [upsert-ledger]]))
|
||||||
|
|
||||||
(defn datums->impacted-entity [db [e changes]]
|
(defn datums->impacted-entity [db [e changes]]
|
||||||
(let [entity (dc/pull db '[{:invoice/_expense-accounts [:db/id] :transaction/_accounts [:db/id]}] e)
|
(let [entity (dc/pull db '[{:invoice/_expense-accounts [:db/id] :transaction/_accounts [:db/id]}] e)
|
||||||
@@ -47,16 +55,16 @@
|
|||||||
:journal-entry/vendor (:db/id (:invoice/vendor entity))
|
:journal-entry/vendor (:db/id (:invoice/vendor entity))
|
||||||
:journal-entry/amount (Math/abs (:invoice/total entity))
|
:journal-entry/amount (Math/abs (:invoice/total entity))
|
||||||
|
|
||||||
:journal-entry/line-items (into [(cond-> {:journal-entry-line/account :account/accounts-payable
|
:journal-entry/line-items (into [(cond-> {:db/id (str (:db/id entity) "-" 0)
|
||||||
:journal-entry-line/dirty true
|
:journal-entry-line/account :account/accounts-payable
|
||||||
:journal-entry-line/location "A"
|
:journal-entry-line/location "A"
|
||||||
}
|
}
|
||||||
credit-invoice? (assoc :journal-entry-line/debit (Math/abs (:invoice/total entity)))
|
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))))]
|
(not credit-invoice?) (assoc :journal-entry-line/credit (Math/abs (:invoice/total entity))))]
|
||||||
(map (fn [ea]
|
(map-indexed (fn [i ea]
|
||||||
(cond->
|
(cond->
|
||||||
{:journal-entry-line/account (:db/id (:invoice-expense-account/account ea))
|
{:db/id (str (:db/id entity) "-" (inc i))
|
||||||
:journal-entry-line/dirty true
|
:journal-entry-line/account (:db/id (:invoice-expense-account/account ea))
|
||||||
:journal-entry-line/location (or (:invoice-expense-account/location ea) "HQ")
|
: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)))
|
credit-invoice? (assoc :journal-entry-line/credit (Math/abs (:invoice-expense-account/amount ea)))
|
||||||
@@ -93,18 +101,19 @@
|
|||||||
:journal-entry/cleared-against (:transaction/cleared-against entity)
|
:journal-entry/cleared-against (:transaction/cleared-against entity)
|
||||||
|
|
||||||
:journal-entry/line-items (into [(remove-nils {:journal-entry-line/account (:db/id (:transaction/bank-account entity))
|
:journal-entry/line-items (into [(remove-nils {:journal-entry-line/account (:db/id (:transaction/bank-account entity))
|
||||||
:journal-entry-line/dirty true
|
:db/id (str (:db/id entity) "-" 0)
|
||||||
:journal-entry-line/location "A"
|
:journal-entry-line/location "A"
|
||||||
:journal-entry-line/credit (when credit-from-bank?
|
:journal-entry-line/credit (when credit-from-bank?
|
||||||
(Math/abs (:transaction/amount entity)))
|
(Math/abs (:transaction/amount entity)))
|
||||||
:journal-entry-line/debit (when debit-from-bank?
|
:journal-entry-line/debit (when debit-from-bank?
|
||||||
(Math/abs (:transaction/amount entity)))})
|
(Math/abs (:transaction/amount entity)))})
|
||||||
]
|
]
|
||||||
(map
|
(map-indexed
|
||||||
(fn [a]
|
(fn [i a]
|
||||||
(remove-nils{:journal-entry-line/account (:db/id (:transaction-account/account a))
|
(remove-nils{
|
||||||
|
:db/id (str (:db/id entity) "-" (inc i))
|
||||||
|
:journal-entry-line/account (:db/id (:transaction-account/account a))
|
||||||
:journal-entry-line/location (:transaction-account/location a)
|
:journal-entry-line/location (:transaction-account/location a)
|
||||||
:journal-entry-line/dirty true
|
|
||||||
:journal-entry-line/debit (when credit-from-bank?
|
:journal-entry-line/debit (when credit-from-bank?
|
||||||
(Math/abs (:transaction-account/amount a)))
|
(Math/abs (:transaction-account/amount a)))
|
||||||
:journal-entry-line/credit (when debit-from-bank?
|
:journal-entry-line/credit (when debit-from-bank?
|
||||||
@@ -186,20 +195,19 @@
|
|||||||
|
|
||||||
|
|
||||||
(defn touch-transaction [e]
|
(defn touch-transaction [e]
|
||||||
(dc/transact conn {:tx-data [[:db/retractEntity [:journal-entry/original-entity e]]]})
|
|
||||||
(when-let [change (entity-change->ledger (dc/db conn)
|
(when-let [change (entity-change->ledger (dc/db conn)
|
||||||
[:transaction e])]
|
[:transaction e])]
|
||||||
(dc/transact conn {:tx-data [{:db/id "datomic.tx"
|
(dc/transact conn {:tx-data [{:db/id "datomic.tx"
|
||||||
:db/doc "touching transaction to update ledger"}
|
:db/doc "touching transaction to update ledger"}
|
||||||
change]})))
|
`(upsert-ledger ~change)]})))
|
||||||
|
|
||||||
(defn touch-invoice [e]
|
(defn touch-invoice [e]
|
||||||
(dc/transact conn [[:db/retractEntity [:journal-entry/original-entity e]]])
|
|
||||||
(when-let [change (entity-change->ledger (dc/db conn)
|
(when-let [change (entity-change->ledger (dc/db conn)
|
||||||
[:invoice e])]
|
[:invoice e])]
|
||||||
(dc/transact conn [{:db/id "datomic.tx"
|
(dc/transact conn [{:db/id "datomic.tx"
|
||||||
:db/doc "touching invoice to update ledger"}
|
:db/doc "touching invoice to update ledger"}
|
||||||
change])))
|
`(upsert-ledger ~change)])))
|
||||||
|
|
||||||
(defn lazy-tx-range
|
(defn lazy-tx-range
|
||||||
([start end xf] (lazy-tx-range start end xf 0))
|
([start end xf] (lazy-tx-range start end xf 0))
|
||||||
([start end xf o]
|
([start end xf o]
|
||||||
@@ -432,11 +440,9 @@
|
|||||||
(set))
|
(set))
|
||||||
ledger-txs (->> affected-entities
|
ledger-txs (->> affected-entities
|
||||||
(map #(entity-change->ledger (:db-after tx) %))
|
(map #(entity-change->ledger (:db-after tx) %))
|
||||||
(filter seq))
|
(filter seq)
|
||||||
;; TODO mark deleted journal-entry-line accounts as dirty, needing refresh
|
(map (fn [l]
|
||||||
retractions (map (fn [[_ e]] [:db/retractEntity [:journal-entry/original-entity e]]) affected-entities)]
|
`(upsert-ledger ~l))))]
|
||||||
(when (seq retractions)
|
|
||||||
(audit-transact retractions id))
|
|
||||||
(when (seq ledger-txs)
|
(when (seq ledger-txs)
|
||||||
(audit-transact ledger-txs id))
|
(audit-transact ledger-txs id))
|
||||||
tx))
|
tx))
|
||||||
@@ -459,3 +465,208 @@
|
|||||||
|
|
||||||
{}
|
{}
|
||||||
(partition-all 50 txes))))
|
(partition-all 50 txes))))
|
||||||
|
|
||||||
|
(defn build-account-lookup [client-id]
|
||||||
|
(let [accounts (by :db/id (map first (dc/q {:query {:find ['(pull ?e [:db/id :account/name
|
||||||
|
:account/numeric-code
|
||||||
|
{:account/type [:db/ident]
|
||||||
|
:account/client-overrides [:account-client-override/client :account-client-override/name]}
|
||||||
|
])]
|
||||||
|
:in ['$]
|
||||||
|
:where ['[?e :account/name]]}
|
||||||
|
:args [(dc/db conn )]})))
|
||||||
|
|
||||||
|
bank-accounts (by :db/id (map first (dc/q {:query {:find ['(pull ?e [:db/id :bank-account/name :bank-account/numeric-code {:bank-account/type [:db/ident]}])]
|
||||||
|
:in ['$]
|
||||||
|
:where ['[?e :bank-account/name]]}
|
||||||
|
:args [(dc/db conn)]})))
|
||||||
|
overrides-by-client (->> accounts
|
||||||
|
vals
|
||||||
|
(mapcat (fn [a]
|
||||||
|
(map (fn [o]
|
||||||
|
[[(:db/id a) (:db/id (:account-client-override/client o))]
|
||||||
|
(:account-client-override/name o)])
|
||||||
|
(:account/client-overrides a))
|
||||||
|
) )
|
||||||
|
(into {} ))]
|
||||||
|
(fn [a]
|
||||||
|
{:name (or (:bank-account/name (bank-accounts a))
|
||||||
|
(overrides-by-client [a client-id])
|
||||||
|
(:account/name (accounts a)))
|
||||||
|
:account_type (or (:db/ident (:account/type (accounts a)))
|
||||||
|
({:bank-account-type/check :account-type/asset
|
||||||
|
:bank-account-type/cash :account-type/asset
|
||||||
|
:bank-account-type/credit :account-type/liability}
|
||||||
|
(:db/ident (:bank-account/type (bank-accounts a)))))
|
||||||
|
:numeric_code (or (:account/numeric-code (accounts a))
|
||||||
|
(:bank-account/numeric-code (bank-accounts a)))
|
||||||
|
:client_id client-id})))
|
||||||
|
|
||||||
|
(defn reset-client+account+location+date
|
||||||
|
([] (reset-client+account+location+date (map first (dc/q '[:find ?c :where [?c :client/code]] (dc/db conn)))))
|
||||||
|
([clients]
|
||||||
|
(doseq [client clients
|
||||||
|
:let [_ (mu/log ::reseting-index-for :client client)]
|
||||||
|
batch
|
||||||
|
(->> (dc/qseq '[:find (pull ?je [:journal-entry/date :journal-entry/client {:journal-entry/line-items [:journal-entry-line/account :journal-entry-line/location :db/id]}])
|
||||||
|
:in $ ?c
|
||||||
|
:where [?je :journal-entry/client ?c]]
|
||||||
|
(dc/db conn)
|
||||||
|
client
|
||||||
|
|
||||||
|
)
|
||||||
|
(map first)
|
||||||
|
(mapcat (fn [je]
|
||||||
|
(map (fn [jel]
|
||||||
|
{:db/id (:db/id jel)
|
||||||
|
:journal-entry-line/client+account+location+date
|
||||||
|
[(-> je :journal-entry/client :db/id)
|
||||||
|
(-> jel :journal-entry-line/account :db/id)
|
||||||
|
|
||||||
|
(-> jel :journal-entry-line/location)
|
||||||
|
|
||||||
|
(-> je :journal-entry/date)]})
|
||||||
|
(:journal-entry/line-items je))))
|
||||||
|
(partition-all 500)
|
||||||
|
)]
|
||||||
|
(mu/log ::batch-completed)
|
||||||
|
(dc/transact conn {:tx-data batch}))))
|
||||||
|
|
||||||
|
(defn find-mismatch-index []
|
||||||
|
(reduce + 0
|
||||||
|
(for [c (map first (dc/q '[:find ?c :where [?c :client/code]] (dc/db conn)))
|
||||||
|
:let [_ (println "searching for" c)
|
||||||
|
a (->> (dc/index-pull (dc/db conn)
|
||||||
|
{:index :avet
|
||||||
|
:selector [:db/id :journal-entry-line/location :journal-entry-line/account :journal-entry-line/client+account+location+date {:journal-entry/_line-items [:journal-entry/date :journal-entry/client]}]
|
||||||
|
:start [:journal-entry-line/client+account+location+date [c]]})
|
||||||
|
(take-while (fn [result]
|
||||||
|
(= c (first (:journal-entry-line/client+account+location+date result)))
|
||||||
|
))
|
||||||
|
(filter (fn [{index :journal-entry-line/client+account+location+date :as result}]
|
||||||
|
(not= index
|
||||||
|
[(-> result :journal-entry/_line-items :journal-entry/client :db/id)
|
||||||
|
(-> result :journal-entry-line/account :db/id)
|
||||||
|
(-> result :journal-entry-line/location)
|
||||||
|
(-> result :journal-entry/_line-items :journal-entry/date)]))))]]
|
||||||
|
(do (println (count a))
|
||||||
|
(count a)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn accounts-needing-rebuild [ db client]
|
||||||
|
(let [client (pull-id db client)]
|
||||||
|
(->> (dc/qseq '[:find ?c ?a ?l (min ?d)
|
||||||
|
:in $ ?c
|
||||||
|
:where
|
||||||
|
[?jel :journal-entry-line/dirty true]
|
||||||
|
[?jel :journal-entry-line/account ?a]
|
||||||
|
[?jel :journal-entry-line/location ?l]
|
||||||
|
[?je :journal-entry/line-items ?jel]
|
||||||
|
[?je :journal-entry/client ?c]
|
||||||
|
[?je :journal-entry/date ?d]]
|
||||||
|
db
|
||||||
|
client)
|
||||||
|
(map (fn [[client account location starting-at ]]
|
||||||
|
{:client client
|
||||||
|
:account account
|
||||||
|
:starting-at starting-at
|
||||||
|
:location location})))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn find-running-balance-start [account-needing-rebuild db ]
|
||||||
|
(or
|
||||||
|
(->> (dc/index-pull db
|
||||||
|
{:index :avet
|
||||||
|
:selector [:db/id :journal-entry-line/running-balance :journal-entry-line/client+account+location+date]
|
||||||
|
:start [:journal-entry-line/client+account+location+date
|
||||||
|
[(:client account-needing-rebuild)
|
||||||
|
(:account account-needing-rebuild)
|
||||||
|
(:location account-needing-rebuild)
|
||||||
|
(:starting-at account-needing-rebuild)]]
|
||||||
|
|
||||||
|
:reverse true
|
||||||
|
:limit 500})
|
||||||
|
(take-while (fn [result]
|
||||||
|
(= [(:client account-needing-rebuild)
|
||||||
|
(:account account-needing-rebuild)
|
||||||
|
(:location account-needing-rebuild)]
|
||||||
|
(take 3 (:journal-entry-line/client+account+location+date result)))))
|
||||||
|
(drop-while (fn [{[_ _ _ date] :journal-entry-line/client+account+location+date}]
|
||||||
|
(>= (compare date (:starting-at account-needing-rebuild)) 0)))
|
||||||
|
first
|
||||||
|
:journal-entry-line/running-balance
|
||||||
|
)
|
||||||
|
0.0))
|
||||||
|
|
||||||
|
(defn get-dirty-entries [account-needing-rebuild db ]
|
||||||
|
(->> (dc/index-pull db
|
||||||
|
{:index :avet
|
||||||
|
:selector [:db/id :journal-entry-line/debit :journal-entry-line/credit :journal-entry-line/client+account+location+date]
|
||||||
|
:start [:journal-entry-line/client+account+location+date
|
||||||
|
[(:client account-needing-rebuild)
|
||||||
|
(:account account-needing-rebuild)
|
||||||
|
(:location account-needing-rebuild)
|
||||||
|
(:starting-at account-needing-rebuild)]]
|
||||||
|
})
|
||||||
|
(take-while (fn [result]
|
||||||
|
(= [(:client account-needing-rebuild)
|
||||||
|
(:account account-needing-rebuild)
|
||||||
|
(:location account-needing-rebuild)]
|
||||||
|
(take 3 (:journal-entry-line/client+account+location+date result)))))
|
||||||
|
(map (fn [result]
|
||||||
|
[(:db/id result) (:journal-entry-line/debit result 0.0) (:journal-entry-line/credit result 0.0) ]))))
|
||||||
|
|
||||||
|
(defn compute-running-balance [account-needing-refresh]
|
||||||
|
(mu/log ::compute
|
||||||
|
:dirty-count (count (:dirty-entries account-needing-refresh)))
|
||||||
|
(second
|
||||||
|
(reduce
|
||||||
|
(fn [[running-balance rows] [id debit credit] ]
|
||||||
|
(let [new-running-balance (+ running-balance
|
||||||
|
(if (#{:account-type/asset
|
||||||
|
:account-type/dividend
|
||||||
|
:account-type/expense} (:account-type account-needing-refresh))
|
||||||
|
(- debit credit)
|
||||||
|
(- credit debit)))]
|
||||||
|
[new-running-balance
|
||||||
|
(conj rows
|
||||||
|
{:db/id id
|
||||||
|
:journal-entry-line/running-balance new-running-balance
|
||||||
|
:journal-entry-line/dirty false})]))
|
||||||
|
|
||||||
|
[(:build-from account-needing-refresh) []]
|
||||||
|
(:dirty-entries account-needing-refresh))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn refresh-running-balance-cache
|
||||||
|
([] (refresh-running-balance-cache (shuffle (map first
|
||||||
|
(dc/q '[:find (pull ?c [:client/code :db/id])
|
||||||
|
:where [?c :client/code]]
|
||||||
|
(dc/db conn))))))
|
||||||
|
([clients]
|
||||||
|
(doseq [c clients]
|
||||||
|
(mu/trace ::building-running-balance
|
||||||
|
[:client c]
|
||||||
|
(mu/with-context {:client c}
|
||||||
|
(let [db (dc/db conn)
|
||||||
|
accounts-needing-rebuild (accounts-needing-rebuild db (:db/id c))]
|
||||||
|
(when (seq accounts-needing-rebuild)
|
||||||
|
(mu/log ::found-accounts-needing-rebuild
|
||||||
|
:accounts accounts-needing-rebuild)
|
||||||
|
(audit-transact-batch
|
||||||
|
(->> accounts-needing-rebuild
|
||||||
|
(mapcat (fn [account-needing-rebuild]
|
||||||
|
(mu/with-context {:account account-needing-rebuild}
|
||||||
|
(-> account-needing-rebuild
|
||||||
|
(assoc :build-from (find-running-balance-start account-needing-rebuild db))
|
||||||
|
(assoc :dirty-entries (get-dirty-entries account-needing-rebuild db))
|
||||||
|
(assoc :account-type (:account_type ((build-account-lookup (:client account-needing-rebuild)) (:account account-needing-rebuild))))
|
||||||
|
(compute-running-balance))))))
|
||||||
|
{:user/name "running-balance-cache"}))))))))
|
||||||
|
|
||||||
|
|
||||||
|
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||||
|
#_(mount/defstate running-balance-cache-worker
|
||||||
|
:start (scheduler/every (* 15 60 (+ 500 (rand-int 500))) (heartbeat refresh-running-balance-cache "running-balance-cache"))
|
||||||
|
:stop (scheduler/stop running-balance-cache-worker))
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
(ns iol-ion.tx
|
|
||||||
(:require [datomic.client.api :as dc])
|
|
||||||
(:import [java.util UUID]))
|
|
||||||
|
|
||||||
(defn random-tempid []
|
|
||||||
(str (UUID/randomUUID)))
|
|
||||||
|
|
||||||
(defn remove-nils [m]
|
|
||||||
(let [result (reduce-kv
|
|
||||||
(fn [m k v]
|
|
||||||
(if (not (nil? v))
|
|
||||||
(assoc m k v)
|
|
||||||
m
|
|
||||||
))
|
|
||||||
{}
|
|
||||||
m)]
|
|
||||||
(if (seq result)
|
|
||||||
result
|
|
||||||
nil)))
|
|
||||||
|
|
||||||
(defn by
|
|
||||||
([f xs]
|
|
||||||
(by f identity xs))
|
|
||||||
([f fv xs]
|
|
||||||
(reduce
|
|
||||||
#(assoc %1 (f %2) (fv %2))
|
|
||||||
{}
|
|
||||||
xs)))
|
|
||||||
|
|
||||||
(defn pull-many [db read ids ]
|
|
||||||
(->> (dc/q '[:find (pull ?e r)
|
|
||||||
:in $ [?e ...] r]
|
|
||||||
db
|
|
||||||
ids
|
|
||||||
read)
|
|
||||||
(map first)))
|
|
||||||
|
|
||||||
(declare upsert-entity)
|
|
||||||
|
|
||||||
(defn reset-rels [db e a vs]
|
|
||||||
(assert (every? :db/id vs) (format "In order to reset attribute %s, every value must have :db/id" a))
|
|
||||||
(let [ids (when-not (string? e)
|
|
||||||
(->> (dc/q '[:find ?z
|
|
||||||
:in $ ?e ?a
|
|
||||||
:where [?e ?a ?z]]
|
|
||||||
db e a)
|
|
||||||
(map first)))
|
|
||||||
new-id-set (set (map :db/id vs))
|
|
||||||
retract-ids (filter (complement new-id-set) ids)
|
|
||||||
{is-component? :db/isComponent} (dc/pull db [:db/isComponent] a)
|
|
||||||
new-rels (filter (complement (set ids)) (map :db/id vs))]
|
|
||||||
(-> []
|
|
||||||
(into (map (fn [i] (if is-component?
|
|
||||||
[:db/retractEntity i]
|
|
||||||
[:db/retract e a i ])) retract-ids))
|
|
||||||
(into (map (fn [i] [:db/add e a i]) new-rels))
|
|
||||||
(into (mapcat (fn [i] (upsert-entity db i)) vs)))))
|
|
||||||
|
|
||||||
(defn reset-scalars [db e a vs]
|
|
||||||
|
|
||||||
(let [extant (when-not (string? e)
|
|
||||||
(->> (dc/q '[:find ?z
|
|
||||||
:in $ ?e ?a
|
|
||||||
:where [?e ?a ?z]]
|
|
||||||
db e a)
|
|
||||||
(map first)))
|
|
||||||
retracts (filter (complement (set vs)) extant)
|
|
||||||
new (filter (complement (set extant)) vs)]
|
|
||||||
(-> []
|
|
||||||
(into (map (fn [i] [:db/retract e a i ]) retracts))
|
|
||||||
(into (map (fn [i] [:db/add e a i]) new)))))
|
|
||||||
|
|
||||||
|
|
||||||
;; TODO unit test this
|
|
||||||
(defn upsert-entity [db entity]
|
|
||||||
(assert (:db/id entity) "Cannot upsert without :db/id")
|
|
||||||
(let [e (:db/id entity)
|
|
||||||
is-new? (string? e)
|
|
||||||
extant-entity (when-not is-new?
|
|
||||||
(dc/pull db (keys entity) (:db/id entity)))
|
|
||||||
ident->value-type (by :db/ident (comp :db/ident
|
|
||||||
:db/valueType)
|
|
||||||
(pull-many
|
|
||||||
db
|
|
||||||
[:db/valueType :db/ident]
|
|
||||||
(keys entity)))
|
|
||||||
ops (->> entity
|
|
||||||
(reduce
|
|
||||||
(fn [ops [a v]]
|
|
||||||
(cond
|
|
||||||
(= :db/id a)
|
|
||||||
ops
|
|
||||||
|
|
||||||
(or (= v (a extant-entity))
|
|
||||||
(= v (:db/ident (a extant-entity) :nope))
|
|
||||||
(= v (:db/id (a extant-entity)) :nope))
|
|
||||||
ops
|
|
||||||
|
|
||||||
(and (nil? v)
|
|
||||||
(not (nil? (a extant-entity))))
|
|
||||||
(conj ops [:db/retract e a (cond-> (a extant-entity)
|
|
||||||
(:db/id (a extant-entity)) :db/id)])
|
|
||||||
|
|
||||||
(nil? v)
|
|
||||||
ops
|
|
||||||
|
|
||||||
;; reset relationships if it's refs, and not a lookup (i.e., seq of maps, or empty seq)
|
|
||||||
(and (sequential? v) (= :db.type/ref (ident->value-type a)) (every? map? v))
|
|
||||||
(into ops (reset-rels db e a v))
|
|
||||||
|
|
||||||
(and (sequential? v) (not= :db.type/ref (ident->value-type a)))
|
|
||||||
(into ops (reset-scalars db e a v))
|
|
||||||
|
|
||||||
(and (map? v)
|
|
||||||
(= :db.type/ref (ident->value-type a)))
|
|
||||||
(let [id (or (:db/id v) (random-tempid))]
|
|
||||||
(-> ops
|
|
||||||
(conj [:db/add e a id])
|
|
||||||
(into (upsert-entity db (assoc v :db/id id)))))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(conj ops [:db/add e a v])
|
|
||||||
))
|
|
||||||
[]))]
|
|
||||||
ops))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn propose-invoice [db invoice]
|
|
||||||
(let [existing? (boolean (seq (dc/q '[:find ?i
|
|
||||||
:in $ ?invoice-number ?client ?vendor
|
|
||||||
:where
|
|
||||||
[?i :invoice/invoice-number ?invoice-number]
|
|
||||||
[?i :invoice/client ?client]
|
|
||||||
[?i :invoice/vendor ?vendor]
|
|
||||||
(not [?i :invoice/status :invoice-status/voided])]
|
|
||||||
db
|
|
||||||
(:invoice/invoice-number invoice)
|
|
||||||
(:invoice/client invoice)
|
|
||||||
(:invoice/vendor invoice))))]
|
|
||||||
(if existing?
|
|
||||||
[]
|
|
||||||
[(remove-nils invoice)])))
|
|
||||||
142
test/clj/auto_ap/integration/graphql/ledger/running_balance.clj
Normal file
142
test/clj/auto_ap/integration/graphql/ledger/running_balance.clj
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
(ns auto-ap.integration.graphql.ledger.running-balance
|
||||||
|
(:require
|
||||||
|
[auto-ap.datomic :refer [conn pull-attr]]
|
||||||
|
[auto-ap.graphql.ledger :as sut]
|
||||||
|
[iol-ion.tx :refer [upsert-entity upsert-ledger]]
|
||||||
|
[auto-ap.integration.util :refer [wrap-setup]]
|
||||||
|
[clojure.test :as t :refer [deftest is testing use-fixtures]]
|
||||||
|
[datomic.client.api :as d]
|
||||||
|
[datomic.client.api :as dc]))
|
||||||
|
|
||||||
|
(use-fixtures :each wrap-setup)
|
||||||
|
|
||||||
|
(deftest running-balance
|
||||||
|
(let [{:strs [test-account-1
|
||||||
|
test-account-2
|
||||||
|
test-client
|
||||||
|
journal-entry-1
|
||||||
|
journal-entry-2
|
||||||
|
journal-entry-3
|
||||||
|
|
||||||
|
line-1-1
|
||||||
|
line-1-2
|
||||||
|
line-2-1
|
||||||
|
line-2-2
|
||||||
|
line-3-1
|
||||||
|
line-3-2]} (:tempids (doto (d/transact conn {:tx-data [{:db/id "test-account-1"
|
||||||
|
:account/type :account-type/asset}
|
||||||
|
{:db/id "test-account-2"
|
||||||
|
:account/type :account-type/equity}
|
||||||
|
{:db/id "test-client"
|
||||||
|
:client/code "TEST"}
|
||||||
|
`(upsert-ledger {:db/id "journal-entry-1"
|
||||||
|
:journal-entry/external-id "1"
|
||||||
|
:journal-entry/date #inst "2022-01-01"
|
||||||
|
:journal-entry/client "test-client"
|
||||||
|
:journal-entry/line-items [{:db/id "line-1-1"
|
||||||
|
:journal-entry-line/account "test-account-1"
|
||||||
|
:journal-entry-line/location "A"
|
||||||
|
:journal-entry-line/debit 10.0}
|
||||||
|
{:db/id "line-1-2"
|
||||||
|
:journal-entry-line/account "test-account-2"
|
||||||
|
:journal-entry-line/location "A"
|
||||||
|
:journal-entry-line/credit 10.0}]})
|
||||||
|
`(upsert-ledger {:db/id "journal-entry-2"
|
||||||
|
:journal-entry/date #inst "2022-01-02"
|
||||||
|
:journal-entry/external-id "2"
|
||||||
|
:journal-entry/client "test-client"
|
||||||
|
:journal-entry/line-items [{:db/id "line-2-1"
|
||||||
|
:journal-entry-line/account "test-account-1"
|
||||||
|
:journal-entry-line/location "A"
|
||||||
|
:journal-entry-line/debit 50.0}
|
||||||
|
{:db/id "line-2-2"
|
||||||
|
:journal-entry-line/account "test-account-2"
|
||||||
|
:journal-entry-line/location "A"
|
||||||
|
:journal-entry-line/credit 50.0}]})
|
||||||
|
`(upsert-ledger {:db/id "journal-entry-3"
|
||||||
|
:journal-entry/date #inst "2022-01-03"
|
||||||
|
:journal-entry/external-id "3"
|
||||||
|
:journal-entry/client "test-client"
|
||||||
|
:journal-entry/line-items [{:db/id "line-3-1"
|
||||||
|
:journal-entry-line/account "test-account-1"
|
||||||
|
:journal-entry-line/location "A"
|
||||||
|
:journal-entry-line/debit 150.0}
|
||||||
|
{:db/id "line-3-2"
|
||||||
|
:journal-entry-line/account "test-account-2"
|
||||||
|
:journal-entry-line/location "A"
|
||||||
|
:journal-entry-line/credit 150.0}]})]})
|
||||||
|
clojure.pprint/pprint))]
|
||||||
|
|
||||||
|
(testing "should set running-balance on ledger entries missing them"
|
||||||
|
|
||||||
|
(sut/refresh-running-balance-cache)
|
||||||
|
(println (d/pull (d/db conn) '[*] line-1-1))
|
||||||
|
|
||||||
|
(is (= [-10.0 -60.0 -210.0]
|
||||||
|
(map #(pull-attr (d/db conn) :journal-entry-line/running-balance %) [line-1-1 line-2-1 line-3-1
|
||||||
|
])))
|
||||||
|
(is (= [10.0 60.0 210.0]
|
||||||
|
(map #(pull-attr (d/db conn) :journal-entry-line/running-balance %) [line-1-2 line-2-2 line-3-2]))))
|
||||||
|
|
||||||
|
(testing "should recompute if the data is out of date"
|
||||||
|
|
||||||
|
(d/transact conn
|
||||||
|
{:tx-data
|
||||||
|
[{:db/id line-1-1
|
||||||
|
:journal-entry-line/dirty true
|
||||||
|
:journal-entry-line/running-balance 123810.23}]})
|
||||||
|
(sut/refresh-running-balance-cache)
|
||||||
|
|
||||||
|
(is (= [-10.0 -60.0 -210.0]
|
||||||
|
(map #(pull-attr (d/db conn) :journal-entry-line/running-balance %) [line-1-1 line-2-1 line-3-1]))))
|
||||||
|
|
||||||
|
(testing "should recompute every entry after the out of date one"
|
||||||
|
|
||||||
|
(d/transact conn
|
||||||
|
{:tx-data
|
||||||
|
[{:db/id line-1-1
|
||||||
|
:journal-entry-line/dirty true
|
||||||
|
:journal-entry-line/debit 70.0}]})
|
||||||
|
(sut/refresh-running-balance-cache)
|
||||||
|
(is (= [-70.0 -120.0 -270.0]
|
||||||
|
(map #(pull-attr (d/db conn) :journal-entry-line/running-balance %) [line-1-1 line-2-1 line-3-1]))))
|
||||||
|
(testing "should not recompute entries that aren't dirty"
|
||||||
|
|
||||||
|
(d/transact conn
|
||||||
|
{:tx-data
|
||||||
|
[{:db/id line-1-1
|
||||||
|
:journal-entry-line/dirty false
|
||||||
|
:journal-entry-line/debit 90.0}]})
|
||||||
|
(sut/refresh-running-balance-cache)
|
||||||
|
(is (= [-70.0 -120.0 -270.0]
|
||||||
|
(map #(pull-attr (d/db conn) :journal-entry-line/running-balance %) [line-1-1 line-2-1 line-3-1])))
|
||||||
|
|
||||||
|
)
|
||||||
|
(testing "changing a ledger entry should mark the line items as dirty"
|
||||||
|
(d/transact conn
|
||||||
|
{:tx-data
|
||||||
|
[`(upsert-ledger ~{:db/id journal-entry-2
|
||||||
|
:journal-entry/date #inst "2022-01-02"
|
||||||
|
:journal-entry/client test-client
|
||||||
|
:journal-entry/external-id "2"
|
||||||
|
:journal-entry/line-items [{:db/id "line-2-1"
|
||||||
|
:journal-entry-line/account test-account-1
|
||||||
|
:journal-entry-line/location "A"
|
||||||
|
:journal-entry-line/debit 50.0}
|
||||||
|
{:db/id "line-2-2"
|
||||||
|
:journal-entry-line/account test-account-2
|
||||||
|
:journal-entry-line/location "A"
|
||||||
|
:journal-entry-line/credit 50.0}]})]})
|
||||||
|
(is (= [true true]
|
||||||
|
(->> (d/pull (d/db conn) '[{:journal-entry/line-items [:journal-entry-line/dirty]}] journal-entry-2)
|
||||||
|
(:journal-entry/line-items)
|
||||||
|
(map :journal-entry-line/dirty))))
|
||||||
|
(testing "should also mark the next entry as dirty, so that if a ledger entry is changed, the old accounts get updated"
|
||||||
|
(is (= [false false]
|
||||||
|
(->> (d/pull (d/db conn) '[{:journal-entry/line-items [:journal-entry-line/dirty]}] journal-entry-1)
|
||||||
|
(:journal-entry/line-items)
|
||||||
|
(map :journal-entry-line/dirty))))
|
||||||
|
(is (= [true true]
|
||||||
|
(->> (d/pull (d/db conn) '[{:journal-entry/line-items [:journal-entry-line/dirty]}] journal-entry-3)
|
||||||
|
(:journal-entry/line-items)
|
||||||
|
(map :journal-entry-line/dirty))))))))
|
||||||
@@ -15,8 +15,10 @@ Fix searching
|
|||||||
* indexing should happen more regularly, and just look for changes since last time it was run
|
* indexing should happen more regularly, and just look for changes since last time it was run
|
||||||
|
|
||||||
Running Balance Cache
|
Running Balance Cache
|
||||||
* much simpler now, just make it handle reverts (see TODO)
|
* Add tests for upsert-ledger
|
||||||
** when a journal entry gets reset, you have to recalculate running balance. Could just make it do this globally by marking the earliest journal entry
|
* try again to see if we can get upsert-ledger into the same transaction, making it all or nothing
|
||||||
|
* make rest of rebuilding the cache use new index
|
||||||
|
* ensure somehow that the index is always right
|
||||||
|
|
||||||
|
|
||||||
Address memory
|
Address memory
|
||||||
|
|||||||
Reference in New Issue
Block a user