Skip to Content

Invoicing

Every milestone in an active project generates an invoice. The invoice workflow tracks the lifecycle of each payment from draft through approval to settlement, with structured paths for disputes and rejections.

Flow

Invoice Creation

The expert raises an invoice against a milestone from the Delivery section of the project workspace — invoices are not auto-created on milestone completion. The button appears on a milestone that is done and has no invoice yet; a milestone that already has one shows that invoice’s status in its place. It inherits its financial values from the parent quote:

FieldCalculationDescription
amountquote.total × (milestone.percentage / 100)The price for this milestone, net of tax — the quote’s customer price, platform fee included
deliveryCostquote.amount × (milestone.percentage / 100)The expert’s share of the delivery cost, which their earnings count
taxRatePercentthe tenant’s tax rate when the invoice is raisedStamped once; a later rate change never restates it
taxamount × taxRatePercent / 100, to the pennyThe tax on the price
totalamount + taxWhat the customer is billed
invoiceIdINV-<requestId>-<milestone order>The readable reference, e.g. INV-26-1
currencyquote.currencyThe unit every amount is in (EUR, USD or GBP)

Tax

Each tenant has one tax rate, commercial.taxRatePercent (0–100). Sustentus sets it from the tenant-management console, beside the platform markup; the tenant’s own admin cannot set or clear it. Unset means no tax. The rate in force when an invoice is raised is applied to its price and stamped on it, so changing the rate never alters an invoice already raised. There are no tax rules — one rate per tenant, on the invoice’s price.

Every invoice surface shows the price, the tax and the total. What the customer is billed — the invoice’s total, the workspace’s Billed and Paid figures, the customer’s payments page — includes the tax. Every revenue, earnings and performance figure — vendor revenue, the SDM figures, the expert’s earnings and figures, the metric dictionary’s money measures — reads the price, net of tax, so tax never counts as revenue — except the expert’s earnings and figures, which read the invoice’s deliveryCost, their share of the cost without the platform fee (see Quote & Acceptance).

The reference

An invoice is known everywhere by its reference, INV-<requestId>-<milestone order> — the lead’s request id, unpadded as the platform prints #<requestId>, and the order of the milestone it bills (INV-26-1 is the first milestone of request 26). It is stamped when the invoice is raised and is unique within the tenant, so the demo, the seed and the tests find an invoice by it and no screen prints an internal id. The Finances › Invoices search matches it.

Every invoice surface — the invoices list, the invoice detail and a person’s Finances tab — shows its amounts in the invoice’s own currency, and a person’s invoice totals are stated once per currency, never converted or summed across currencies. An invoice raised on a quote that predates quote currencies carries EUR, the quote’s default. A quote takes its currency from the customer it is drafted for — the unit that customer reports in — with EUR only when the customer carries none; the expert quote’s price is not converted, so it is read in that unit.

One invoice is created per milestone — enforced by a unique index on milestone. This means each milestone can only ever have one invoice, preventing duplicate billing.

Disputing an invoice also raises a delivery blocker on the engagement, so the dispute is visible in the team’s blocker queue until it is resolved (resolution returns the invoice to invoice_draft).

A raised invoice is a draft awaiting a manager, so only the project’s CSM and SDM are told about it. The customer learns of an invoice when a manager approves it and it reaches invoice_sent — telling them about a draft would announce a bill nobody has agreed to yet.

Manager Review

A drafted invoice does not reach the customer until a manager decides it. The decision sits on the invoice’s own row in the Invoices section of the project workspace: a drafted invoice offers Approve and Reject, and every other status offers nothing. Both are also available, alongside Dispute, from the Awaiting your approval list that leads the invoices tab under Finances. That list shows only the invoices the viewer can decide on — the whole tenant’s for an admin, their assigned leads’ for a manager — and is absent entirely when there are none.

DecisionResult
Approveinvoice_draft → invoice_sent. The invoice goes to the customer to pay.
Rejectinvoice_draft → invoice_rejected. Terminal — and because one invoice is created per milestone, that milestone cannot be billed again.

Approval is the finance hand-off. When an invoice is approved, the date is recorded on it, with the tenant’s finance system at that moment when it has one, and the invoice’s workspace row and detail page show “Sent to system on date” — or “Handed to finance on date” for a tenant with no finance system. The finance system is the tenant’s integration whose catalogue entry is a finance system — Xero today; Stripe is a payment processor and is not one — and is not disconnected; with several, the earliest added. An invoice approved before the hand-off was recorded on every approval (October 2026) shows no line when its tenant had no finance system. Nothing is exported and no system is called: the line records where the invoice passes, and there is no export, sync or reconciliation behind it.

Either the CSM or the SDM decides, and one decision settles it. Both hold the invoice.approve capability — which reject and dispute ride too — and a manager may act on any invoice on a lead they are assigned to, whether or not they are the invoice’s own recorded manager. Once either has decided, the invoice is no longer a draft, so the other sees the settled status with no decision to make.

Who is offered the decision follows the person’s own permissions, not just their role: the Awaiting your approval list appears exactly when the approve action would accept their click. An admin who revokes a manager’s invoice.approve hides the list from them, and one who grants it to someone individually shows it — scoped, as ever, to the leads they are assigned to.

What the Customer Sees

Only an invoice that has reached invoice_sent or invoice_paid is shown to the customer. A drafted, disputed or rejected invoice is theirs but has not reached them, so it is filtered out of every invoice surface they can open:

  • the Invoices section of the project workspace
  • the Finances › Invoices list, and an invoice opened directly by its id
  • their own project pages — the next-payment-due card and the at-risk strip’s overdue-invoice signal

On the workspace each invoice reads as its reference, milestone, price, tax, total, due date and status. The due date is the invoice’s own where it has one and its milestone’s otherwise. The customer is told when an invoice reaches them: approval notifies them with a link straight to that invoice, so a bill is never left to be found by digging through the finance list.

The Billed and Paid figures already counted only invoice_sent and invoice_paid, so they read the same for every persona.

Each row’s status is read from the invoice itself: a paid invoice reads Paid whatever else its record says, and the Billed and Paid figures follow the same rule as the rows, so they always agree. Paid shows in green on every surface that renders the status.

The workspace is refreshed by every invoice decision, wherever it is made. Paying from Finances › Invoices, approving, rejecting, disputing or resolving a dispute from Finances, and the expert raising an invoice all update that lead’s workspace, so a CSM opening it next sees the invoice as it now stands.

One gap remains: the customer’s activity feed still carries “Invoice raised” and “Invoice rejected” entries, so the feed can name an invoice the customer cannot open. That is tracked separately.

Invoice Status Workflow

StatusReadsTransition TypeMeaning
invoice_draftCreated—Raised by the expert against a milestone; awaiting manager approval
invoice_sentSentpositiveApproved by the manager; with the customer awaiting payment
invoice_disputedDisputednegativeManager has flagged an issue; loops back to draft after resolution
invoice_paidPaidpositivePayment confirmed; project value released
invoice_rejectedRejectednegativeInvoice rejected outright; terminal state

Every persona reads the same words, on every surface that shows an invoice’s status. Overdue is a fifth reading, not a status: a sent, unpaid invoice whose due date — its own, else its milestone’s — fell on a day before today (UTC) reads Overdue, in red, without anyone changing it. An invoice due today still reads Sent. Nothing stores Overdue, and nothing decides on it: an overdue invoice still counts as billed, is still paid with Pay exactly like a sent one, and the customer’s at-risk strip flags it on the same rule.

Payment Recording

The customer records payment from the Invoices section of the project workspace. A sent invoice carries Pay on its own row — per invoice, so a project billing two milestones at once settles the one the customer chose. Every other status offers nothing, and so does every other persona: the rule is an identity one, not a capability one, so an admin holding every permission on the platform is offered no control and would be refused if one were forced.

Pay records a payment that was made outside the platform. No money moves through Sustentus and no card details are captured. The control says so before it commits, because invoice_paid is terminal and nothing on any surface undoes it. A payment gateway is a separate future capability.

When an invoice is marked as paid:

  • paid = true is set on the invoice record
  • paidAt is required and must be provided — the model validates that paidAt exists whenever paid is true
  • isApproved = true is set to indicate the full approval and payment cycle is complete

Paying the final milestone’s invoice on an expert quote also starts the delivery-completion path — the lead’s positive status transition and the CSAT request to the customer. See delivery.

Invoice Fields

FieldTypeDescription
quoteQuote refThe quote this invoice is billed against
milestoneMilestone refThe completed milestone this invoice covers (unique)
leadLead refThe originating lead
expertUser refThe expert being paid
customerUser refThe customer being billed
managerUser refThe CSM managing the invoice
vendorUser refThe vendor account
amountNumberThe price for this milestone, net of tax
taxNumberThe tax on amount; absent (read as 0) on an invoice raised before tax existed
taxRatePercentNumberThe tenant’s tax rate when the invoice was raised
totalNumberWhat the customer is billed: amount + tax
invoiceIdStringThe reference, INV-<requestId>-<milestone order>; unique per tenant. Absent on an invoice raised before it existed — that invoice shows the same reference, derived when it is read
financeHandoffObject{ system?, at } — when the invoice was approved, and the finance system it passed to when the tenant had one; absent before approval
currencyStringEUR, USD or GBP — the quote’s currency, stamped when the invoice is raised and never edited
statusStringCurrent invoice status name
paidBooleanWhether payment has been received
paidAtDateRequired when paid = true
dueDateDateOptional payment due date; falls back to the linked milestone’s due date when unset

Notifications

Key invoice events trigger platform notifications to the relevant parties:

EventNotification KeyRecipients
Invoice raisedinvoice-raisedThe lead’s managers
Invoice approvedinvoice-approvedExpert, Customer
Invoice disputedinvoice-disputedExpert, the lead’s managers
Invoice rejectedinvoice-rejectedExpert
Payment confirmedinvoice-paidExpert, Customer, the lead’s managers

Every one of these links to the invoice itself (/finances/invoices/<id>) — the record its title names and its “Open invoice INV-…” button promises — for every recipient. The invoice page reads each viewer’s finance scope: the lead’s managers open any invoice, the expert their own, and the customer theirs once it has been sent or paid, which is when they are told of it.

Last updated on