I have been using ledger
to keep track of my expenses. It is simple, it
doesn’t interfere with my workflow, and I can edit data with my favorite
text editor Emacs, because ledger only deals with plain text files. I love
this workflow.
At some point I ran onto the limitation about how to organize the invoices
I receive. Most of the time they are pdfs I receive on my email, but it
could also be physical ones that I then scan with my smartphone. I
started storing them on a local folder, renaming them into something I can
make sense when looking at all the files, and finally writing their
location as some extra metadata to the posting on my ledger file.
Naturally, after doing it a few times, my annoyance grew. I needed to
automate this process. Emacs is great to edit text and its operating system
capabilities should also take care of the renaming of the files. So I dived
into practicing some ELisp to implement a function that does exactly what I
need.
Emacs already has a mode for ledger files, which provides all the
functionalities to edit the files and each posting individually. The next
code block automates my previous manual workflow. I’ll comment on each step
of this function, as a kind of reminder to myself what everything does.
1(defun on/ledger-link-invoice ()
2 "Attach an invoice file to this posting."
3 (interactive) ; This allow the function to be called with interactively; M-x
4 ; when-let* is a beatiful macro, every language should have it. What it does
5 ; is as a let bind variables, if that variable holds a true value it goes into
6 ; the body of the macro. As soon as any varible evaluates to nil, the process
7 ; stops and the body is not executed.
8 (when-let* (; Use ledger-mode function to get the date of the posting
9 (date (ledger-xact-date))
10 ; Extract the payee of the posting and replace whitespace with underscores
11 ; the trim right is because this extraction includes the new line \n
12 (payee (replace-regexp-in-string " " "_" (string-trim-right (ledger-xact-payee))))
13 ; Select the file I want to link in my file, default the folder of searching
14 ; to ~/Downloads, "Attach: " is for the prompt
15 (src-file (read-file-name "Attach: " "~/Downloads"))
16 ; Create the new relative path "invoices/YYYY-MM-DD_payee.pdf"
17 (file-name (concat "invoices/" date "_" payee "." (file-name-extension src-file))))
18 ; move my selected file to the new location and new name given by file-name.
19 ; The expansion is done for safety reasons to be sure which is the target
20 ; base directory. I my case it does match to whery the ledger file is
21 ; and the relative path is good enough
22 (rename-file src-file (expand-file-name file-name "~/accounting"))
23 ; this is the text editing part. Move to the very-begging of the posting
24 (ledger-navigate-beginning-of-xact)
25 ; move pointer to end
26 (end-of-line)
27 ; This writes a new line on my buffer
28 (newline)
29 ; write the metadata string " ; Invoice: invoices/YYYY-MM-DD_payee.pdf"
30 (insert " ; Invoice: " file-name)))
That is all the automation I need for the moment. I’m happy that I can
adapt Emacs to help me on these tasks. Now every digital invoice is neatly
organized and won’t get lost. There is now enough context, for each posting,
because I link to the invoice.