Account-owned MSP Seats
The Account owns the customer's dated roster of supported people. Each MSP Seat links to Account and Contact. A Service owns its commercial charges; monthly and annual seat Products calculate quantity from the roster on that Service's Account. The billing rules and staff procedure define invoice operations.
Ownership and compatibility
- Seat record IDs, Contacts, names, statuses, billing dates and form evidence are preserved. An agreement change does not require copying the roster.
- Seat
Accountis a direct lookup to Accounts, with an MSP Seats related list. The field is required for staff entry. The historicalPlanlookup is kept in Unused Items as recovery evidence; new seat writers do not populate it. - Service Lines retain their Service parent, Products, rates, date rules and billing authority. Invoice grouping, invoice keys and issued snapshots are unchanged. Seat billing still uses month overlap, rather than Billing Status.
- Annual renewals retain their anniversary month.
MSP-PRORATE-SEATretains its explicit line quantity; it does not consume the whole Account roster. - The contact lifecycle still requires exactly one active Service with MSP Seats Enabled before enrolment or lifecycle changes. Matching uses Account and Contact. Duplicates or seats missing Account require review.
- Imports resolve the selected Service's Account and write Account-owned seats. Current reports expose Account identity and derive compatibility Service columns only when attribution is unambiguous. Account seat counts and seat history count each seat record once, even with multiple eligible Services. Duplicate person records are not silently merged.
- Older private captures that omit the Account field can still be read through their historical Plan relationship. A blank live Account is not a fallback: invoice generation stops until ownership is repaired.
Each seat Product line deliberately consumes the Account roster. Before adding another service that charges the same support scope, review overlapping lines and dates to avoid charging that coverage twice. Subset rosters and negotiated minimum-seat rules require a separate design; they are not inferred from Contact Company, Service Role or Product names.
Migration evidence
The 1 October 2026 source capture contains 416 seats and 28 Services, with a separate Account for every Service. Every seat has a resolvable Service Account. The migration maps that billed Account directly, preserving the exact roster identities for every Service before and after the move.
Two duplicate Account/Contact groups are preserved. Fifty linked Contacts have
no Company and eighteen have a different Company. These are separate review
items: deriving ownership from the Contact or merging duplicates during this
migration would alter billing. Private source evidence and journals are kept
under ignored docs/local/msp-account-seats-20261001/; no customer details belong
in the published documentation.
The before/after comparison covers 48 invoice months from January 2025 through December 2028 using the captured configuration. Full previews match, including October 2026's 159 generated lines. This verifies the ownership change against that configuration; it does not certify future commercial edits or historical agreement accuracy. Verification creates no invoices and makes no Xero writes. The report comparison also preserves 26 current Service rows, 318 current seat rows, 447 Service-month rows, 5,436 seat-month rows and 26 company-size rows. Current seat exports add Account columns while retaining their existing Service joins for this scope.
Deployment verification
The 1 October 2026 cutover is installed in CRM. The initial middleware deployment
at /home/deployer/pepit used runtime commit 45fb3a1 on
codex/account-seat-deployment, containing only the twelve affected runtime files.
That branch is retained as recovery evidence. Routine deployment uses the
registry release pipeline. The complete source change
includes the tests and documentation; it does not require rerunning the CRM
migration. See the deployment flow.
The previous image is retained as pepit-app:before-account-seats-20261001, with
file before-images and hashes in /home/deployer/msp-account-seats-20261001/.
All 416 Account-only backfills passed full readback. The Account lookup is field
42659000018700058; Accounts exposes the MSP Seats related list. The Standard
Seat layout requires Account and Contact and excludes the legacy Service field
from staff entry. No seat was deleted or recreated.
Production health and four authenticated input-validation checks pass. Fresh
CRM inputs produce the original October preview hash
b5c38fbc0b468fdd6712454e11a1287d9f815bef4d75925fa3495afd28ec0765.
Single-Service and batch read paths agree. Live migrated seats also reproduce
all 48 captured month previews. The local suite passes 1,165 tests, both strict
documentation builds pass, and formatting and diff checks are clean. No invoices
were generated by these checks.
Administrative sequence
commercial_services.account_seats binds operations to the inspected MSP Seats,
Accounts and Client Services module IDs. Schema installation checks the lookup
target. The preview refuses any mapping that would change a Service's roster.
Apply accepts only Account lookup writes, requires the exact preview hash,
preflights all records and Service Accounts, disables workflow triggers and uses
If-Unmodified-Since per seat. Complete readback verifies all preserved inputs.
A resume skips matching after-images; drift requires a fresh review.
- Save a complete private capture of Seats, Services and Accounts, plus the existing billing configuration and GET-only invoice previews.
- Install the optional Account lookup and prepare the reviewed mapping.
- Backfill Account while the existing billing runtime still uses the preserved Service relationship. Capture and review any concurrent record changes.
- Build the tested replacement runtime. Pause affected middleware mutations, finish the complete seat readback, then switch the runtime and layout together.
- Require Account, make the legacy Service field optional and move it to Unused Items. Verify Account related-list metadata, authenticated health, both invoice entry points' read paths and full billing previews. Resume operations.
python -m commercial_services.account_seats --install-field --directory PRIVATE_DIR
python -m commercial_services.account_seats --directory PRIVATE_DIR
python -m commercial_services.account_seats --apply --directory PRIVATE_DIR --expected-batch REVIEWED_HASH
python -m commercial_services.account_seats --configure-layout --runtime-deployed --directory PRIVATE_DIR
The preview reads source-before.json with complete MSP_Seats, Client_Services
and Accounts collections. Preserve the original capture; the apply journal and
readback are separate files. The runtime-deployed flag acknowledges that the
Account-based writers are ready before the layout changes.
The installer follows Zoho's custom-field API and layout API. Moving the legacy field to Unused Items preserves its data; it is not permanent field deletion. The earlier universal layout installer excludes Seat ownership.
Recovery
Pause affected mutations before restoring the previous runtime and layout. Existing seats retain their old Service lookup. Any seats created after cutover need a reviewed Service mapping before an old Plan-based reader can use them; restoring an old image alone is not sufficient. Preserve new Account ownership, billing dates and later edits. Do not delete seats or rewrite issued invoices.
Repository consumers cover billing, contact middleware, staff imports, boarding form reconciliation, MSP reports and the tracked Power Query extracts. External Flow, marketplace and independently deployed BI definitions remain part of the external-consumer audit.