Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Double entry modeling
(def billing-violations [negative-entries small-minimum-payment prepaid-mutual])
(def loss-violations [credit-loss pre-loss])
(def general-violations [minimum-payment-mutual])
(defn all-violations [balances]
(concat billing-violations loss-violations general-violations))
(s/defn fixing-invariants :- [Entry] [initial-balances :- Balances, new-entries :- [Entry]]
;; Pre-checks that the DB starts in valid state
(when-let [violations (all-violations initial-balances)] (ex/invalid-input! {:reason :wrong-state-before-fixing-invariants
:balances balances
:violations violations}))
(let [new-balances (apply-entries initial-balances new-entries)
violations (apply-all all-violations new-balances)
;; if there are violations, try to fix them automatically
[new-entries final-balances] (reduce (fn [[new-entries current-balance] violation]
(let [corrections (case violation
:negative-entry (fix-negative-entry current-balance)
:prepaid-mutual (fix-prepaid-mutual current-balance)
:minimum-payment-mutual (fix-minimum-payment-mutual current-balance)
:credit-loss (fix-credit-loss current-balance)
:pre-loss (fix-pre-loss current-balance)
:small-minimum-payment (fix-small-minimum-payment current-balance))]
[(concat corrections new-entries) (apply-entries current-balance corrections)]))
violations)]
;; If we weren't able to fix all of them, error!
(when-let [violations (not-empty (all-violations final-balances))]
(ex/invalid-input! {:reason :invariant-violations-after-fixing-them :balances final-balances :violations violations}))
new-entries))))
(defschema Movement
{:movement/id UUID
:movement/flow-id String
:movement/topic TopicEnum
:movement/owner-account Account
:movement/produced-at LocalDateTime
:movement/consumed-at LocalDateTime
:movement/user String})
(defschema Entry
{:entry/id UUID
:entry/amount t-money/PositiveAmount
:entry/debit-account BookAccountType ; all of our book accounts
:entry/credit-account BookAccountType
:entry/post-date LocalDate
:entry/movement Movement})
(def new-purchase [{:entry/debit-account :book-account-type.asset/unsettled
:entry/credit-account :book-account-type.liability/unsettled-counterparty
:entry/amount #'transaction-amount ; function that extracts this amount from the event payload
:entry/post-date #'produced-date}
{:entry/debit-account :book-account-type.liability/current-limit-counterparty
:entry/credit-account :book-account-type.asset/current-limit
:entry/amount #'transaction-amount
:entry/post-date #'produced-date}])
(def settle-purchase [{:entry/debit-account :book-account-type.asset/settled-brazil
:entry/credit-account :book-account-type.asset/installments-merchant
:entry/amount #'amount
:entry/post-date #'post-date}
{:entry/debit-account :book-account-type.liability/payable-installments
:entry/credit-account :book-account-type.liability/payable-brazil
:entry/amount #'amount
:entry/post-date #'post-date}])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.