Service Contracts
Recurring service agreements — generate work orders on a cadence, track included visits, enforce SLAs.
A service contract is a recurring agreement with a customer to provide periodic service — quarterly HVAC maintenance, monthly inspections, annual safety audits, whatever the cadence. Workestra generates the visits automatically; you can also link individual ad-hoc work orders to the contract for billing or reporting.
When to use a service contract
| Use a contract when… | Don't bother when… |
|---|---|
| The same customer needs the same service repeatedly on a schedule | One-off jobs (raise a work order directly) |
| You want SLA terms (response + resolution times) auto-applied to every related WO | You quote and invoice each job individually |
| You want a "next visit due" date that auto-rolls | You want full pricing flexibility per job |
A contract's monthly fee and/or included-visit count can also drive billing automation downstream (full Finance bridge queued for Phase 4.8).
Creating a service contract
Go to /fsm/service-contracts → New service contract.
| Field | Notes |
|---|---|
| Number | Auto-allocated SC-YYYY-NNNN |
| Name + Description | Internal labels |
| Customer | Contact and/or company from CRM |
| Status | draft / active / paused / expired / cancelled |
| Starts on / Ends on | Contract dates |
| Auto-renew | Boolean — when the end date passes, auto-extend |
| Cadence | weekly / biweekly / monthly / quarterly / semiannual / annual / custom |
| Cadence interval + unit | For custom cadence: every N days/weeks/months |
| SLA response minutes | Default response SLA for related work orders |
| SLA resolution minutes | Default resolution SLA for related work orders |
| Included visits per cycle | E.g. "2 visits per quarter" |
| Hourly rate / Flat fee per visit / Monthly fee | Pricing knobs (used by reporting today; bridge to Finance queued) |
| Currency | 3-letter ISO |
| Terms | Free text — the actual contract language |
| Asset IDs | Array of assets under this contract |
Visit generation
Every day at 13:30 UTC, a Vercel cron job (fsm-service-contract-due-renewals) scans active contracts and:
- Looks at the contract's
cadenceandlast_visit_generated_at. - Computes the next due date.
- Inserts a row into
fsm_service_contract_visitsif one doesn't already exist for that contract + date. - Updates
next_visit_due_onon the contract.
A fsm_service_contract_visit row sits in status planned until a dispatcher converts it to a work order — at which point the visit links to the new WO via work_order_id.
Auto-materializing each visit into a fully-formed work order from a template (with line items + parts + required skills pre-filled) is queued for Phase 4.10 alongside RRULE-based cadences. Today the cron generates the visit row; the dispatcher promotes it to a WO with a one-click action.
Linking ad-hoc work orders
A work order that wasn't auto-generated can still link to a contract — set the WO's service_contract_id. This:
- Inherits the contract's SLA defaults onto the WO
- Counts toward "included visits per cycle" tracking
- Shows up under the contract's history view
Cadence options
| Cadence | Generated every… |
|---|---|
weekly | 7 days |
biweekly | 14 days |
monthly | Same day of the month, next month |
quarterly | Same day, +3 months |
semiannual | Same day, +6 months |
annual | Same day, +1 year |
custom | cadence_interval × cadence_unit (e.g. every 45 days) |
Limitations of the v1 cadence engine:
- "First Tuesday of every quarter" — not supported
- "Twice a year, in March and September" — not supported (use
customevery 6 months starting March) - Skipping holidays — not yet
Richer scheduling (RRULE-based) is queued for Phase 4.10. Today's enum + interval covers ~90% of common recurring service cases.
Pausing a contract
Use status = paused. The cron skips paused contracts entirely — no new visit rows generated. Past visits remain linked. Resume by flipping back to active.
Expiring vs cancelling
| Status | When | Cron behavior |
|---|---|---|
expired | Past ends_on without auto-renew | Skipped |
cancelled | Explicit termination | Skipped |
Both are terminal in practice — to revive, flip back to active and set a fresh ends_on.
What's on a generated visit
| Field | Notes |
|---|---|
| Visit due on | Date the visit should happen |
| Status | planned / scheduled (WO created) / completed / skipped / cancelled |
| Work order | Set when the visit is promoted to a WO |
| Notes | Free text |
Unique constraint on (workspace_id, service_contract_id, visit_due_on) — re-running the cron never duplicates a date.
Related
- Work Orders — what visits become
- Settings — SLA defaults, cadence config (Phase 4.8 UI)