User guide
Invoicing
Looking for what it does rather than how to use it? Read the Invoicing overview .
Accounts receivable: raising invoices against client organizations, issuing them, tracking what is owed, and chasing what is late.
What it is
Invoicing turns work you have done — ticket time, store orders, accepted quotes, or lines you type by hand — into an invoice addressed to a client organization, and tracks that invoice from draft through to paid, void, or overdue.
It covers the money your MSP is owed by its clients. Your own Solidlio subscription bill is a different thing and lives at Billing → Subscription & Invoices.
Concepts
| Concept | What it is |
|---|---|
| Invoice | A receivable addressed to one organization. Carries a status, a due date, a currency, a tax rate and a set of line items. |
| Invoice number | The customer-facing document number, e.g. ACME-00042. Counted per tenant from your own sequence. Immutable once issued. |
| Line item | Description, quantity, unit price, and a per-line taxable flag. The line amount is computed for you. |
| Status | DRAFT, SENT, VIEWED, PARTIAL, PAID, OVERDUE, VOID. |
| Organization | Who the invoice is billed to. Also supplies the address that determines the tax rate. |
| Account | The tenant that owns the invoice. A managed client is a separate account from the MSP that services it. |
| Aging | Where an unpaid invoice sits relative to its due date: current, 1–30, 31–60, or 60+ days past due. |
Status lifecycle
DRAFT ──send──► SENT ──payment──► PARTIAL ──payment──► PAID
│ │ │
│ └──past due────────┴──► OVERDUE
│
└──void──► VOID
- DRAFT — created, not yet issued. Editable. Invisible to the customer.
- SENT — issued and emailed. Visible in the customer portal.
- VIEWED — reserved. Nothing sets it today; see Limits.
- PARTIAL — some payment received, a balance remains.
- PAID — settled in full. Terminal.
- OVERDUE — past its due date with a balance outstanding. Set by the aging sweep (§8) and derived independently in reporting, so the figures are correct between runs.
- VOID — cancelled. Terminal. Only possible while nothing has been paid.
There is no WRITTEN_OFF status. To reduce or cancel a receivable that has already taken payment, issue a credit note.
Roles and permissions
| Action | platform administrator | MSP administrator | MSP technician | organization administrator | power user | CUSTOMER |
|---|---|---|---|---|---|---|
| List / view invoices (own tenant) | ● | ● | ● | ● | ● | ● |
| View DRAFT invoices | ● | ● | ● | ● | ● | — |
| Create an invoice | ● | ● | ● | — | — | — |
| Send an invoice | ● | ● | ● | — | — | — |
| Edit due date / notes | ● | ● | ● | — | — | — |
| Void an invoice | ● | ● | ● | — | — | — |
| Bill unbilled time | ● | ● | ● | — | — | — |
| Download PDF | ● | ● | ● | ● | ● | ● |
| Change the numbering format | ● | ● | — | — | — | — |
The organization portal consumes invoices; it does not issue them. An org admin sees the invoices billed to their organization, downloads the PDF, and pays. They cannot raise one. If you are looking for “let our client invoice their own customers”, that is not what this feature is.
Everything is tenant-scoped. A customer-portal user sees only their own organization, and never a draft.
Walkthroughs
Raise an invoice by hand
- Go to Invoices in the MSP portal (
/msp/invoices), then Create Invoice. - Pick the client organization. If you have a client selected in the client switcher it is preselected for you.
- Set the due date. Defaults to 30 days out.
- Add line items — description, quantity, unit price. Quantity accepts fractions, so 1.75 hours is fine. Clear the Taxable checkbox on any line that is exempt.
- Leave Override the tax rate off unless you have a reason. Tax is calculated from the client’s registered address when you save. The preview shows “Calculated on save” rather than inventing a number.
- Save as draft, or Create & send to issue and email it in one step.
Bill unbilled ticket time
- Open the client at Clients → <client> (
/msp/clients/[id]). - The Unbilled time panel shows the count and value of time that is billable, approved, and not yet invoiced.
- Click Bill time. A draft invoice is created with one line per time entry, labelled
[#<ticket number>] <description>, and those entries are marked invoiced in the same transaction so they can never be billed twice. - You land on the new draft. Review, then send.
Only time that is billable AND approved AND un-invoiced is picked up. Non-billable and unapproved time is excluded.
Send, chase, and settle
- Open the invoice at Invoices → <number>.
- Send Invoice issues it, records
sentAt, emails the organization’s primary contact, and queues a QuickBooks sync. - When money arrives, Record Payment. Status moves to
PARTIALorPAIDautomatically from the payments on file. - If it goes past due, the aging sweep (§8) marks it
OVERDUEand emails a payment reminder. - Download PDF at any time, from either side.
Void an invoice
Open the invoice and click Void. This is only offered while paidAmount is zero. If money has already been received, void is refused —
Cannot void an invoice with payments applied. Issue a credit note instead.
— because voiding would leave real payments attached to a zero-value document.
Set your invoice number format
- Invoices → Numbering settings (
/msp/settings/invoicing). - Set a prefix (uppercase letters and digits, dashes allowed, up to 12 characters) and a number width (1–10).
- The page shows exactly what the next invoice will be numbered.
- Save. The change applies to future invoices only.
Invoice numbering
Each tenant counts its own invoices. The document number is <prefix>-<zero-padded sequence> — ACME-00042 — drawn from a counter on your account, not from a number line shared with other Solidlio customers.
- Atomic. The number is claimed with a single
UPDATE … RETURNINGinside the same database transaction that inserts the invoice. The row lock that statement takes serialises concurrent creates, so two invoices raised at the same instant cannot receive the same number. Verified against eight simultaneous creates with zero duplicates. - Transactional. If the invoice insert rolls back, the number rolls back with it.
- Immutable. Once issued, the number never changes — not when you edit the invoice, not when you void it, and not when you later change your prefix. Verified by test across all three.
- Not truncated. A sequence wider than the padding prints in full (
INV-100000on a padding of 5) rather than dropping a digit and minting a duplicate.
Tax
Tax comes from the client organization’s own country and province, resolved through the shared tax engine. It is not a global default.
Canada — computed. All ten provinces and three territories:
| Rate | |
|---|---|
| ON | 13% HST |
| NB, NS, PE, NL | 15% HST |
| BC, MB | 5% GST + 7% PST = 12% |
| SK | 5% GST + 6% PST = 11% |
| QC | 5% GST + 9.975% QST = 14.975% |
| AB, NT, NU, YT | 5% GST |
Canada with no province on file — held, not guessed. Creating the invoice is refused:
Cannot determine tax: this Canadian organization has no province set. Set the organization’s province, or pass an explicit taxRate.
Set the province on the organization, or supply an explicit rate.
United States — returns 0% and is not held. US sales tax is fragmented across state, county and city and requires a tax provider (Avalara, Stripe Tax). Solidlio does not have one wired in, so it does not fabricate a rate. If you invoice US clients you must set the rate yourself using the override.
Everywhere else — 0%, same reasoning.
Per-line exemptions are honoured: tax applies to the sum of taxable lines only, not the whole subtotal.
The override is on the create form and persists onto the invoice, so the rate you charged is recorded on the document.
Money arithmetic
All invoice money is computed in integer cents.
The header subtotal is the sum of the rounded line amounts, so an invoice always foots: the lines you can see add up to the total you are charging. A line amount is computed from the unit price as it will be stored, so a line can always be reproduced from its own printed figures.
This is pinned by a regression test that encodes the previous floating-point implementation and asserts the two differ — on three lines of 3 × 33.335 the old path stored a header subtotal of 300.02 against lines summing to 300.03.
Aging and payment reminders
A scheduled job, invoice-aging-sweep, runs daily at 08:00.
It does two passes:
- Aging. Any invoice that is
SENT,VIEWEDorPARTIAL, is past its due date, and still has a balance becomesOVERDUE.DRAFT,PAIDandVOIDare never touched. An invoice settled in full is never aged, whatever its status says. - Dunning. Overdue invoices with an outstanding balance and a primary contact get the
invoice_reminderemail, showing the amount still owed rather than the invoice face value, and the number of days overdue.
Reminders are rate-limited per invoice by lastReminderAt against a seven-day cooldown, so running the sweep more often does not mail the customer more often. The timestamp is written only on a successful send, so a failed reminder is retried on the next run instead of silently consuming the cooldown window.
This job is disabled by default. It emails real customers. It honours
dryRun, which performs no writes and sends no email, and the job fails loudly if a dry run is not echoed back.
Reporting
| Figure | How it is derived |
|---|---|
| MTD / YTD revenue | Sum of payments received in the period. |
| Total outstanding | total − paidAmount across SENT, VIEWED, PARTIAL, OVERDUE. |
| Overdue amount and count | The same set, filtered to dueDate < now. Derived from the due date, not read off the status, so it is correct between sweep runs. |
| Aging | Four buckets — current (not yet due), 1–30, 31–60, 60+ days past due. Exhaustive and disjoint: they always sum to total outstanding. |
| DSO | (outstanding ÷ credit sales over the trailing 365 days) × 365. null when nothing was invoiced in the window. |
| Collection rate | Payments this year ÷ invoiced this year, as a percentage. null when nothing was issued this year. |
| Top clients | Top 10 organizations by paid revenue. |
null means “no data”. It is never rendered as 0, because a tenant with no invoices has no collection rate — showing 0% would read as “we collect nothing”.
Configuration
| Setting | Where | Default | If unset |
|---|---|---|---|
| Invoice prefix | /msp/settings/invoicing | INV | Numbers read INV-00001. |
| Number width | /msp/settings/invoicing | 5 | Five digits, zero-padded. |
| Organization country / province | Organization record | Country CA, province empty | A Canadian org with no province cannot be invoiced until one is set. |
| Account default currency | Account record | CAD | Invoices are stamped CAD. |
| Organization sell currency | Organization record | unset | Falls back to the account default. |
Plan tiers
Invoicing carries no plan gate. Every tier, including Free, gets all of it. The only money-related plan limit anywhere in the catalogue is limits.maxContracts, which belongs to Contracts.
MSP and managed clients
A managed client is a separate account from the MSP that services it, linked by Account.managedByAccountId or an active management link.
Invoice reads and writes use a widened tenant boundary: MSP staff span their own account plus every client account they service. That is what makes “invoice my client” work at all — the invoice belongs to the client’s account, not the MSP’s.
Two consequences worth knowing:
- The MSP invoice list shows the MSP’s own invoices alongside its clients’. Filtering by a client in the switcher narrows by that client’s account.
- Tenant-owned configuration does not widen. Your invoice numbering format is yours; it is not shared with, or inherited from, a client you service.
An unrelated MSP gets a 404 on your invoices, verified by the cross-tenant isolation suite.
Troubleshooting
| Message | Cause | Fix |
|---|---|---|
Cannot determine tax: this Canadian organization has no province set. | The org’s country is CA and its province is blank. | Set the province on the organization, or pass an explicit taxRate. |
Organization not found or access denied | The organization is outside your tenant boundary, or the id is wrong. | Check you service that client. |
No billable, approved, un-invoiced time entries to bill | Time exists but is non-billable, unapproved, or already invoiced. | Approve the time and mark it billable first. |
Cannot void an invoice with payments applied. Issue a credit note instead. | paidAmount > 0. | Issue a credit note. |
Cannot void a paid invoice / Invoice is already void | Terminal status. | Nothing to do. |
Cannot send a void invoice / Cannot send a paid invoice | Re-issuing a closed receivable. | Raise a new invoice. |
Cannot modify paid invoice / Cannot modify a voided invoice | Editing a terminal invoice. | Raise a new invoice or a credit note. |
Invoice prefix must be uppercase letters and digits, optionally separated by single dashes | The prefix contains spaces, slashes, or dots. | Use [A-Z0-9] and single dashes. |
Invoice prefix cannot be empty | Blank prefix. | Supply one. |
Payment amount exceeds balance due | Recording more than is owed on an accounting document. | Split the allocation. |
Document number already exists | Duplicate document number within your account. | Use a different number. |
Only draft documents can be deleted. Void the document instead. | Deleting an issued accounting document. | Void it. |
| Invoice email never arrives | The organization has no active primary contact. | Set a primary membership on the organization. |
Limits and known behaviour
- The legacy global invoice number is retained.
Invoice.number— a single sequence shared across every tenant — still exists on every row and is still what pre-existing invoices display. Invoices issued before per-tenant numbering were deliberately not renumbered: customers hold those documents and, in Canada, the records must stay stable. New invoices get a per-tenant number; old ones keep theirs. Both are searchable. VIEWEDis never set. No code marks an invoice as viewed by the customer. It is accepted in filters and reporting for completeness only.- There is no
WRITTEN_OFFstatus. Use a credit note. - US and non-Canadian tax is 0%. Not a bug and not global coverage — there is no tax provider integrated. Use the override for those clients.
- No line-item editing after creation. A draft’s due date and notes can be changed; its lines cannot. Void and re-raise.
- No recurring invoices here. Scheduled and contract-driven billing is Contracts & Recurring.
- Payment reminders need the job armed. Disabled by default.
- Aging bucket names. The reporting keys are
current,over30,over60,over90, meaning not-yet-due, 1–30, 31–60 and 60+ days past due respectively.
Related
- Payments — recording and reconciling money received.
- Credit Notes & Client Billing — reducing a receivable after payment.
- Contracts & Recurring — scheduled and contract-driven invoice generation.
- Quoting — accepted quotes convert into invoices.