Invoicing App Starter · Industry version
Hours become line items, retainers draw down visibly, deposits gate the work — billing that matches how services are sold, in code you own.
Services billing is a chain: hours tracked, hours approved, hours invoiced, invoice chased. Every link that lives in a different tool leaks money. This starter generates the whole chain as one app — timesheet to line item to payment — and hands you the code.
01 · The problem
Generic invoicing starts at the invoice, but a consultant's billing starts weeks earlier, at the tracked hour. When time lives in one tool and invoices in another, month-end means exporting a timesheet, eyeballing it against the contract, and re-typing it as line items — the step where unbilled hours quietly evaporate. The industry even has a name for the loss: leakage.
Retainers make it worse. A client on twenty hours a month needs an answer to 'how much is left?' at any moment, an alert before the bucket overruns, and a clean decision about whether unused hours roll over. An invoice tool with no retainer model leaves all of that to a spreadsheet the client never sees — and disputed bills follow.
Then there's the shape of project money: a deposit before work starts, milestones on delivery, the balance on acceptance. Generic tools can print those invoices but can't see the structure — that milestone two is 60% delivered and unbilled, or that a deposit hasn't cleared and work is about to start anyway.
02 · Data model
Everything in the base Invoicing starter —
clientsestimatesinvoicesline_itemspaymentsexpenses— plus the entities this industry actually runs on:
| table | what it holds |
|---|---|
projects | The engagement layer between client and invoice — scope, rate arrangement, budget, and every hour and expense hanging off it. |
time_entries | Tracked hours with date, description, rate, and a billed flag — the raw material invoices are built from, never re-typed. |
retainers | Prepaid hour or fee buckets per client — balance, period, rollover rule — drawn down by time entries as they land. |
03 · Screens
Select a project and a date range, and unbilled time entries become draft line items — grouped how the client likes to read them, by task or by week. Hours are marked billed in the same transaction, so nothing invoices twice and nothing slips.
Each retainer shows balance, burn rate, and days to depletion, with an alert threshold you set. The month-end retainer statement — hours used, hours left, rollover applied — generates from the same records the client's invoice does.
The aging report reads by project as well as by client, and it knows about deposits — a project whose deposit is unpaid shows red before work begins, which is precisely when you can still do something about it.
04 · In practice
Filter to unbilled time across all projects, review the draft lines each project generated, adjust a description, send. What used to be an evening of cross-referencing a timesheet export is a review pass, because the hours were invoice-shaped from the day they were logged.
A client's twenty-hour retainer hits its 75% threshold mid-month. The alert fires; you flag it in the weekly call rather than the awkward overage email. The top-up invoice generates from the retainer record, and next month's balance starts clean.
A new engagement starts with a 40% deposit invoice generated from the project's budget. The project's status shows deposit-pending until payment lands; when it clears, the milestone schedule activates and hours start accruing against it.
Time entries are a first-class entity — log hours in the app against projects, and they flow to invoices as line items. If you prefer a dedicated tracker, its export maps into the time_entries table; either way, invoicing reads from one source.
Retainers carry a balance, a period, and a rollover rule you define — use-it-or-lose-it, roll forward, or cap at a maximum. Time entries draw the balance down automatically, and statements generate per period. The rule is code, so your policy is exactly expressible.
Yes — the rate arrangement lives on the project, so hourly, fixed, and milestone engagements coexist for the same client. Fixed projects still track hours internally if you want margin visibility without showing hours on the invoice.
Nothing — that's the point of a starter. The app is source code in your repository, running on your infrastructure with data in your database. Dual7 generates and regenerates it; the running app never depends on us.
Dual7 App Starters
Hours become line items, retainers draw down visibly, deposits gate the work — billing that matches how services are sold, in code you own. Describe your version to start — the output is a project you own.