Most invoice templates are one pretty page you copy for every client. After a few months you have forty files and no idea who still owes you. I built an invoice tracker in a single spreadsheet (Excel + Google Sheets, no macros): one table of invoices, one table of line items, and a printable invoice page that fills itself in from whichever invoice number you pick. Here's how the formulas work.
Due date from each client's terms
Clients have an optional "Terms (days)" column. Blank means the default from Setup. The due date on the Invoices tab:
=D6 + IFERROR(
IF(INDEX(ClientTerms, MATCH(C6, ClientNames, 0)) = "",
DefaultTerms,
INDEX(ClientTerms, MATCH(C6, ClientNames, 0))),
DefaultTerms)
The IFERROR covers a client name that isn't on the Clients tab yet, so the row never shows an error.
Status that updates itself
=IF(J6=0, "Draft",
IF(M6<=0.005, "Paid",
IF(E6<TODAY(), "Overdue "&(TODAY()-E6)&" days",
IF(N(K6)>0, "Partial", "Sent"))))
J is the total, K is the amount paid, M is the balance. <=0.005 instead of =0 stops a rounding leftover from keeping an invoice open. Conditional formatting with "begins with Overdue" turns those red.
The printable invoice
This is the part I wanted most: pick an invoice number in one cell and get a finished invoice. The header is easy: find the invoice's row once with MATCH and reuse it.
Row: =MATCH($D$2, InvNum, 0)
Client: =INDEX(InvClient, $J$2)
Due: =INDEX(InvDue, $J$2)
The line items are harder, because one invoice can have any number of lines spread anywhere in the Items table. Without FILTER (older Excel doesn't have it), a helper column on Items marks the lines that belong to the selected invoice with their row number:
=IF(AND($B6<>"", $B6=Invoice!$D$2), ROW(), "")
Then line k on the invoice is the k-th smallest of those row numbers:
Helper: =IFERROR(SMALL(ItemKey, 1), "")
Description: =IF($J20="", "", INDEX(Items!$C:$C, $J20))
Amount: =IF($J20="", "", INDEX(Items!$H:$H, $J20))
SMALL skips the empty text cells, so lines 1, 2, 3 pull the first, second and third matching rows in order, and the rest stay blank. It works the same in Google Sheets.
Aging buckets
A hidden column holds days overdue (0 if paid or not yet due):
=IF(OR($B6="", N(M6)<=0.005), 0, MAX(0, TODAY()-E6))
Then each bucket is one SUMIFS with two conditions on that column:
1-30: =SUMIFS(InvBal, InvBal, ">0.005", InvOver, ">=1", InvOver, "<=30")
31-60: =SUMIFS(InvBal, InvBal, ">0.005", InvOver, ">=31", InvOver, "<=60")
Average days to get paid
Helper: =IF(AND(N(M6)<=0.005, L6<>"", N(J6)>0), L6-D6, "")
Average: =IFERROR(ROUND(AVERAGE(InvDays), 0), 0)
Only fully paid invoices with a paid date count. If that number creeps up, it's a sign to tighten your terms or send reminders sooner.
The finished sheet
It also has a dashboard (invoiced vs collected by month, outstanding, oldest overdue invoices, top clients) and per-client totals. It's on Payhip and Gumroad for $9 if you'd rather not build it yourself.
Top comments (0)