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

ConceptWhat it is
InvoiceA receivable addressed to one organization. Carries a status, a due date, a currency, a tax rate and a set of line items.
Invoice numberThe customer-facing document number, e.g. ACME-00042. Counted per tenant from your own sequence. Immutable once issued.
Line itemDescription, quantity, unit price, and a per-line taxable flag. The line amount is computed for you.
StatusDRAFT, SENT, VIEWED, PARTIAL, PAID, OVERDUE, VOID.
OrganizationWho the invoice is billed to. Also supplies the address that determines the tax rate.
AccountThe tenant that owns the invoice. A managed client is a separate account from the MSP that services it.
AgingWhere 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

Actionplatform administratorMSP administratorMSP technicianorganization administratorpower userCUSTOMER
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

  1. Go to Invoices in the MSP portal (/msp/invoices), then Create Invoice.
  2. Pick the client organization. If you have a client selected in the client switcher it is preselected for you.
  3. Set the due date. Defaults to 30 days out.
  4. 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.
  5. 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.
  6. Save as draft, or Create & send to issue and email it in one step.

Bill unbilled ticket time

  1. Open the client at Clients → <client> (/msp/clients/[id]).
  2. The Unbilled time panel shows the count and value of time that is billable, approved, and not yet invoiced.
  3. 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.
  4. 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

  1. Open the invoice at Invoices → <number>.
  2. Send Invoice issues it, records sentAt, emails the organization’s primary contact, and queues a QuickBooks sync.
  3. When money arrives, Record Payment. Status moves to PARTIAL or PAID automatically from the payments on file.
  4. If it goes past due, the aging sweep (§8) marks it OVERDUE and emails a payment reminder.
  5. 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

  1. Invoices → Numbering settings (/msp/settings/invoicing).
  2. Set a prefix (uppercase letters and digits, dashes allowed, up to 12 characters) and a number width (1–10).
  3. The page shows exactly what the next invoice will be numbered.
  4. 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 … RETURNING inside 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-100000 on 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
ON13% HST
NB, NS, PE, NL15% HST
BC, MB5% GST + 7% PST = 12%
SK5% GST + 6% PST = 11%
QC5% GST + 9.975% QST = 14.975%
AB, NT, NU, YT5% 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:

  1. Aging. Any invoice that is SENT, VIEWED or PARTIAL, is past its due date, and still has a balance becomes OVERDUE. DRAFT, PAID and VOID are never touched. An invoice settled in full is never aged, whatever its status says.
  2. Dunning. Overdue invoices with an outstanding balance and a primary contact get the invoice_reminder email, 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

FigureHow it is derived
MTD / YTD revenueSum of payments received in the period.
Total outstandingtotal − paidAmount across SENT, VIEWED, PARTIAL, OVERDUE.
Overdue amount and countThe same set, filtered to dueDate < now. Derived from the due date, not read off the status, so it is correct between sweep runs.
AgingFour 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 ratePayments this year ÷ invoiced this year, as a percentage. null when nothing was issued this year.
Top clientsTop 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

SettingWhereDefaultIf unset
Invoice prefix/msp/settings/invoicingINVNumbers read INV-00001.
Number width/msp/settings/invoicing5Five digits, zero-padded.
Organization country / provinceOrganization recordCountry CA, province emptyA Canadian org with no province cannot be invoiced until one is set.
Account default currencyAccount recordCADInvoices are stamped CAD.
Organization sell currencyOrganization recordunsetFalls 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

MessageCauseFix
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 deniedThe organization is outside your tenant boundary, or the id is wrong.Check you service that client.
No billable, approved, un-invoiced time entries to billTime 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 voidTerminal status.Nothing to do.
Cannot send a void invoice / Cannot send a paid invoiceRe-issuing a closed receivable.Raise a new invoice.
Cannot modify paid invoice / Cannot modify a voided invoiceEditing a terminal invoice.Raise a new invoice or a credit note.
Invoice prefix must be uppercase letters and digits, optionally separated by single dashesThe prefix contains spaces, slashes, or dots.Use [A-Z0-9] and single dashes.
Invoice prefix cannot be emptyBlank prefix.Supply one.
Payment amount exceeds balance dueRecording more than is owed on an accounting document.Split the allocation.
Document number already existsDuplicate 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 arrivesThe 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.
  • VIEWED is 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_OFF status. 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.

  • 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.

Questions this guide did not answer?

Ask us. You will get a reply from someone who uses the product every day.

Book a demo Contact us

A 30-minute walkthrough against your own workflow. No slides.