So, as explained in my last post, we needed a new invoicing system for the church hall. The back-of-a-beer-mat requirements were pretty simple...
- One-off bookings can be invoiced by a manual process, but the regulars must be sent an invoice by e-mail automatically at the end of each month.
- One invoice should contain all bookings for a customer in the period covered.
- Must be possible to see an aggregated summary of which customers owe us how much money
- Must be easy to track repeating series of bookings and make ad-hoc tweaks
There did not seem to be anything out there to buy (at least, not for sensible money), so I sat down over a couple of weekends and wrote it.
Naturally, I used other systems as much as possible. Back in the day, we used Google Calendar to track the actual bookings, which is a well known interface and worked nicely. Unlike HallMaster, it doesn't automatically prevent clashing bookings - but we only have one human entering bookings, and this hasn't been a problem in six months of operation. It also doesn't guarantee that every calendar event is associated with a known customer - but since most of our invoices go out monthly, we solved that with a weekly nagging e-mail to the staff in question about any calendar events which can't be matched to a customer or room.
So, with a Google Calendar full of bookings in place, I turned to Django for the next bit. Thanks to the automatic form generation (and a bit of customisation of the automatic admin interface), writing a web app to keep track of customers and their billing contact details was super-quick. Each customer also has a list of "event names" which are the titles under which their bookings appears in Google Calendar. This also allows the use of e.g. "Private Event 42" for customers who don't want their name visible in the public calendar.
And now to the beating heart of the system: automatic invoicing.
This has to be customer-centric, so the first thing I implemented was a method on the Customer model which could generate an invoice for that customer's bookings in a given time period. Looking things up in Google Calendar is super-easy: they have a nice, well-documented API including example code which can be copied and pasted for a fast start. Their API also has the crucial feature of forcing the "expansion" of repeating events, i.e. your code doesn't have to understand repeats; it just gets an event for every repeat.
From there, looking up each room's hourly cost according to whatever tariff the customer is on is an easy modelling and maths problem. Thankfully we're not required to charge VAT owing to our size and charitable status, so that complication doesn't have to be factored in.
The most complicated part turned out to be generating invoices in an acceptable format. My first thought was plain text e-mails, but getting a big table of bookings laid out nicely in one is a challenge, and probably impossible given that many e-mail clients don't use a monospace font. What's more, it would look rubbish on a mobile phone, which is where many e-mails get read these days.
Generating PDFs from Python doesn't seem to be a super-easy solved problem, so in the end, I went for generating invoices as HTML (using a normal Django template) and then turning this in to PDF using wkhtmltopdf (which has some decent Python bindings). This does require the right fonts to be installed on the target system (ttf-mscorefonts-installer on Debian) to avoid invoices coming out in a blocky default font, but other than that blip it seems to be 100% reliable, and doesn't require an X server.
We send these out attached to a plain text e-mail, which just says how much you owe and refers you to the attachment for details.
Naturally, the invoicing logic is covered by a wide set of automated tests. We do have the odd hirer whose bookings cross multiple days (i.e. span midnight), so I tested that in particular and also the possibility of bookings which span the clocks going forward or backward. Django makes this fairly easy to cope with: store all the times in UTC, which is also what the Google Calendar will give them to you in. Then all you need to do is format them for display to the user in local time (which covers BST when needed).
With that logic in place, it was just a quick custom management command to loop over all customers and invoice them if they are a regular and it's the first of the month. Set that on a cron job for early morning on the first of the month, sit back, and relax.
Make it easy to look under the hood. The Django admin interface helps with this, as does the facility I built in to CC all outgoing e-mails to extra recipients. I use this to stick them all in folders under my own inbox so I can refer back to them.
Ensure a failure to invoice one customer doesn't stop all the others being invoiced. Easy enough with a try/catch, and the customer-centric design makes it easy to issue that one customer's invoice later with a manual invocation from "manage.py shell" once the problem is fixed. This works well and we've had 100% successful invoicing runs for the last five months (out of six since going live).
Make it easy for people to get the payment reference right when paying you via BACS. Our system assigns a customer number to each hirer, and tells them to put just that as their payment reference. There's no need for an invoice number or dates, and the reference can be the same for all payments, which helps with some online banking systems that make it painful to set a fresh reference for each payment to the same recipient.
- This works pretty well, and nearly all customers manage to do the right thing. Some insist on quoting an invoice number (which is managable, we can just de-reference that back to the customer), and the odd one or two continue to make stuff up. I have an "upload bank statement" view which allows me to manually assign incoming payments where we haven't detected the customer automatically. At the moment this is rare enough that I'm letting it slide, but if we ever get an automatic feed in of this information (Monzo and Starling, where are those charity accounts?), we will start cracking down and insisting on the right reference.
Don't start your invoice numbers with the letter I, people will mis-read it as the number 1. We solved that by switching to "V" instead.
The end result is less than 2,000 lines of code which I'm continuing to evolve and tweak, but overall, this has solved it. We can see exactly who owes us money, split between the one-ofs and the regulars, and being reliably sent a bill on time is helping the vast majority of them to pay up promptly with the right reference. We're approaching the mid-point of the year and well on track to get back to previously attained levels of hiring income.
The authors of HallMaster might well argue that I haven't replaced their entire product with two weekends and not much code - and they'd be right, my system is designed to do exactly what we need and not service multiple customers - but I can't help but feel it's high time they offered fully automatic invoicing to really save their users some time. It's not difficult and it makes things feel so much more professional.