After I wrote my mbork-ledger-insert-transaction command, bound it to C-c C-v
(in Ledger mode only, of course) and started using it, I noticed a big problem with it. It only inserts the heading line of the transaction and its source account, leaving the line for the target account empty. The result is that I need to fill in that line manually, which defeats the purpose of inserting the whole transaction fast.
By the way, in the meantime I upgraded my Ledger mode and it turned out that the newer version already has something quite similar to what I am writing, that is, C-c C-a
(ledger-add-transaction
). It does not work for me very well, though – it uses the Ledger xact
command, which is much too “magical” (that is, I don’t really know what it does under the hood) and unreliable for me. I suspect that it finds the first transaction in the file matching the given criteria and creates a “similar” one with the given date. This is one of the problems – I would very much prefer if it looked for the last transaction matching what I tell it. Moreover, I still have to type quite a bit. For example, my solution lets me choose the source account with just one keystroke instead of a regex matching some account. Also, I heavily utilize amount eliding, so my grocery transaction may have three postings, food, candy, and wallet, and I will type in the amount spent on candy (which is usually much smaller than the one for food, hence it is easier for me to enter) and the whole amount I paid (“wallet”, with a minus sign), which is something I can just copy from the receipt or the bank history, letting Ledger fill in the food spending (which is often a lot of items I don’t want to spend time adding manually (even using my Calc-based command). Apparently, when the first posting (here, “food”) does not have an amount associated with it, xact
does not work. All these issues are probably related to how I use Ledger (and since it is very flexible, this may be very different to how John Wiegley uses it), but they mean that xact
is pretty much useless for me.
I spent some time thinking how to solve it. I started (again) with looking at Ledger mode sources – this time I made sure that I pulled the newest version from the official repo (it turned out that previously I was using a very outdated version). It is easy enough to get the list of all accounts in the current buffer (as strings) – the function ledger-accounts-list-in-buffer
does exactly that. I could do that and ask the user for the account – with completion – but that is not what I want here. If I went that route, I would need to add one more interactive step to the list of things the user has to do, that is, select the target account. This could work, but usually, when making a purchase in some specific store (whose name I already have in the transaction description), the target account (which is the “spending category”, like “food” or “books”) is the same – it is not very probable that I’d buy food in a bookstore or books in a grocery store. With that in mind, I decided that my code should search the current buffer for the most recent existing transaction with the same description and get the target account from there. This will not cover all cases (I often shop in discount stores where I can buy food, snacks and other things, and sometimes I may buy very different things there), but should be enough for a vast majority, especially that the “most common” type of things I usually buy in a particular store is almost always the first posting in the transaction.
For example, here is one possible transaction from my Ledger file (translated into English):
2024-11-29 * Some store name Expenses:Food Assets:Cash:My wallet -12.34 PLN
I may additionally buy some candy and/or something for a friend, and then the transaction would look like this:
2024-11-29 * Some store name Expenses:Food Expenses:Candy 4.56 PLN Assets:Receivables:Tom 5.67 PLN Assets:Cash:My wallet -22.57 PLN
As you can see, the first posting is always the same, so copying that line and only it is a very reasonable idea. Of course, if there are no matching transactions (which may happen when entering some description for the first time), the target will be left empty for the user to fill in, but that’s ok, since it’s going to happen only once per transaction description (which is the store name in my case).
As for finding the previous transaction, I just decided to use re-search-backward
with a custom regex. Again, this might not work for everyone – for example, I use ISO-8601 dates exclusively, and all my transactions are in the “cleared” state. (I don’t often use transaction states, and Ledger mode displays the description of a “pending” transaction in a bright red, so I reserve that state for transactions which are entered tentatively and should be fixed as soon as possible. For example, when I type in the bank statements, I don’t always know how to categorize some items there, so the transactions spend the relatively short time – before me typing them in and my wife telling me what she bought – in a “pending” state.)
Finally, I set ledger-clear-whole-transactions
to t
– I have no use for using states of individual postings in a transaction anyway, so this allows me to clear the current transaction even if the point is not on its first line.
Note: I’ve been using this setup for some time now, and I have to say that it is much better than the previous version. Time will tell if I find yet another way of streamlining my usage of Ledger, but I’m quite happy with it as it is!