Skip to content

MSP Plan agreement migration

msp_imports.plan_agreements adds four optional current-agreement fields to Managed_Services_Plans and fills reviewed values on explicitly selected Plans. It reuses each Plan's identity and original Deal. The operating rules define their meaning and distinguish agreement dates from billing dates.

Capture and prepare

Use a fresh private directory for each capture. The capture mode refuses to overwrite existing source evidence and uses a CRM client that rejects business writes. Supply only the Plan IDs being reviewed.

python -m msp_imports.plan_agreements \
  --directory .local/msp-plan-agreements \
  --capture --plan-id <plan-id> --plan-id <another-plan-id>

The command saves plans-and-metadata.json with the selected records and module field/layout metadata. Write decisions.json in the same private directory with owner-only permissions. The following identities and dates are synthetic:

{
  "10001": {
    "deal_id": "20001",
    "start": "2026-05-15",
    "accepted": "2026-06-05",
    "renewal": "2027-05-15",
    "evidence": "Private signed agreement and reviewed source reference"
  }
}

Each decision requires an existing Deal ID, three evidenced ISO dates and an evidence reference. Acceptance may follow commencement; renewal must follow commencement. Omit a Plan whose current agreement remains unresolved. A shared Company, Deal closing date or Plan billing start does not prove agreement terms.

python -m msp_imports.plan_agreements --directory .local/msp-plan-agreements

This local-only command writes preview.json: proposed field definitions, before/after values, preserved Plan fields, deferred IDs and a batch hash. Review the exact payload and its sources before application.

Apply and verify

python -m msp_imports.plan_agreements \
  --directory .local/msp-plan-agreements \
  --apply --expected-batch <reviewed-batch-hash>

The writer verifies the preview against its captured source and decisions. It checks all reviewed Plans and their agreement Deals before creating fields. Existing fields must have compatible types and the correct Deals lookup target; conflicting labels or API names stop the run. Missing fields are added to the Standard layout and checked by metadata readback.

Record updates are restricted to the preview's Plan IDs and four agreement fields, with trigger: [] and If-Unmodified-Since. Existing custom Plan values, identity, Company, original Deal, owner and currency must remain unchanged. Readback checks both the intended values and preserved fields. Missing or changed records, conflicting agreement values, an unrelated Deal or failed readback stop the run. There are no writes to other modules' business records.

journal.json records request intent, responses and verified records privately. applied.json records updated, already-matching and deferred Plan IDs. A rerun skips verified matching values and existing compatible fields. A partially completed run can therefore resume; differing human edits require a fresh capture and review. No destructive rollback or automatic deletion is implemented.

The optional fields exist across the Plan module. Only reviewed target records are filled; other Plans remain blank. The Deal lookup adds a related list on Deals without editing Deal records. Invoice generation and operational field layouts are otherwise unchanged.

Offline verification: tests/test_msp_plan_agreements.py covers preservation, missing evidence, date boundaries, customer mismatch, metadata conflicts, source drift, restricted writes and repeated application. Existing Plan creation and MSP invoice tests cover the operational behaviour alongside it.

API references: Zoho custom field creation and conditional record updates.