diff --git a/iol_ion/src/iol_ion/tx.clj b/iol_ion/src/iol_ion/tx.clj index 69f7fbb3..1d90c284 100644 --- a/iol_ion/src/iol_ion/tx.clj +++ b/iol_ion/src/iol_ion/tx.clj @@ -38,21 +38,20 @@ (defn regenerate-literals [] (require 'com.github.ivarref.gen-fn) - (spit - "resources/functions.edn" - (let [datomic-fn @(resolve 'com.github.ivarref.gen-fn/datomic-fn)] - [(datomic-fn :pay #'iol-ion.tx.pay/pay) - (datomic-fn :plus #'iol-ion.tx.plus/plus) - (datomic-fn :propose-invoice #'iol-ion.tx.propose-invoice/propose-invoice) - (datomic-fn :reset-rels #'iol-ion.tx.reset-rels/reset-rels) - (datomic-fn :reset-scalars #'iol-ion.tx.reset-scalars/reset-scalars) - (datomic-fn :upsert-entity #'iol-ion.tx.upsert-entity/upsert-entity) - (datomic-fn :upsert-invoice #'iol-ion.tx.upsert-invoice/upsert-invoice) - (datomic-fn :upsert-ledger #'iol-ion.tx.upsert-ledger/upsert-ledger) - (datomic-fn :upsert-transaction #'iol-ion.tx.upsert-transaction/upsert-transaction) - (datomic-fn :upsert-sales-summary #'iol-ion.tx.upsert-sales-summary-ledger/upsert-sales-summary)]))) + (spit + "resources/functions.edn" + (let [datomic-fn @(resolve 'com.github.ivarref.gen-fn/datomic-fn)] + [(datomic-fn :pay #'iol-ion.tx.pay/pay) + (datomic-fn :plus #'iol-ion.tx.plus/plus) + (datomic-fn :propose-invoice #'iol-ion.tx.propose-invoice/propose-invoice) + (datomic-fn :reset-rels #'iol-ion.tx.reset-rels/reset-rels) + (datomic-fn :reset-scalars #'iol-ion.tx.reset-scalars/reset-scalars) + (datomic-fn :upsert-entity #'iol-ion.tx.upsert-entity/upsert-entity) + (datomic-fn :upsert-invoice #'iol-ion.tx.upsert-invoice/upsert-invoice) + (datomic-fn :upsert-ledger #'iol-ion.tx.upsert-ledger/upsert-ledger) + (datomic-fn :upsert-transaction #'iol-ion.tx.upsert-transaction/upsert-transaction) + (datomic-fn :upsert-sales-summary #'iol-ion.tx.upsert-sales-summary-ledger/upsert-sales-summary)]))) (comment (regenerate-literals) - - (auto-ap.datomic/install-functions)) \ No newline at end of file + (auto-ap.datomic/install-functions)) diff --git a/iol_ion/src/iol_ion/tx/upsert_sales_summary_ledger.clj b/iol_ion/src/iol_ion/tx/upsert_sales_summary_ledger.clj index 26db4a9f..3db00f80 100644 --- a/iol_ion/src/iol_ion/tx/upsert_sales_summary_ledger.clj +++ b/iol_ion/src/iol_ion/tx/upsert_sales_summary_ledger.clj @@ -1,7 +1,5 @@ (ns iol-ion.tx.upsert-sales-summary-ledger - (:require [datomic.api :as dc] - [iol-ion.tx.upsert-entity :as upsert-entity] - [iol-ion.tx.upsert-ledger :as upsert-ledger])) + (:require [datomic.api :as dc])) (defn summary->journal-entry [db summary-id] (let [summary (dc/pull db '[:sales-summary/client @@ -9,7 +7,7 @@ {:sales-summary/items [:sales-summary-item/category :ledger-mapped/account :ledger-mapped/amount - :ledger-mapped/ledger-side]}] + {:ledger-mapped/ledger-side [:db/ident]}]}] summary-id) items (:sales-summary/items summary) aggregated (->> items @@ -18,18 +16,22 @@ (map (fn [[account acc-items]] (reduce (fn [m item] - (update m (:ledger-mapped/ledger-side item) (fnil + 0.0) (:ledger-mapped/amount item 0.0))) + (update m (:db/ident (:ledger-mapped/ledger-side item)) (fnil + 0.0) (:ledger-mapped/amount item 0.0))) {:account account} acc-items)))) + _ (clojure.pprint/pprint aggregated) line-items (mapv (fn [{:keys [account] :as m}] - (cond-> {:db/id (str (java.util.UUID/randomUUID)) - :journal-entry-line/account account - :journal-entry-line/location "A"} - (get m :ledger-side/debit) (assoc :journal-entry-line/debit (get m :ledger-side/debit)) - (get m :ledger-side/credit) (assoc :journal-entry-line/credit (get m :ledger-side/credit)))) + (cond-> {:db/id (str (java.util.UUID/randomUUID)) + :journal-entry-line/account account + :journal-entry-line/location "A"} + (get m :ledger-side/debit) (assoc :journal-entry-line/debit (get m :ledger-side/debit)) + (get m :ledger-side/credit) (assoc :journal-entry-line/credit (get m :ledger-side/credit)))) aggregated) + total-debits (reduce + 0.0 (map #(get % :ledger-side/debit 0.0) aggregated)) - total-credits (reduce + 0.0 (map #(get % :ledger-side/credit 0.0) aggregated))] + total-credits (reduce + 0.0 (map #(get % :ledger-side/credit 0.0) aggregated)) +_ (clojure.pprint/pprint [total-debits total-credits]) + ] (when (and (seq line-items) (= (Math/round (* 1000 total-debits)) (Math/round (* 1000 total-credits)))) @@ -40,6 +42,14 @@ :journal-entry/amount total-debits :journal-entry/line-items line-items}))) +(defn current-date [db] + (let [last-tx (dc/t->tx (dc/basis-t db)) + [[date]] (seq (dc/q '[:find ?ti :in $ ?tx + :where [?tx :db/txInstant ?ti]] + db + last-tx))] + date)) + (defn upsert-sales-summary [db summary] (let [upserted-summary [[:upsert-entity summary]] db-after (-> (dc/with db upserted-summary) :db-after) @@ -48,13 +58,13 @@ :sales-summary/client :db/id) journal-entry (summary->journal-entry db-after summary-id)] - (into upserted-summary + upserted-summary + #_(into upserted-summary (if journal-entry [[:upsert-ledger journal-entry]] - (let [existing-je-id (ffirst (dc/q '[:find ?je . :in $ ?ss - :where [?je :journal-entry/original-entity ?ss]] - db-after summary-id))] - (concat - (when existing-je-id [[:db/retractEntity existing-je-id]]) - (when client-id [{:db/id client-id - :client/ledger-last-change (upsert-ledger/current-date db)}]))))))) + (concat + [[:db/retractEntity [:journal-entry/original-entity (:db/id summary)]]] + + + (when client-id [{:db/id client-id + :client/ledger-last-change (current-date db)}])))))) diff --git a/resources/functions.edn b/resources/functions.edn index efe3cac1..776cf345 100644 --- a/resources/functions.edn +++ b/resources/functions.edn @@ -1 +1 @@ -[#:db{:ident :pay, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db e amount], :code "(let [] (letfn [] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [e (genfn-coerce-arg e) amount (genfn-coerce-arg amount)] (do (let [current-outstanding-balance (-> (dc/pull db [:invoice/outstanding-balance] e) :invoice/outstanding-balance) new-outstanding-balance (- current-outstanding-balance amount)] [[:upsert-invoice {:invoice/status (if (< -1.0E-4 new-outstanding-balance 1.0E-4) :invoice-status/paid :invoice-status/unpaid), :db/id e, :invoice/outstanding-balance new-outstanding-balance}]]))))))"}} #:db{:ident :plus, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db e a amount], :code "(let [] (letfn [] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [e (genfn-coerce-arg e) a (genfn-coerce-arg a) amount (genfn-coerce-arg amount)] (do [[:db/add e a (-> (dc/pull db [a] e) a (+ amount))]])))))"}} #:db{:ident :propose-invoice, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db invoice], :code "(let [] (letfn [] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [invoice (genfn-coerce-arg invoice)] (do (let [existing? (boolean (seq (dc/q (quote [: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)))) [locked-until] (first (dc/q (quote [:find ?locked-until :in $ ?c :where [?c :client/locked-until ?locked-until]]) db (:invoice/client invoice))) is-locked? (cond (not locked-until) false (not (:invoice/date invoice)) true (< (compare (:invoice/date invoice) locked-until) 0) true :else false)] (if (or existing? is-locked?) [] [[:upsert-invoice invoice]])))))))"}} #:db{:ident :reset-rels, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db e a vs], :code "(let [] (letfn [] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [e (genfn-coerce-arg e) a (genfn-coerce-arg a) vs (genfn-coerce-arg vs)] (do (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 (quote [: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 (map (fn [i] [:upsert-entity i]) vs)))))))))"}} #:db{:ident :reset-scalars, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db e a vs], :code "(let [] (letfn [] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [e (genfn-coerce-arg e) a (genfn-coerce-arg a) vs (genfn-coerce-arg vs)] (do (let [extant (when-not (string? e) (->> (dc/q (quote [: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)))))))))"}} #:db{:ident :upsert-entity, :fn #db/fn{:lang :clojure, :imports [[java.util UUID]], :requires [[datomic.api :as dc]], :params [db entity], :code "(let [] (letfn [(-random-tempid [] (do (str (UUID/randomUUID)))) (-by [f fv xs] (do (reduce (fn* [p1__143570# p2__143571#] (assoc p1__143570# (f p2__143571#) (fv p2__143571#))) {} xs))) (-pull-many [db read ids] (do (->> (dc/q (quote [:find (pull ?e r) :in $ [?e ...] r]) db ids read) (map first))))] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [entity (genfn-coerce-arg entity)] (do (when-not (or (:db/id entity) (:db/ident entity)) (datomic.api/cancel #:cognitect.anomalies{:message (str \"Cannot upsert without :db/id or :db/ident, \" entity), :category :cognitect.anomalies/incorrect})) (let [e (or (:db/id entity) (:db/ident entity)) is-new? (string? e) extant-entity (when-not is-new? (dc/pull db (keys entity) (or (:db/id entity) (:db/ident entity)))) ident->value-type (-by :db/ident (comp :db/ident :db/valueType) (-pull-many db [#:db{:valueType [:db/ident]} :db/ident] (keys entity))) ident->cardinality (-by :db/ident (comp :db/ident :db/cardinality) (-pull-many db [#:db{:cardinality [:db/ident]} :db/ident] (keys entity))) ops (->> entity (reduce (fn [ops [a v]] (cond (= :db/id a) ops (= :db/ident 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)))) (if (= :db.cardinality/many (ident->cardinality a)) (into ops (map (fn [v] [:db/retract e a (cond-> v (:db/id v) :db/id)]) (a extant-entity))) (conj ops [:db/retract e a (cond-> (a extant-entity) (:db/id (a extant-entity)) :db/id)])) (nil? v) ops (and (sequential? v) (= :db.type/tuple (ident->value-type a)) (not (= :db.cardinality/many (ident->cardinality a)))) (conj ops [:db/add e a v]) (and (sequential? v) (= :db.type/ref (ident->value-type a)) (every? map? v)) (into ops [[:reset-rels e a v]]) (= :db.cardinality/many (ident->cardinality a)) (into ops [[:reset-scalars e a v]]) (and (sequential? v) (not= :db.type/ref (ident->value-type a))) (into ops [[:reset-scalars 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 (assoc v :db/id id)]]))) :else (conj ops [:db/add e a v]))) []))] ops))))))"}} #:db{:ident :upsert-invoice, :fn #db/fn{:lang :clojure, :imports [[java.util Date]], :requires [[datomic.api :as dc]], :params [db invoice], :code "(let [] (letfn [(-remove-nils [m] (do (let [result (reduce-kv (fn [m k v] (if (not (nil? v)) (assoc m k v) m)) {} m)] (if (seq result) result nil)))) (invoice->journal-entry [db invoice-id raw-invoice-id] (do (let [entity (dc/pull db (quote [:invoice/total :invoice/exclude-from-ledger :invoice/outstanding-balance :invoice/date #:invoice{:client [:db/id :client/code], :status [:db/ident], :import-status [:db/ident], :vendor [:db/id :vendor/name], :payment [:db/id #:payment{:status [:db/ident]}], :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{:date (:invoice/date entity), :original-entity raw-invoice-id, :client (:db/id (:invoice/client entity)), :line-items (into [(cond-> {:journal-entry-line/account :account/accounts-payable, :db/id (str raw-invoice-id \"-\" 0), :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-> {:journal-entry-line/account (:db/id (:invoice-expense-account/account ea)), :db/id (str raw-invoice-id \"-\" (inc i)), :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))), :source \"invoice\", :cleared (and (< (:invoice/outstanding-balance entity) 0.01) (every? (fn* [p1__143573#] (= :payment-status/cleared (:payment/status p1__143573#))) (:invoice/payments entity))), :amount (Math/abs (:invoice/total entity)), :vendor (:db/id (:invoice/vendor entity))}))))) (current-date [db] (do (let [last-tx (dc/t->tx (dc/basis-t db)) [[date]] (seq (dc/q (quote [:find ?ti :in $ ?tx :where [?tx :db/txInstant ?ti]]) db last-tx))] date)))] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [invoice (genfn-coerce-arg invoice)] (do (let [upserted-entity [[:upsert-entity invoice]] with-invoice (dc/with db 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)) client-id (-> (dc/pull (:db-after with-invoice) [#:invoice{:client [:db/id]}] invoice-id) :invoice/client :db/id)] (into upserted-entity (if journal-entry [[:upsert-ledger journal-entry]] [[:db/retractEntity [:journal-entry/original-entity (:db/id invoice)]] {:client/ledger-last-change (current-date db), :db/id client-id}]))))))))"}} #:db{:ident :upsert-ledger, :fn #db/fn{:lang :clojure, :imports [[java.util UUID Date]], :requires [[datomic.api :as dc]], :params [db ledger-entry], :code "(let [extant-read (quote [: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]}])] (letfn [(-random-tempid [] (do (dc/tempid :db.part/user))) (get-line-items-after [db journal-entry] (do (for [jel (:journal-entry/line-items journal-entry) next-jel (->> (dc/index-pull db {:selector [:db/id :journal-entry-line/client+account+location+date], :index :avet, :start [:journal-entry-line/client+account+location+date (:journal-entry-line/client+account+location+date jel) (:db/id jel)]}) (take-while (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))))) (take 2)) :when next-jel] (:db/id next-jel)))) (current-date [db] (do (let [last-tx (dc/t->tx (dc/basis-t db)) [[date]] (seq (dc/q (quote [:find ?ti :in $ ?tx :where [?tx :db/txInstant ?ti]]) db last-tx))] date))) (calc-client+account+location+date [je jel] (do [(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)]))] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [ledger-entry (genfn-coerce-arg ledger-entry)] (do (assert (:journal-entry/date ledger-entry) (format \"Must at least provide date when updating ledger: %s\" (pr-str ledger-entry))) (assert (:journal-entry/client ledger-entry) \"Must at least provide client 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])))] (cond-> [[:upsert-entity (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 (fn* [p1__143577#] (-> p1__143577# (assoc :journal-entry-line/date (:journal-entry/date ledger-entry)) (assoc :journal-entry-line/client (:journal-entry/client ledger-entry)))) lis)))))] {:client/ledger-last-change (current-date db), :db/id (:journal-entry/client ledger-entry)}])))))))"}} #:db{:ident :upsert-transaction, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db transaction], :code "(let [my-transaction {:transaction/bank-account 17592232681223, :transaction/date (read-string \"#inst \\\"2024-02-24T08:00:00.000-00:00\\\"\"), :transaction/matched-rule 17592233159891, :transaction/client 17592232577980, :transaction/status \"POSTED\", :transaction/plaid-merchant {:plaid-merchant/name \"Rotten Robbie\", :db/id \"b2776792-9e2b-46e8-a9c8-bf80abea359e\"}, :db/id \"ac2efd80-bb03-48b2-b0d0-6b47a5c119dc\", :transaction/id \"11a4a13e713d63f476009027e9a53e217e13d0192a37df8ab96c0eed4bdbe996\", :transaction/description-original \"Rotten Robbie #03\", :transaction/approval-status #:db{:id 17592231963877, :ident :transaction-approval-status/approved}, :transaction/amount -84.43, :transaction/accounts [{:transaction-account/account 17592231963549, :db/id \"c402c7b3-c11b-484b-b670-bd48f79a3e5f\", :transaction-account/location \"CB\", :transaction-account/amount 84.43}], :transaction/raw-id \"gQypbv5946F08op74wZmidDg8qD8Q1fM6gEBP\", :transaction/vendor 17592232627053} my-journal #:journal-entry{:vendor 17592232627053, :amount 84.43, :date (read-string \"#inst \\\"2024-02-24T08:00:00.000-00:00\\\"\"), :alternate-description \"Rotten Robbie #03\", :original-entity \"ac2efd80-bb03-48b2-b0d0-6b47a5c119dc\", :client 17592232577980, :source \"transaction\", :line-items [{:journal-entry-line/credit 84.43, :journal-entry-line/account 17592232681223, :db/id \"ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-0\", :journal-entry-line/location \"A\"} {:journal-entry-line/account 17592231963549, :db/id \"ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-1\", :journal-entry-line/debit 84.43, :journal-entry-line/location \"CB\"} {:journal-entry-line/account 17592231963549, :db/id \"ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-2\", :journal-entry-line/debit 84.43, :journal-entry-line/location \"CB\"}], :cleared true}] (letfn [(-remove-nils [m] (do (let [result (reduce-kv (fn [m k v] (if (not (nil? v)) (assoc m k v) m)) {} m)] (if (seq result) result nil)))) (transaction->journal-entry [db transaction-id raw-transaction-id] (do (let [entity (dc/pull db [:transaction/client :transaction/date :transaction/description-original :db/id :transaction/vendor :transaction/amount :transaction/cleared-against #:transaction{:bank-account [:db/id #:bank-account{:type [:db/ident]}], :approval-status [:db/ident], :accounts [:transaction-account/account :transaction-account/location :transaction-account/amount]}] transaction-id) decreasing? (< (or (:transaction/amount entity) 0.0) 0.0) credit-from-bank? decreasing? debit-from-bank? (not decreasing?)] (when (and (not (= :transaction-approval-status/excluded (:db/ident (:transaction/approval-status entity)))) (not (= :transaction-approval-status/suppressed (:db/ident (:transaction/approval-status entity)))) (:transaction/amount entity) (not (< -0.001 (:transaction/amount entity) 0.001))) (-remove-nils #:journal-entry{:vendor (:db/id (:transaction/vendor entity)), :amount (Math/abs (:transaction/amount entity)), :date (:transaction/date entity), :alternate-description (:transaction/description-original entity), :original-entity raw-transaction-id, :client (:db/id (:transaction/client entity)), :cleared-against (:transaction/cleared-against entity), :source \"transaction\", :line-items (into [(-remove-nils {:journal-entry-line/credit (when credit-from-bank? (Math/abs (:transaction/amount entity))), :journal-entry-line/account (:db/id (:transaction/bank-account entity)), :db/id (str raw-transaction-id \"-\" 0), :journal-entry-line/debit (when debit-from-bank? (Math/abs (:transaction/amount entity))), :journal-entry-line/location \"A\"})] (map-indexed (fn [i a] (-remove-nils {:journal-entry-line/credit (when debit-from-bank? (Math/abs (:transaction-account/amount a))), :journal-entry-line/account (:db/id (:transaction-account/account a)), :db/id (str raw-transaction-id \"-\" (inc i)), :journal-entry-line/debit (when credit-from-bank? (Math/abs (:transaction-account/amount a))), :journal-entry-line/location (:transaction-account/location a)})) (if (seq (:transaction/accounts entity)) (:transaction/accounts entity) [#:transaction-account{:amount (:transaction/amount entity)}]))), :cleared true})))))] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [transaction (genfn-coerce-arg transaction)] (do (let [upserted-entity [[:upsert-entity (dissoc transaction :transaction/payment :import-batch/_entry)]] with-transaction (dc/with db upserted-entity) transaction-id (or (-> with-transaction :tempids (get (:db/id transaction))) (:db/id transaction)) journal-entry (transaction->journal-entry (:db-after with-transaction) transaction-id (:db/id transaction))] (into [[:upsert-entity transaction]] (if journal-entry [[:upsert-ledger journal-entry]] [[:db/retractEntity [:journal-entry/original-entity (:db/id transaction)]]]))))))))"}}] \ No newline at end of file +[#:db{:ident :pay, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db e amount], :code "(let [] (letfn [] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [e (genfn-coerce-arg e) amount (genfn-coerce-arg amount)] (do (let [current-outstanding-balance (-> (dc/pull db [:invoice/outstanding-balance] e) :invoice/outstanding-balance) new-outstanding-balance (- current-outstanding-balance amount)] [[:upsert-invoice {:invoice/status (if (< -1.0E-4 new-outstanding-balance 1.0E-4) :invoice-status/paid :invoice-status/unpaid), :db/id e, :invoice/outstanding-balance new-outstanding-balance}]]))))))"}} #:db{:ident :plus, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db e a amount], :code "(let [] (letfn [] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [e (genfn-coerce-arg e) a (genfn-coerce-arg a) amount (genfn-coerce-arg amount)] (do [[:db/add e a (-> (dc/pull db [a] e) a (+ amount))]])))))"}} #:db{:ident :propose-invoice, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db invoice], :code "(let [] (letfn [] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [invoice (genfn-coerce-arg invoice)] (do (let [existing? (boolean (seq (dc/q (quote [: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)))) [locked-until] (first (dc/q (quote [:find ?locked-until :in $ ?c :where [?c :client/locked-until ?locked-until]]) db (:invoice/client invoice))) is-locked? (cond (not locked-until) false (not (:invoice/date invoice)) true (< (compare (:invoice/date invoice) locked-until) 0) true :else false)] (if (or existing? is-locked?) [] [[:upsert-invoice invoice]])))))))"}} #:db{:ident :reset-rels, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db e a vs], :code "(let [] (letfn [] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [e (genfn-coerce-arg e) a (genfn-coerce-arg a) vs (genfn-coerce-arg vs)] (do (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 (quote [: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 (map (fn [i] [:upsert-entity i]) vs)))))))))"}} #:db{:ident :reset-scalars, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db e a vs], :code "(let [] (letfn [] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [e (genfn-coerce-arg e) a (genfn-coerce-arg a) vs (genfn-coerce-arg vs)] (do (let [extant (when-not (string? e) (->> (dc/q (quote [: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)))))))))"}} #:db{:ident :upsert-entity, :fn #db/fn{:lang :clojure, :imports [[java.util UUID]], :requires [[datomic.api :as dc]], :params [db entity], :code "(let [] (letfn [(-random-tempid [] (do (str (UUID/randomUUID)))) (-by [f fv xs] (do (reduce (fn* [p1__112282# p2__112283#] (assoc p1__112282# (f p2__112283#) (fv p2__112283#))) {} xs))) (-pull-many [db read ids] (do (->> (dc/q (quote [:find (pull ?e r) :in $ [?e ...] r]) db ids read) (map first))))] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [entity (genfn-coerce-arg entity)] (do (when-not (or (:db/id entity) (:db/ident entity)) (datomic.api/cancel #:cognitect.anomalies{:message (str \"Cannot upsert without :db/id or :db/ident, \" entity), :category :cognitect.anomalies/incorrect})) (let [e (or (:db/id entity) (:db/ident entity)) is-new? (string? e) extant-entity (when-not is-new? (dc/pull db (keys entity) (or (:db/id entity) (:db/ident entity)))) ident->value-type (-by :db/ident (comp :db/ident :db/valueType) (-pull-many db [#:db{:valueType [:db/ident]} :db/ident] (keys entity))) ident->cardinality (-by :db/ident (comp :db/ident :db/cardinality) (-pull-many db [#:db{:cardinality [:db/ident]} :db/ident] (keys entity))) ops (->> entity (reduce (fn [ops [a v]] (cond (= :db/id a) ops (= :db/ident 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)))) (if (= :db.cardinality/many (ident->cardinality a)) (into ops (map (fn [v] [:db/retract e a (cond-> v (:db/id v) :db/id)]) (a extant-entity))) (conj ops [:db/retract e a (cond-> (a extant-entity) (:db/id (a extant-entity)) :db/id)])) (nil? v) ops (and (sequential? v) (= :db.type/tuple (ident->value-type a)) (not (= :db.cardinality/many (ident->cardinality a)))) (conj ops [:db/add e a v]) (and (sequential? v) (= :db.type/ref (ident->value-type a)) (every? map? v)) (into ops [[:reset-rels e a v]]) (= :db.cardinality/many (ident->cardinality a)) (into ops [[:reset-scalars e a v]]) (and (sequential? v) (not= :db.type/ref (ident->value-type a))) (into ops [[:reset-scalars 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 (assoc v :db/id id)]]))) :else (conj ops [:db/add e a v]))) []))] ops))))))"}} #:db{:ident :upsert-invoice, :fn #db/fn{:lang :clojure, :imports [[java.util Date]], :requires [[datomic.api :as dc]], :params [db invoice], :code "(let [] (letfn [(-remove-nils [m] (do (let [result (reduce-kv (fn [m k v] (if (not (nil? v)) (assoc m k v) m)) {} m)] (if (seq result) result nil)))) (invoice->journal-entry [db invoice-id raw-invoice-id] (do (let [entity (dc/pull db (quote [:invoice/total :invoice/exclude-from-ledger :invoice/outstanding-balance :invoice/date #:invoice{:client [:db/id :client/code], :status [:db/ident], :import-status [:db/ident], :vendor [:db/id :vendor/name], :payment [:db/id #:payment{:status [:db/ident]}], :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{:date (:invoice/date entity), :original-entity raw-invoice-id, :client (:db/id (:invoice/client entity)), :line-items (into [(cond-> {:journal-entry-line/account :account/accounts-payable, :db/id (str raw-invoice-id \"-\" 0), :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-> {:journal-entry-line/account (:db/id (:invoice-expense-account/account ea)), :db/id (str raw-invoice-id \"-\" (inc i)), :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))), :source \"invoice\", :cleared (and (< (:invoice/outstanding-balance entity) 0.01) (every? (fn* [p1__112285#] (= :payment-status/cleared (:payment/status p1__112285#))) (:invoice/payments entity))), :amount (Math/abs (:invoice/total entity)), :vendor (:db/id (:invoice/vendor entity))}))))) (current-date [db] (do (let [last-tx (dc/t->tx (dc/basis-t db)) [[date]] (seq (dc/q (quote [:find ?ti :in $ ?tx :where [?tx :db/txInstant ?ti]]) db last-tx))] date)))] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [invoice (genfn-coerce-arg invoice)] (do (let [upserted-entity [[:upsert-entity invoice]] with-invoice (dc/with db 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)) client-id (-> (dc/pull (:db-after with-invoice) [#:invoice{:client [:db/id]}] invoice-id) :invoice/client :db/id)] (into upserted-entity (if journal-entry [[:upsert-ledger journal-entry]] [[:db/retractEntity [:journal-entry/original-entity (:db/id invoice)]] {:client/ledger-last-change (current-date db), :db/id client-id}]))))))))"}} #:db{:ident :upsert-ledger, :fn #db/fn{:lang :clojure, :imports [[java.util UUID Date]], :requires [[datomic.api :as dc]], :params [db ledger-entry], :code "(let [extant-read (quote [: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]}])] (letfn [(-random-tempid [] (do (dc/tempid :db.part/user))) (get-line-items-after [db journal-entry] (do (for [jel (:journal-entry/line-items journal-entry) next-jel (->> (dc/index-pull db {:selector [:db/id :journal-entry-line/client+account+location+date], :index :avet, :start [:journal-entry-line/client+account+location+date (:journal-entry-line/client+account+location+date jel) (:db/id jel)]}) (take-while (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))))) (take 2)) :when next-jel] (:db/id next-jel)))) (current-date [db] (do (let [last-tx (dc/t->tx (dc/basis-t db)) [[date]] (seq (dc/q (quote [:find ?ti :in $ ?tx :where [?tx :db/txInstant ?ti]]) db last-tx))] date))) (calc-client+account+location+date [je jel] (do [(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)]))] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [ledger-entry (genfn-coerce-arg ledger-entry)] (do (assert (:journal-entry/date ledger-entry) (format \"Must at least provide date when updating ledger: %s\" (pr-str ledger-entry))) (assert (:journal-entry/client ledger-entry) \"Must at least provide client 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])))] (cond-> [[:upsert-entity (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 (fn* [p1__112289#] (-> p1__112289# (assoc :journal-entry-line/date (:journal-entry/date ledger-entry)) (assoc :journal-entry-line/client (:journal-entry/client ledger-entry)))) lis)))))] {:client/ledger-last-change (current-date db), :db/id (:journal-entry/client ledger-entry)}])))))))"}} #:db{:ident :upsert-transaction, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db transaction], :code "(let [my-transaction {:transaction/bank-account 17592232681223, :transaction/date (read-string \"#inst \\\"2024-02-24T08:00:00.000-00:00\\\"\"), :transaction/matched-rule 17592233159891, :transaction/client 17592232577980, :transaction/status \"POSTED\", :transaction/plaid-merchant {:plaid-merchant/name \"Rotten Robbie\", :db/id \"b2776792-9e2b-46e8-a9c8-bf80abea359e\"}, :db/id \"ac2efd80-bb03-48b2-b0d0-6b47a5c119dc\", :transaction/id \"11a4a13e713d63f476009027e9a53e217e13d0192a37df8ab96c0eed4bdbe996\", :transaction/description-original \"Rotten Robbie #03\", :transaction/approval-status #:db{:id 17592231963877, :ident :transaction-approval-status/approved}, :transaction/amount -84.43, :transaction/accounts [{:transaction-account/account 17592231963549, :db/id \"c402c7b3-c11b-484b-b670-bd48f79a3e5f\", :transaction-account/location \"CB\", :transaction-account/amount 84.43}], :transaction/raw-id \"gQypbv5946F08op74wZmidDg8qD8Q1fM6gEBP\", :transaction/vendor 17592232627053} my-journal #:journal-entry{:vendor 17592232627053, :amount 84.43, :date (read-string \"#inst \\\"2024-02-24T08:00:00.000-00:00\\\"\"), :alternate-description \"Rotten Robbie #03\", :original-entity \"ac2efd80-bb03-48b2-b0d0-6b47a5c119dc\", :client 17592232577980, :source \"transaction\", :line-items [{:journal-entry-line/credit 84.43, :journal-entry-line/account 17592232681223, :db/id \"ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-0\", :journal-entry-line/location \"A\"} {:journal-entry-line/account 17592231963549, :db/id \"ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-1\", :journal-entry-line/debit 84.43, :journal-entry-line/location \"CB\"} {:journal-entry-line/account 17592231963549, :db/id \"ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-2\", :journal-entry-line/debit 84.43, :journal-entry-line/location \"CB\"}], :cleared true}] (letfn [(-remove-nils [m] (do (let [result (reduce-kv (fn [m k v] (if (not (nil? v)) (assoc m k v) m)) {} m)] (if (seq result) result nil)))) (transaction->journal-entry [db transaction-id raw-transaction-id] (do (let [entity (dc/pull db [:transaction/client :transaction/date :transaction/description-original :db/id :transaction/vendor :transaction/amount :transaction/cleared-against #:transaction{:bank-account [:db/id #:bank-account{:type [:db/ident]}], :approval-status [:db/ident], :accounts [:transaction-account/account :transaction-account/location :transaction-account/amount]}] transaction-id) decreasing? (< (or (:transaction/amount entity) 0.0) 0.0) credit-from-bank? decreasing? debit-from-bank? (not decreasing?)] (when (and (not (= :transaction-approval-status/excluded (:db/ident (:transaction/approval-status entity)))) (not (= :transaction-approval-status/suppressed (:db/ident (:transaction/approval-status entity)))) (:transaction/amount entity) (not (< -0.001 (:transaction/amount entity) 0.001))) (-remove-nils #:journal-entry{:vendor (:db/id (:transaction/vendor entity)), :amount (Math/abs (:transaction/amount entity)), :date (:transaction/date entity), :alternate-description (:transaction/description-original entity), :original-entity raw-transaction-id, :client (:db/id (:transaction/client entity)), :cleared-against (:transaction/cleared-against entity), :source \"transaction\", :line-items (into [(-remove-nils {:journal-entry-line/credit (when credit-from-bank? (Math/abs (:transaction/amount entity))), :journal-entry-line/account (:db/id (:transaction/bank-account entity)), :db/id (str raw-transaction-id \"-\" 0), :journal-entry-line/debit (when debit-from-bank? (Math/abs (:transaction/amount entity))), :journal-entry-line/location \"A\"})] (map-indexed (fn [i a] (-remove-nils {:journal-entry-line/credit (when debit-from-bank? (Math/abs (:transaction-account/amount a))), :journal-entry-line/account (:db/id (:transaction-account/account a)), :db/id (str raw-transaction-id \"-\" (inc i)), :journal-entry-line/debit (when credit-from-bank? (Math/abs (:transaction-account/amount a))), :journal-entry-line/location (:transaction-account/location a)})) (if (seq (:transaction/accounts entity)) (:transaction/accounts entity) [#:transaction-account{:amount (:transaction/amount entity)}]))), :cleared true})))))] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [transaction (genfn-coerce-arg transaction)] (do (let [upserted-entity [[:upsert-entity (dissoc transaction :transaction/payment :import-batch/_entry)]] with-transaction (dc/with db upserted-entity) transaction-id (or (-> with-transaction :tempids (get (:db/id transaction))) (:db/id transaction)) journal-entry (transaction->journal-entry (:db-after with-transaction) transaction-id (:db/id transaction))] (into [[:upsert-entity transaction]] (if journal-entry [[:upsert-ledger journal-entry]] [[:db/retractEntity [:journal-entry/original-entity (:db/id transaction)]]]))))))))"}} #:db{:ident :upsert-sales-summary, :fn #db/fn{:lang :clojure, :imports [], :requires [[datomic.api :as dc]], :params [db summary], :code "(let [] (letfn [(summary->journal-entry [db summary-id] (do (let [summary (dc/pull db (quote [:sales-summary/client :sales-summary/date #:sales-summary{:items [:sales-summary-item/category :ledger-mapped/account :ledger-mapped/amount #:ledger-mapped{:ledger-side [:db/ident]}]}]) summary-id) items (:sales-summary/items summary) aggregated (->> items (filter :ledger-mapped/account) (group-by :ledger-mapped/account) (map (fn [[account acc-items]] (reduce (fn [m item] (update m (:db/ident (:ledger-mapped/ledger-side item)) (fnil + 0.0) (:ledger-mapped/amount item 0.0))) {:account account} acc-items)))) _ (clojure.pprint/pprint aggregated) line-items (mapv (fn [{:as m, :keys [account]}] (cond-> {:journal-entry-line/account account, :db/id (str (java.util.UUID/randomUUID)), :journal-entry-line/location \"A\"} (get m :ledger-side/debit) (assoc :journal-entry-line/debit (get m :ledger-side/debit)) (get m :ledger-side/credit) (assoc :journal-entry-line/credit (get m :ledger-side/credit)))) aggregated) total-debits (reduce + 0.0 (map (fn* [p1__112291#] (get p1__112291# :ledger-side/debit 0.0)) aggregated)) total-credits (reduce + 0.0 (map (fn* [p1__112293#] (get p1__112293# :ledger-side/credit 0.0)) aggregated)) _ (clojure.pprint/pprint [total-debits total-credits])] (when (and (seq line-items) (= (Math/round (* 1000 total-debits)) (Math/round (* 1000 total-credits)))) #:journal-entry{:date (:sales-summary/date summary), :original-entity summary-id, :client (:db/id (:sales-summary/client summary)), :line-items line-items, :source \"sales-summary\", :amount total-debits})))) (current-date [db] (do (let [last-tx (dc/t->tx (dc/basis-t db)) [[date]] (seq (dc/q (quote [:find ?ti :in $ ?tx :where [?tx :db/txInstant ?ti]]) db last-tx))] date)))] (let [genfn-coerce-arg (clojure.core/fn [x] (clojure.walk/prewalk (clojure.core/fn [e] (if (clojure.core/some? e) (do (clojure.core/when (clojure.core/instance? clojure.lang.PersistentTreeMap e) (throw (clojure.core/ex-info \"Using sorted-map will cause different types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/var? e) (throw (clojure.core/ex-info \"Using var does not work for remote transactor\" {:val e}))) (clojure.core/when (clojure.core/or (clojure.core/= clojure.lang.PersistentList$EmptyList (.getClass e)) (clojure.core/instance? clojure.lang.PersistentList e)) (throw (clojure.core/ex-info \"Using list will cause indistinguishable types in transactor for in-mem and remote\" {:val e}))) (clojure.core/when (clojure.core/instance? clojure.lang.PersistentQueue e) (throw (clojure.core/ex-info \"Using clojure.lang.PersistentQueue does not work for remote transactor\" {:val e}))) (clojure.core/cond (clojure.core/instance? java.util.HashSet e) (clojure.core/into #{} e) (clojure.core/and (clojure.core/instance? java.util.List e) (clojure.core/not (clojure.core/vector? e))) (clojure.core/vec e) :else e)) e)) x))] (let [summary (genfn-coerce-arg summary)] (do (let [upserted-summary [[:upsert-entity summary]] db-after (-> (dc/with db upserted-summary) :db-after) summary-id (:db/id summary) client-id (-> (dc/pull db-after [#:sales-summary{:client [:db/id]}] summary-id) :sales-summary/client :db/id) journal-entry (summary->journal-entry db-after summary-id)] upserted-summary))))))"}}] \ No newline at end of file diff --git a/src/clj/auto_ap/jobs/sales_summaries.clj b/src/clj/auto_ap/jobs/sales_summaries.clj index 0fcb23ed..e759078b 100644 --- a/src/clj/auto_ap/jobs/sales_summaries.clj +++ b/src/clj/auto_ap/jobs/sales_summaries.clj @@ -334,20 +334,35 @@ (apply mark-dirty [:client/code "NGDG"] (last-n-days 30)) - (apply mark-dirty [:client/code "NGPG"] (last-n-days 30)) + (dirty-sales-summaries [:client/code "NGWH"]) - (mark-all-dirty 50) + + (apply mark-dirty [:client/code "NGWH"] (last-n-days 5)) + + (iol-ion.tx.upsert-sales-summary-ledger/summary->journal-entry (dc/db conn) 17592314245819) + + + (iol-ion.tx.upsert-sales-summary-ledger/upsert-sales-summary (dc/db conn) {:db/id 17592314241429}) + + + + (mark-all-dirty 5) (delete-all) + (sales-summaries-v2) + + 1 + + (dc/q '[:find (pull ?sos [* {:sales-summary/sales-items [*]}]) :in $ :where [?sos :sales-summary/client [:client/code "NGHW"]] [?sos :sales-summary/date ?d] [(= ?d #inst "2024-04-10T00:00:00-07:00")]] (dc/db conn)) - + (dc/q '[:find ?n ?p2 (sum ?total) :with ?c :in $ [?clients ?start-date ?end-date] @@ -360,18 +375,21 @@ (dc/db conn) [[(auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGHW"])] #inst "2024-04-11T00:00:00-07:00" #inst "2024-04-11T00:00:00-07:00"]) - (dc/q '[:find ?n + (dc/q '[:find ?n :in $ [?clients ?start-date ?end-date] :where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]] [?e :sales-order/line-items ?li] - [?li :order-line-item/item-name ?n] ] + [?li :order-line-item/item-name ?n]] (dc/db conn) [[(auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGCL"])] #inst "2024-04-11T00:00:00-07:00" #inst "2024-04-24T00:00:00-07:00"]) - -@(dc/transact conn [{:db/id :sales-summary/total-tax :db/ident :sales-summary/total-tax-legacy} - {:db/id :sales-summary/total-tip :db/ident :sales-summary/total-tip-legacy}]) -(auto-ap.datomic/transact-schema conn) + @(dc/transact conn [{:db/id :sales-summary/total-tax :db/ident :sales-summary/total-tax-legacy} + {:db/id :sales-summary/total-tip :db/ident :sales-summary/total-tip-legacy}]) + + (auto-ap.datomic/transact-schema conn) + + + )