Skip to content

MSP Billing

Use the staff procedure for day-to-day actions, the billing rationale for the meaning of the implemented boundaries, and record maintenance for reviewed agreement and historical-data operations.

Last updated: 2026-10-01.

This page documents the implemented business rules for MSP invoice generation and Service Line reconciliation.

The MSP invoicing process simplifies recurring invoicing for managed services customers. The Client Service is the contract context for the client and billing window; Service Lines and Products define what gets billed. MSP Seats belong to the Account and supply its dated population of supported people.

The model is intentionally simple: keep the commercial relationship on the plan, keep invoice items visible as invoice lines, and use Product defaults unless a client-specific override is needed.

The retained modules now display Client Services and Service Lines, using Client_Services and Service_Lines module APIs. MSP-specific dates, tiers and source lookup APIs remain compatible. See the CRM cutover for preserved identities and the reviewed existing-record mapping.

Legacy generation requires each configured line's Billing_Authority to be Legacy MSP. Billing Events and Hold lines are excluded even when their dates overlap the month. Blank or unknown authority blocks generation for that Service pending review. New lines default to Hold; choosing a Product or an active Service does not grant billing ownership.

A draft refresh also stops when an existing invoice item belongs to a configured line that is now outside legacy ownership. Older items without a source lookup are held if their Product matches such a line. This prevents a legacy refresh from removing or rewriting a charge owned by another billing path.

MSP contact eligibility separately requires MSP_Seats_Enabled to be true and Service Status Active. That flag does not change invoice quantities or select charges; the contact lifecycle owns this rule.

Definitions

Managed Service Plans

Client Services are CRM records that represent a managed services client's recurring billing relationship. The plan identifies the client account and billing window; it does not itself decide every invoice line.

Invoice contents are controlled by Service Lines linked to the plan. Each line links to a Product, which supplies the default invoice label, unit price, and Xero account code. The invoice line controls the billing dates, quantity for non-seat charges, and any client-specific overrides.

Seat Products are the special case. For Products whose code starts with MSP-SEAT-, invoice generation calculates quantity from billing-eligible MSP Seats on the Service's Account whose Billing_Start / Billing_End range overlaps the invoice month.

All other charges are invoice-line-controlled. This includes base charges, extra devices, licenses, subscriptions, discounts, adjustments, and special commercial conditions.

The current special Product groups are:

  • Monthly seat Products: MSP-SEAT-SB, MSP-SEAT-L1, MSP-SEAT-L2, MSP-SEAT-L3.
  • 12-month annual seat Products: MSP-SEAT-ANNUAL-SB, MSP-SEAT-ANNUAL-L1, MSP-SEAT-ANNUAL-L2, MSP-SEAT-ANNUAL-L3.
  • Annual seat prorate Product: MSP-PRORATE-SEAT.

Prorated annual seat additions use MSP-PRORATE-SEAT. This Product code does not start with MSP-SEAT-, so the generator keeps the invoice-line Quantity instead of calculating quantity from all active seats.

Recurring monthly lines normally have a Start_Date and blank End_Date. Annual seat Products use Start_Date as the renewal-month anchor and recur every 12 months when End_Date is blank. Prorate charges are represented by bounded invoice lines whose Start_Date and End_Date fall inside the one invoice month where the charge should appear.

For non-seat charges that are not dynamically modeled, operators use the invoice-line Quantity and update it when needed.

The process supports monthly recurring lines and 12-month annual seat lines. For special cases such as a six-month upfront arrangement, use an invoice line, usually the Special Condition Product (MSP-SPE), rather than a separate billing cadence.

MSP Invoices

At a high level, each recurring managed services contract should have one Client Service in CRM. Invoice generation builds a monthly preview or invoice from the applicable Service Lines for that plan.

Seats are currently the only dynamic quantity source. Device, license, subscription, discount, adjustment, and other non-seat charges use the invoice-line Quantity.

Seat A seat represents a person covered by managed services. It links directly to an Account and a CRM Contact. The Account is the billing ownership source; the Contact's current Company does not silently move an existing seat.

Service Lines remain attached to their Service. Seat Products use the Account of that Service to find the shared roster. A live seat with no Account blocks generation until its ownership is repaired. The retained legacy Service lookup is migration evidence and does not select the roster. Existing duplicate seats remain separate billing records until explicitly reviewed; neither migration nor generation merges them.

Seats have Billing_Start and Billing_End dates, which control whether the seat is counted in an invoice cycle. When a person is off-boarded, add a Billing_End date rather than deleting the seat.

Device Device charges are currently represented by Product-backed Service Lines. Store the device quantity on the invoice line and use Description_Override when device details need to appear on the invoice.

Source of Truth

CRM is the operational source of truth for:

  • Client Services;
  • MSP Seats;
  • Service Lines;
  • generated CRM invoice records and their Xero references.

Service Lines are also the operational charge memory for recurring annual seat renewal lines and one-off seat prorate lines. A generated invoice remains the accounting snapshot once reviewed and approved in Xero.

Xero is the accounting system of record. The automation creates or refreshes Xero invoices only while they are DRAFT; approval and sending remain finance team actions outside this automation.

Invoice Generation

Users can generate or refresh:

  • one plan invoice from a Client Service button;
  • all monthly plan invoices from the Client Service list button.

Rules currently implemented:

  • The generated invoice month is explicit when supplied. If not supplied, the route defaults by Australia/Sydney date: days 1-20 use the current month and days 21+ use the next month.
  • Monthly generation is controlled by the Client Service billing date range and each Service Line's applicability rule, not by Plan_Status or a plan-level manual-generation flag.
  • Billing_Start is required for recurring billing. Billing_End is optional; when blank, the plan is treated as ongoing. When filled, invoices generate only for months that overlap the plan billing range.
  • Client Service Plan_Type is metadata. Staff may edit plan-type labels without changing billing behavior.
  • This legacy automation processes reviewed recurring MSP charges only. One-off onboarding or project invoices should remain outside this automation and be generated through Deal-led invoicing. If finance created that invoice in Xero first, use the reviewed Project Invoice Reconciliation process to mirror it into CRM with Commercial_Deal; do not also link it to an MSP Plan.
  • The generator refuses live writes only for structural gaps that prevent a usable CRM/Xero invoice from being created.
  • Non-structural setup gaps are reported as warnings in the success message and preview report, but the automation still creates or refreshes the draft.
  • CRM button popups show only the warning text when warnings exist.
  • A new generated invoice creates:
    • a CRM Invoice;
    • a Xero DRAFT invoice;
    • CRM fields linking the CRM Invoice to the Xero invoice.
  • Existing generated invoices can be refreshed only while the linked Xero invoice is still DRAFT.
  • Once the linked Xero invoice is no longer DRAFT, the automation returns locked and does not mutate CRM or Xero.
  • If an existing generated CRM invoice has no Xero ID, the automation creates and links a Xero draft only when the CRM invoice is still in the generated editable state.
  • Duplicate protection uses the MSP invoice key for the plan and month.

Business reasoning:

  • CRM can be regenerated during finance review, but Xero becomes authoritative after the draft moves forward.
  • Exceptional or pre-billed one-off items should be represented by bounded or out-of-range Service Lines so ongoing monthly items can still be invoiced.
  • Structural validation prevents unusable accounting records, while warnings let finance create a draft and then fill in or correct incomplete line details during review.

Invoice Lines

Current invoices generate one line for each applicable Service Line. Non-annual lines are applicable when their date range overlaps the invoice month. Annual seat lines are applicable only in the first renewal invoice month and each 12-month anniversary after that. Invoice behavior is derived from the linked Product code, not from a separate line-type field or from the plan type.

Rules currently implemented:

  • Service Line Start_Date is required for invoice generation. Lines missing Start_Date are excluded and reported for review.
  • Service Line End_Date is optional. A blank End_Date means the line is ongoing.
  • Non-annual lines are applicable when their Start_Date / End_Date range overlaps the invoice month.
  • Product line labels come from Description_Override when filled, otherwise from the Product default invoice label, otherwise from the Product name.
  • Unit prices come from MSP_Invoice_Lines.Unit_Price_Override when filled, otherwise from Product Unit_Price. If neither value is available, the line is still generated with a 0.00 price and a warning.
  • Xero account codes come from MSP_Invoice_Lines.Xero_Account_Code_Override when filled, otherwise from Product Xero_Account_Code. If neither value is available, the line is still generated with a blank Xero account code and a warning.
  • Seat-line behavior is identified by a linked Product whose code starts with MSP-SEAT-.
  • Current seat Products are MSP-SEAT-SB, MSP-SEAT-L1, MSP-SEAT-L2, and MSP-SEAT-L3.
  • Annual seat Products are MSP-SEAT-ANNUAL-SB, MSP-SEAT-ANNUAL-L1, MSP-SEAT-ANNUAL-L2, and MSP-SEAT-ANNUAL-L3.
  • Annual seat Product Start_Date must be the first day of the first renewal invoice month. With blank End_Date, the line appears only in that month and every 12 months after it.
  • Annual seat Product descriptions get a generated covered-period line for the invoice year.
  • Seat-line quantity is calculated from MSP Seats on the Service's Account whose Billing_Start / Billing_End range overlaps the invoice month.
  • Billing_Status does not drive invoice seat quantity at this stage.
  • If a seat line's stored Quantity differs from the calculated billing-eligible seat count, the difference is kept in preview/report output but is not shown as a warning.
  • One-off prorated annual seat additions use Product MSP-PRORATE-SEAT. This line type keeps invoice-line Quantity, normally 1, and the prorate amount comes from Unit_Price_Override.
  • Seats missing Billing_Start are excluded and reported for review.
  • Non-seat lines missing Quantity are still generated with a blank quantity and a warning.
  • If Include_Users_Invoice is true on the plan, billing-eligible seat names are included in the seat-line description.
  • If a historical imported seat-line description has an old numbered staff list, the generator replaces it with the current billing-eligible seat list and records a non-blocking warning.
  • Device details should be stored on the device line's Description_Override when they need to appear on invoices.

Common causes for invoice preview needs_review:

  • Account is missing Xero_Contact_ID.
  • Plan is missing Billing_Start.
  • No applicable Service Lines exist for the month.
  • An applicable invoice line is missing Product or references a Product that was not found.

Common non-blocking warnings:

  • One or more MSP Seats are missing Billing_Start and were excluded.
  • One or more Service Lines are missing Start_Date and were excluded.
  • An applicable invoice line is missing Quantity, a resolved unit price, or a resolved Xero account code.

Yearly Seat and Prorate Operations

Operators use Service Lines as visible charge memory. Annual seat renewal lines recur yearly from their renewal-month anchor; prorate lines stay bounded to one invoice month.

For a yearly seat renewal:

  • Confirm the Account's MSP Seats have correct Billing_Start and Billing_End values.
  • Create or update one annual seat Service Line.
  • Link it to the matching annual seat Product, such as MSP-SEAT-ANNUAL-L2.
  • Set Start_Date to the first day of the first renewal invoice month.
  • Leave End_Date blank to renew every 12 months. Fill End_Date only when the recurring annual charge should stop.
  • Leave Quantity blank or use it only as a reference; generated quantity comes from billing-eligible MSP Seats in the renewal invoice month.
  • Set Description_Override to the base label if needed; the generator appends the covered annual period.
  • Generate or refresh while the linked Xero invoice is still DRAFT.

For a mid-period annual seat addition:

  • Create the MSP Seat with the billable Billing_Start.
  • Create a one-off Service Line linked to MSP-PRORATE-SEAT.
  • Set Quantity to 1.
  • Set Unit_Price_Override to the calculated prorate amount.
  • Set Start_Date to the invoice month and End_Date to the last day of that same month.
  • Set Description_Override to identify the person and covered period.
  • Search existing Service Lines for the same person and covered period before creating the prorate line.

Use monthly proration unless the commercial agreement requires daily proration. The recommended monthly formula is:

annual seat price * remaining full months / 12

If the customer should be charged for the current partial month, count that as a full remaining month. If the customer should not be charged until the next month, start the remaining-month count from the next month.

Draft Refresh

When refreshing a generated draft:

  • Existing CRM items are matched to their configured MSP charge when linked. Older items without that link are matched by unique Product, or by unique Product and description where a Product appears more than once. Ambiguous matches require review before either invoice changes.
  • Matched CRM items retain their IDs and existing service/coverage annotations when charges are reordered or added. Only removed charges are deleted; new charges receive new items. Native item display order is not used as identity.
  • Before either write, the linked Xero draft must agree with the current CRM invoice's customer, currency, dates, totals, descriptions, quantities and prices. Discounts, tax differences, changed Xero invoice references/numbers, missing snapshot values or unverifiable accounting codes require reconciliation.
  • Xero account, tax and item codes must agree with the billing configuration. Ended charges can be removed when their retained configuration is identifiable and their existing invoice values reconcile. Physically deleted or ambiguous configurations require review. Reconciliation must also update the MSP charge configuration where a Xero edit should carry into future generated invoices.
  • CRM and Xero are read again before the first write. A change during that check stops regeneration. The two systems do not support one shared atomic transaction; this check cannot prevent edits made after the final read.
  • The linked Xero draft is updated from the current CRM preview data.

Business reasoning:

  • Staff can generate, review, adjust Service Lines, and regenerate while finance has not approved or sent the Xero invoice.
  • Once the Xero invoice is no longer a draft, the automation avoids rewriting accounting history.

Service Line Reconciliation

When an Service Line is created or edited, the compatibility middleware route may still be called by CRM workflow, but date fields are now operator owned.

Rules currently implemented:

  • The route does not set Start_Date.
  • The route does not set or clear End_Date.
  • Invoice generation uses the invoice-line date range to decide applicability.
  • Price and account-code override fields are left unchanged. Blank override fields intentionally allow invoice generation to use the linked Product defaults.

Default price and account-code rules now live on the linked Product. If a line has no override and the Product is missing its default price, invoice generation writes the draft with price 0.00 and reports a warning. If the Product is missing its Xero account code, invoice generation writes the draft with the account-code value blank and reports a warning.

Plan-Level Compatibility Route

The plan-level reconciliation route remains callable from CRM for compatibility, but it no longer updates invoice-line account-code overrides and does not read plan type values. Blank override fields now mean "use the linked Product default", and filled override fields are treated as manual line-level values.

Deliberate Limits

  • The automation does not approve or send Xero invoices.
  • An MSP billing run module is not implemented.
  • Scheduled monthly batch generation is not implemented.
  • Bi-directional post-create Xero state sync is not implemented.
  • Zoho Billing is intentionally not in the MSP invoice path.
  • Dynamic device counts are not implemented; device quantities remain invoice-line values.

References

  • Technical invoice docs: docs/systems/engineering/invoice-integration.md
  • Invoice-line setup notes: docs/notes/2026-05-29_msp_invoice_line_crm_implementation_plan.md
  • Yearly seat/prorate operating plan: docs/notes/260703_msp_invoicing_improvement.md
  • MSP import notes: msp_imports/README.md
  • Code: msp_imports/msp_invoices/service.py
  • Code: msp_imports/msp_invoices/reconcile.py
  • Routes: POST /zoho/msp-invoices/generate-plan, POST /zoho/msp-invoices/generate-all, POST /zoho/msp-invoices/reconcile-line, POST /zoho/msp-invoices/reconcile-plan-lines