PPM contracts (recurring maintenance)
A PPM contract (planned preventative maintenance) commits a customer to regular service visits on a defined cadence: annual gas safety checks, quarterly AC service, monthly fire-extinguisher walkarounds, biannual legionella inspections. concorbit generates the visit Jobs automatically ahead of each due date, inheriting the contract's customer, address, job type, and preferred technician.
Manage them at Jobs > PPM contracts.
Contract fields
| Field | What it drives |
|---|---|
| Customer | The Job created for each visit is assigned to this company |
| Name | Human-facing label, e.g. "Annual boiler service" |
| Frequency | weekly / monthly / quarterly / biannually / annually (defines the visit cadence) |
| Start date | First visit due |
| End date / Perpetual | When the contract expires. Perpetual contracts generate visits indefinitely until cancelled |
| Job type | Feeds into the generated Job: drives SLA targets, form template, callout fee (see Jobs settings) |
| Preferred technician | Default assignee on generated Jobs (dispatcher can override per-visit) |
| Annual price | Informational, used by JCP to compare contract revenue vs labour cost on generated visits |
| SLA (min) | Target completion time; flagged in reporting if visits routinely overrun |
| Generate N days ahead | How far ahead of next_visit_due the daily scheduler mints the Job. Default 7, so the Monday cron fires visits for the following Monday. 0 = same-day generation |
Visit auto-generation
The concorbit:generate-ppm-jobs command runs daily at 06:00. It scans every active contract whose next_visit_due <= today + auto_dispatch_days_before and:
Atomically claims the visit slot via a conditional
UPDATE ... WHERE next_visit_due = <current value>, so two workers running the command can't both mint a visit.Creates a Job via
JobService::createwith the contract's customer, address (copied from the Company), job type, preferred technician, and scheduled date =next_visit_dueat 09:00.Advances
next_visit_dueto the next occurrence based on frequency (+1 week, +1 month, +3 months, +6 months, +1 year).Stamps
field_jobs.ppm_contract_idso the Job links back: the job detail page shows a clickable PPM badge in the header, and the contract detail page lists every generated visit.
If the scheduler somehow fails for a day, the next day's run picks up any still-in-window contracts, so no visit gets permanently skipped because of a transient issue.
Pause / resume / cancel
Pause: visit generation stops immediately.
next_visit_dueisn't advanced while paused, so visits simply don't generate until you resume.Resume: back to active. The next visit generates on the next scheduler run if the due date is within the dispatch window.
Cancel: terminal. The daily scheduler skips cancelled contracts. Takes an optional reason logged on the contract for audit.
Renew: extend the
end_date+ flip status back to active. Useful for contracts that were allowed to expire before the customer signed a renewal.
Billing
V1 ships contract storage + visit generation. Billing automation is intentionally decoupled:
Per-visit billing (most common for MSPs): each generated visit runs through the normal Jobs invoice flow: time entries create Charges via the JCP pipeline, and the dispatcher generates a per-visit invoice.
Annual up-front billing: link the contract to a
ClientSubscriptionvia thesubscription_idfield. The Subscription handles the recurring invoice independently of visit generation. V1 requires manual linking; an auto-create flow is a follow-up session.
Contract revenue flows into the JCP dashboard's Field Jobs tab via the generated visits' Charges.
Reporting
The contract detail page shows visit history with dates and completion state. Platform-wide PPM reporting (renewal rate, churn, on-time service rate, most profitable contract types) is a follow-up session. For now, the JCP dashboard's Field Jobs tab provides the underlying per-visit profitability numbers.
Permissions
jobs.contracts.view: read the contract list + detailjobs.contracts.manage: create, edit, pause, resume, cancel, renew
Not yet shipped
Per-asset tracking: contracts today cover a single service definition. Landing
ppm_contract_assets(boilers, extinguishers, AC units, each with its own service history) is a follow-up.Auto-create billing subscription: today you link a Subscription manually in the contract's DB record.
Renewal automation: 60-day-ahead auto-creation of a renewal quote + email is planned.