Hledger: Robust Plain Text Accounting
This is exactly how I do my accounting: record transactions in a plain text file, which is processed by the system, resulting in various ledger and account type objects being in a certain state, which can then be subject to queries and reports.<p>Mine is written in TXR Lisp. It is not released to the public.<p>All input is recorded via function call expressions, which are of course S-exps. There is only one single macro in the whole thing: <i>def-date-var</i> defines a variable that exhibits different values depending on a effective date context (established by a dynamically scoped date variable). With this we can do things like different tax rates for different periods, without having to introduce such things as parameters appearing in every input item.<p>&gt; <i>The amounts within a transaction must always sum up to zero. As a convenience, one amount may be left blank; it will be inferred so as to balance the transaction.</i><p>I also have transactions that sum to zero. There can be three or more account deltas in a transaction, not only two.<p>This is not how accounting is taught in North America, though; I came up with it myself.<p>Inferring one amount is a bad idea. My system throws an exception if the transaction amounts do not add to zero.<p>However, the entry of items is done via various convenience functions, most of which generate the necessary transactions against all the right accounts. For instance, recording a new asset:<p><pre><code>  (record-asset 1 &quot;Discombobulator&quot; :cca-50 ;; ID, description, capital-cost-allowance class
                (date 2017 1 23) ($ 79.99) ($ 4.00) ($ 5.60)) ;; base price, GST, PST taxes.
</code></pre>
From the looks of the journal format of hledger, it&#x27;s too generic. I wouldn&#x27;t want to use anything of the sort.<p>Different items need their own syntax. I don&#x27;t want to record a simple purchase of a recurring expense in the same way that I record an invoice.<p>I bind a new variable when recording an invoice:<p><pre><code>  (defparml %inv-0042%
    (new invoice
         number 42
         provider %my-addr%
         client %foo-client-addr%
         remit-to &quot;Kazinator&quot;
         remit-days 25
         date (date 2017 6 30)
         items (list (new time-unit
                          start (date 2017 6 1)
                          end (date 2017 6 2)
                          hours 16
                          rate %rate-foo-client-2017%)
                     (new time-unit
                          start (date 2017 6 5)
                          end (date 2017 6 9)
                          hours 40
                          rate %rate-foo-client-2017%)
                     (new time-unit
                          start (date 2017 6 12)
                          end (date 2017 6 16)
                          hours 40
                          rate %rate-foo-client-2017%)
                     (new time-unit
                          start (date 2017 6 19)
                          end (date 2017 6 23)
                          hours 40
                          rate %rate-foo-client-2017%)
                     (new time-unit
                          start (date 2017 6 26)
                          end (date 2017 6 30)
                          hours 40
                          rate %rate-foo-client-2017%))))
</code></pre>
There is no custom DSL here at all; just the <i>new</i> macro of the object system.<p>This is then recorded using a plain function call to the convenience function <i>record-invoice</i>:<p><pre><code>  (record-invoice %inv-0042% &quot;Invoice 0042 issued to Foo, Inc.&quot;)
</code></pre>
This function will digest the invoice and post the right amounts into various accounts. The GST tax has to be charged, and such,<p>When the invoice is paid:<p><pre><code>  (record-paid-invoice %inv-0042% &quot;Invoice 0042 paid by Foo, Inc.&quot;
                       (date 2017 7 31))
</code></pre>
this function will also update multiple accounts, including the automatic withholding of income tax, based on the withholding rate configured for the date into which the invoice lands.<p>To generate a nice HTML invoice, at the REPL, I just call the .(html ...) method of the invoice object, which takes a stream argument.<p><pre><code>  1&gt; (with-stream (s (open-file &quot;invoice.html&quot; &quot;w&quot;))
       %inv-0042%.(html s))</code></pre>