(ns auto-ap.ledger (:require [datomic.api :as d] [auto-ap.datomic.accounts :as a] [auto-ap.datomic :refer [uri remove-nils]])) (defn datums->impacted-entity [db [e changes]] (let [entity (d/pull db '[* {:invoice/_expense-accounts [:db/id] :transaction/_accounts [:db/id]}] e) namespaces (->> changes (map :a) (map namespace) set)] (cond (namespaces "invoice" ) [[:invoice e]] (namespaces "invoice-expense-account" ) [[:invoice (:db/id (:invoice/_expense-accounts entity))]] (namespaces "transaction-account" ) [[:transaction (:db/id (:transaction/_accounts entity))]] (namespaces "transaction" ) [[:transaction e]] :else nil))) (defn infer-entity [_ [_ changes]] (let [namespaces (->> changes (map :a) (map namespace) set)] (cond (namespaces "invoice" ) :invoice (namespaces "invoice-expense-account" ) :invoice-expense-account (namespaces "transaction-account" ) :transaction-account :else nil))) (defmulti entity-change->ledger (fn [_ [type]] type)) (defmethod entity-change->ledger :invoice [db [type id]] (let [entity (d/pull db ['* {:invoice/vendor '[*] :invoice/payment '[*]}] id)] (remove-nils {:journal-entry/source "invoice" :journal-entry/client (:db/id (:invoice/client entity)) :journal-entry/date (:invoice/date entity) :journal-entry/original-entity (:db/id entity) :journal-entry/vendor (:db/id (:invoice/vendor entity)) :journal-entry/amount (:invoice/total entity) :journal-entry/line-items (into [{:journal-entry-line/account (a/get-account-by-numeric-code-and-sets 2110 ["default"]) :journal-entry-line/location "A" :journal-entry-line/credit (:invoice/total entity)}] (map (fn [ea] (remove-nils {:journal-entry-line/account (:db/id (:invoice-expense-account/account ea)) :journal-entry-line/location (or (:invoice-expense-account/location ea) "HQ") ;; TODO? :journal-entry-line/debit (:invoice-expense-account/amount ea)})) (:invoice/expense-accounts entity))) :journal-entry/cleared (and (< (:invoice/outstanding-balance entity) 0.01) (every? #(= :payment-status/cleared (:payment/status %)) (:invoice/payments entity)) )}))) (defmethod entity-change->ledger :transaction [db [type id]] (let [entity (d/pull db ['* {:transaction/vendor '[*] :transaction/client '[*] :transaction/bank-account '[* {:bank-account/type [:db/ident]}] :transaction/accounts '[* {:transaction-account/account [*]}] }] id) bank-account-type (-> entity :transaction/bank-account :bank-account/type :db/ident) decreasing? (< (:transaction/amount entity) 0.0) credit-from-bank? decreasing? debit-from-bank? (not decreasing?)] (when-not (:transaction/exclude-from-ledger entity) (remove-nils {:journal-entry/source "transaction" :journal-entry/client (:db/id (:transaction/client entity)) :journal-entry/date (:transaction/date entity) :journal-entry/original-entity (:db/id entity) :journal-entry/vendor (:db/id (:transaction/vendor entity)) :journal-entry/amount (Math/abs (:transaction/amount entity)) :journal-entry/line-items (into [(remove-nils {:journal-entry-line/account (:db/id (:transaction/bank-account entity)) :journal-entry-line/location "A" :journal-entry-line/credit (when credit-from-bank? (Math/abs (:transaction/amount entity))) :journal-entry-line/debit (when debit-from-bank? (Math/abs (:transaction/amount entity)))}) ] (map (fn [a] (remove-nils{:journal-entry-line/account (:db/id (:transaction-account/account a)) :journal-entry-line/location (:transaction-account/location a) :journal-entry-line/debit (when credit-from-bank? (Math/abs (:transaction-account/amount a))) :journal-entry-line/credit (when debit-from-bank? (Math/abs (:transaction-account/amount a)))})) (if (seq (:transaction/accounts entity)) (:transaction/accounts entity) [{:transaction-account/amount (:transaction/amount entity)}]))) :journal-entry/cleared true})))) (defmethod entity-change->ledger :invoice-expense-account [db [entity changes]] nil ) (defmethod entity-change->ledger nil [db [entity changes]] nil) #_(defn entity-change->ledger [[entity-id changes]] [entity-id (infer-entity changes)]) (defn ledger-entries->transaction [entries] (into [[:replace-general-ledger (:journal-entry/original-entity (first entries))]] entries)) (defn process-one [report-queue] (let [transaction (.take report-queue) _ (println "processing transaction" transaction) db (:db-after transaction) affected-entities (->> (:tx-data transaction) (map (fn [^datomic.db.Datum x] {:e (:e x) :a (d/ident db (:a x)) :v (:v x) :added (:added x)})) (group-by :e) (mapcat #(datums->impacted-entity db %)) (set)) d-txs (->> affected-entities (map #(entity-change->ledger db %)) (filter seq)) retractions (map (fn [[_ e]] [:db/retractEntity [:journal-entry/original-entity e]]) affected-entities)] (when (seq retractions) (println "Retracting " retractions) @(d/transact (d/connect uri) retractions)) (doseq [d-tx d-txs] (clojure.pprint/pprint d-tx) #_(println "updating general-ledger " d-tx) @(d/transact (d/connect uri) [d-tx])))) (defn process-all [] (while (not (Thread/interrupted)) (process-one (d/tx-report-queue (d/connect uri) )))) #_(process-one (d/tx-report-queue (d/connect uri) )) #_(process-all)