MSP Invoicing
Last updated: 2026-07-03.
This page documents the implemented business rules for MSP invoice generation and MSP Invoice Line reconciliation.
This process is in place to simplify the management and invoicing of the managed service customers.
The plan should be the database of reference for the information about the contract and operations of maintaining that commercial relationship.
Its aim is to be built on simple and clear foundations that can be use in flexible ways.
Definitions
Managed Service Plans
Managed Services Plans are CRM modules to represent the commercial relationship between Peppermint IT and a client.
It is composed of the following elements: 1. A per seat support price, and a set of seats 2. A per device support price, and a set of devices. All seats are associated with a device, but there can be additional devices. 3. Any number of licensing / security and other products.
The main driver are products, on the managed services categories.
Products must contain: - price - xero account code - label
They are then associated with a quantity inside an invoice line.
At this stage all products are constructed simply : - add an invoice line - choose a base product (Managed Services Category) - This will load a default Xero Account code and price - override if you want - Add a quantity - control the exact test displayed on the Xero invoice - control is active or not via billing start / billing end dates - NOTE: this might not be the right way because this should be a present snapshot of the next invoices. By design the invoices are the track, but the invoice lines are the active, current lines.
Special seat products use a Product code beginning with MSP-SEAT-. The
generator calculates quantity for these lines from billing-eligible MSP Seats.
Annual seat Products are the special recurring annual set:
MSP-SEAT-ANNUAL-SB, MSP-SEAT-ANNUAL-L1, MSP-SEAT-ANNUAL-L2, and
MSP-SEAT-ANNUAL-L3.
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 items we will eventually model but have not yet modeled, operators can use the quantity field in the invoice line and update it when needed.
Similarly, billing cycles supported are only annual or monthly. For special cases like 6 month upfront options, use the Special Condition product.
MSP Invoices
At a high level, for each contract with a managed service client, we have a corresponding managed services plans record in CRM.
Each plan is associated to products via Invoice lines.
At the core of the system, is the automatic calculation of quantity for dynamic items like seats and devices. The intent is that eventually, those seats number will change through well-controlled processes.
As a starting point, we use mechanisms around the invoice lines and progressively more supported dynamic items. The first one is Seats.
Seat A seat represents a physical person that we manage services for along with their paired device.
It must tie to a Contact.
Seat have important billing start and billing end dates, that are used to control whether it is counted in an invoice cycle. When a person is off-boarded, we add a billing end date rather than delete the seat.
Device Next candidate to represent in the model. Ties to a physical device managed through Ninja One.
Source of Truth
CRM is the operational source of truth for:
- Managed Services Plans;
- MSP Seats;
- MSP Invoice Lines;
- generated CRM invoice records and their Xero references.
MSP Invoice 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 Managed Services Plan button;
- all monthly plan invoices from the Managed Services Plan 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 Managed Services Plan billing date
range and each MSP Invoice Line's applicability rule, not by
Plan_Statusor a plan-level manual-generation flag. Billing_Startis required for recurring billing.Billing_Endis optional; when blank, the plan is treated as ongoing. When filled, invoices generate only for months that overlap the plan billing range.- Managed Services Plan
Plan_Typeis metadata. Staff may edit plan-type labels without changing billing behavior. - Managed Services Plans cover recurring invoice items only. One-off onboarding or project invoices should remain outside this automation and be generated through Deal-led invoicing.
- 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
DRAFTinvoice; - 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 returnslockedand 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 MSP Invoice 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 MSP Invoice 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:
- MSP Invoice Line
Start_Dateis required for invoice generation. Lines missingStart_Dateare excluded and reported for review. - MSP Invoice Line
End_Dateis optional. A blankEnd_Datemeans the line is ongoing. - Non-annual lines are applicable when their
Start_Date/End_Daterange overlaps the invoice month. - Product line labels come from
Description_Overridewhen filled, otherwise from the Product default invoice label, otherwise from the Product name. - Unit prices come from
MSP_Invoice_Lines.Unit_Price_Overridewhen filled, otherwise from ProductUnit_Price. If neither value is available, the line is still generated with a0.00price and a warning. - Xero account codes come from
MSP_Invoice_Lines.Xero_Account_Code_Overridewhen filled, otherwise from ProductXero_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, andMSP-SEAT-L3. - Annual seat Products are
MSP-SEAT-ANNUAL-SB,MSP-SEAT-ANNUAL-L1,MSP-SEAT-ANNUAL-L2, andMSP-SEAT-ANNUAL-L3. - Annual seat Product
Start_Datemust be the first day of the first renewal invoice month. With blankEnd_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 whose
Billing_Start/Billing_Endrange overlaps the invoice month. Billing_Statusdoes not drive invoice seat quantity at this stage.- If a seat line's stored
Quantitydiffers 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-lineQuantity, normally1, and the prorate amount comes fromUnit_Price_Override. - Seats missing
Billing_Startare excluded and reported for review. - Non-seat lines missing
Quantityare still generated with a blank quantity and a warning. - If
Include_Users_Invoiceis 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_Overridewhen 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 MSP Invoice 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_Startand were excluded. - One or more MSP Invoice Lines are missing
Start_Dateand 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 MSP Invoice 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 plan's MSP Seats have correct
Billing_StartandBilling_Endvalues. - Create or update one annual seat MSP Invoice Line.
- Link it to the matching annual seat Product, such as
MSP-SEAT-ANNUAL-L2. - Set
Start_Dateto the first day of the first renewal invoice month. - Leave
End_Dateblank to renew every 12 months. FillEnd_Dateonly when the recurring annual charge should stop. - Leave
Quantityblank or use it only as a reference; generated quantity comes from billing-eligible MSP Seats in the renewal invoice month. - Set
Description_Overrideto 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 MSP Invoice Line linked to
MSP-PRORATE-SEAT. - Set
Quantityto1. - Set
Unit_Price_Overrideto the calculated prorate amount. - Set
Start_Dateto the invoice month andEnd_Dateto the last day of that same month. - Set
Description_Overrideto identify the person and covered period. - Search existing MSP Invoice 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:
- If the CRM invoice line product order still matches the generated preview, existing CRM line item IDs are preserved and updated in place.
- If the product order has changed, old generated CRM line items are replaced with regenerated line items.
- The linked Xero draft is updated from the current CRM preview data.
Business reasoning:
- Staff can generate, review, adjust MSP Invoice 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.
MSP Invoice Line Reconciliation
When an MSP Invoice 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/dev/xero_crm_invoice_sync.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