A wholesale distributor came to us with a familiar setup. Invoices were written by hand. Stock lived in spreadsheets. Salesmen visiting shops phoned their orders back to the office, and payments collected on the road were hard to follow up. Nobody could say with confidence how much stock was really in the godown, or which brands were actually making money.
They had looked at off-the-shelf billing software. It didn't fit, and the reasons why turned out to be the most interesting part of the project.
We built them Invoice Desk: a web app for the office and an Android app for salesmen, both on one backend. These are the problems that shaped it.
Stock isn't counted in "units"
Most billing software has one idea of quantity: a number. Sell 3, stock goes down by 3.
A distributor's world is messier. A product arrives in cases. A case holds a number of boxes. A box holds a number of pieces. A shop might order two cases and five loose pieces in the same line, and the price per piece depends on how it's bought.
If the software can't think in those terms, staff end up converting in their heads — and mental arithmetic at the billing counter is where stock errors start.
So we made case, box and piece first-class throughout the system. Products know their pack sizes. Orders, invoices, purchases and stock reports all speak the same language the trade uses. Internally everything is stored in the smallest unit so the maths is exact, but nobody has to see that.
Every delivery has its own price
The same product bought in March and again in June rarely costs the same. If you average it all together, your profit numbers are fiction.
We kept each delivery from a supplier as its own priced lot. When you sell, the system uses up the oldest stock first, and when one lot runs out mid-invoice it carries on into the next. Each sale therefore knows its true cost, which is what makes per-brand profit and loss and margin reports trustworthy.
Returns follow the same logic: returned stock goes back into the lot it originally came from, and the shop's balance is credited automatically.
The invoice has to look exactly like the old one
This sounds cosmetic. It isn't. Shops, accountants and auditors are used to a particular invoice layout. A new design, however clean, creates questions and friction.
So the GST tax invoice PDF matches the company's existing layout exactly, while handling everything behind it properly: CGST, SGST and IGST, trade schemes, cash discounts, TCS and rounding. Matching an existing layout exactly took real effort with PDFKit, but it meant the change was invisible to every customer receiving an invoice. For a business, that's the best kind of change.
One pricing engine, everywhere
Prices and tax are calculated in several places: on the final invoice, in the billing preview, and on the salesman's phone when he's giving a shop an estimate. If those calculations live in three separate pieces of code, they will eventually disagree. And when the estimate a salesman quoted doesn't match the invoice the shop receives, it becomes an argument about trust, not arithmetic.
We built one pricing engine used by the invoice, the preview and the mobile order estimate. Schemes, discounts and GST are worked out once, the same way, every time. The web app and mobile app can't drift apart because there's nothing to drift.
Two people billing at once
In a busy office, two staff members may bill the same product within seconds of each other. Without care, both can sell the last box in stock, or two invoices can end up with the same number — a real problem for GST compliance.
Each invoice is created inside a database transaction with row locking. The stock rows and the invoice counter are locked while one invoice is being written, so the second person waits a fraction of a second instead of overselling. It's invisible in daily use, which is the point.
Orders from the field, reviewed in the office
Salesmen sign in on the Android app with their mobile number and a four-digit PIN. They see their shop list with outstanding dues, filter by route, take orders by case, box or piece with a live price estimate, and collect payments against any unpaid bill.
Field orders don't become invoices automatically. They arrive in a review queue in the office, where staff can edit, approve and bill them, or reject them. That keeps the office in control while removing the phone calls and the re-typing.
And because salesmen don't need to see what the company paid for its stock, purchase costs are never sent to the mobile app at all. Role-based access isn't only about what's hidden on screen; it's about what never leaves the server.
The engineering that keeps it boring
Billing software has to be dull and dependable. A few decisions helped:
- Versioned database migrations run automatically on every deploy, so the database always matches the code. One of them was a one-time fix for stock that older invoices had oversold before the new rules were in place.
- Embedded Postgres (PGlite) for local development and tests, so a new developer can run everything without installing a database.
- 80 automated tests run against a real Postgres engine in memory, not mocks, so they catch the kind of bugs that only appear with real SQL.
What we'd tell any business considering custom billing
Off-the-shelf software is the right choice for a lot of businesses. But if your team is constantly working around it — converting units in their heads, keeping a spreadsheet next to the billing screen, phoning orders in — that's a sign the software doesn't understand how your trade works.
Custom software is worth it when it encodes the way your business really operates, and then gets out of the way.
You can see the full project, including the tech stack, on the Invoice Desk project page. If your business is outgrowing its billing tools, book a discovery call.