Skip to content

Service Contracts: Workflows

Lifecycle states

       draft  ───  date_start ≤ today ≤ date_end  ───►  active
        │                                                  │
        │                              date_end < today    │
        ▼                                                  ▼
   cancelled  ◄───  manual only (sticky)           expired

Four states, two automatic transitions, one manual.

State Set by Meaning
draft initial computed (and manual reset) future or incomplete contract
active daily cron + initial compute within validity window
expired daily cron + initial compute past date_end
cancelled user action only terminated early; sticky

Drafting a contract

  1. Maintenance → Service Contracts → :guilabel:New
  2. Fill in Name, Vendor, Contract Type, Date Start, Date End
  3. Add Equipment in the Equipment tab
  4. Add Coverage lines per maintenance kind
  5. Save — the contract is assigned a reference (SC/YYYY/NNNNN) and the state is computed from the dates

Single equipment → vendor auto-fills

If you add exactly one equipment to a fresh contract while the Vendor field is empty, the equipment's assigned_partner_id fills it automatically.

Activation

There are two activation paths:

Path 1 — Auto (daily cron)

If date_start ≤ today ≤ date_end when the contract is saved, the initial compute sets state to active immediately. The daily cron re-evaluates every contract once a day so that drafts whose start date arrives later get promoted without intervention.

Path 2 — Manual

Click any state on the status bar to jump directly to it — useful for back-dating a contract or returning a cancelled contract to draft.

Coverage banner behaviour

When a maintenance.request is opened on covered equipment, a banner appears above the title with a copy that follows this matrix:

Contract on equipment? Kind on request? Covered? Billable? Banner
True Out of contract — hospital pays
n/a True ✅ Covered by SC/X — hospital billable
True ⚠ Covered by SC/X but kind not in coverage
✅, billable=True True ✅ Covered by SC/X — hospital billable
✅, billable=False False ✅ Covered by SC/X (vendor) — vendor pays

The contract picked for the request is the equipment's roots_active_contract_id — the most recent active contract by date_start. If two contracts cover the same equipment with overlapping windows, the newer-started wins. The technician can manually override.

Renewal workflow

date_end - 60d           date_end             date_end + 1
     │                       │                      │
     ▼                       ▼                      ▼
  Alert On            Renewal target            Auto-expire
     │                       │                      │
     └─ cron fires           ├─ procurement         └─ state=expired
        mail.activity        │  finalises renewal      cron sets
        on Responsible       │  (extends contract      roots_renewal_
        + sets               │  or signs new one)      alerted intact
        roots_renewal_       │
        alerted=True         │
                       ┌──────────────┐
                       │ Option A     │ Option B
                       │ Extend       │ Replace
                       │ existing     │ with new
                       │ contract     │ contract
                       │              │
                       │ Edit dates,  │ Create new
                       │ reset        │ contract,
                       │ roots_       │ leave old
                       │ renewal_     │ one to
                       │ alerted=False│ auto-expire
                       └──────────────┘

Option A — Extend (most common for warranty)

  1. Open the contract form
  2. Push Date End forward (e.g. add 12 months)
  3. Reset Renewal Alerted to False (in the Validity group)
  4. Save — the contract stays active, Alert On recomputes, and the next renewal cycle is armed

Option B — Replace (most common for MA)

  1. Create a new contract starting on the day after the old one ends
  2. Add the same equipment to the new contract
  3. Copy the coverage lines (or refine them based on renegotiation)
  4. Leave the old contract alone — it auto-expires when its date_end passes
  5. The equipment's roots_active_contract_id will switch to the new contract automatically (newer-started wins)

Cancellation

To terminate a contract early (e.g. equipment decommissioned, vendor default):

  1. Open the contract form
  2. Click :guilabel:Cancel Contract in the header
  3. Confirm

The state becomes cancelled (sticky — date-based recompute will not un-cancel it). The equipment's roots_active_contract_id drops this contract (cancelled is not active). Requests already linked to the cancelled contract keep their link unless re-computed.

To revert a cancelled contract back into the date-based lifecycle:

  1. Open the contract form (still in cancelled state)
  2. Click :guilabel:Reset to Draft
  3. The compute re-runs based on dates — the contract becomes draft, active, or expired as the calendar dictates

Multi-contract scenarios

One equipment covered by two contracts (warranty + MA)

Common scenario: 2-year manufacturer warranty + a 5-year extended MA purchased separately, both covering the same MRI.

  • Add the equipment to both contracts
  • Both contracts will be active if their windows include today
  • The equipment's roots_active_contract_id resolves to the newer-started one (most recent date_start)
  • Requests auto-link to the newer contract
  • The technician can manually override to the warranty contract if a defect should be claimed against it

One contract covering many equipment (fleet MA)

Common scenario: one MA covering all 4 ventilators in the ICU.

  • Create one contract, add all 4 equipment
  • Each ventilator's roots_active_contract_id resolves to this same contract
  • Each new request auto-links to it
  • Renewal alert fires once for the contract (not 4×)

Spare-parts-only contract sharing equipment with a labor MA

Common scenario: spare parts supplied by a specialist vendor, labor covered by a separate firm.

  • Create two contracts, both covering the same equipment
  • The newer-started one wins for auto-link
  • Coverage banner reflects the auto-linked contract — technician manually switches when working on a "the other half" job
  • The decision-matrix logic (Q4) for routing pickings to RFQ instead of stock is not implemented in Q3 — only the banner reflects the scenario today

Renewal-alert de-duplication

If the daily cron fires once, the activity is created and roots_renewal_alerted flips to True. On every subsequent run, the cron sees True and skips. To re-arm the alert (e.g. you extended the contract and want a fresh reminder near the new date_end):

  1. Open the contract form
  2. In the Validity group, uncheck Renewal Alerted
  3. Save — next cron run will fire a fresh activity if today >= Alert On

This is the only manual reset on a contract; everything else is date-driven.