API Reference¶
This page documents the public surface of each roots_hemms_* module — the models, fields, and methods that other modules (yours included) are expected to inherit, call, or reference. It is intentionally selective: internal helpers and compute methods that aren't extension points are omitted; read the source for those.
Conventions used on this page
- Brand-new models declared by HEMMS use a
hemms._nameprefix. - Hospital-specific fields added to inherited (Odoo / OCA) models use a
roots_prefix. - "Extension point" calls out methods designed to be overridden via
super()in downstream code.
For the bigger picture, read Architecture first. For how to add a new module that extends these, read Module Development.
Equipment & criticality¶
These modules form the asset master + risk-classification core that every other HEMMS module depends on.
roots_hemms_criticality¶
Adds the 2D Value × Risk → traffic-light criticality matrix on top of maintenance.equipment.
Model: maintenance.equipment (_inherit)
| Field | Type | Notes |
|---|---|---|
roots_value_tier | Selection (low / medium / high) | Required, tracked, default medium |
roots_risk_tier | Selection (low / medium / high) | Required, tracked, default medium |
roots_criticality_color | Selection (green / yellow / red) | Stored compute from the 2D matrix; indexed |
roots_criticality_label | Char | Non-stored bilingual label that follows the user's lang |
The matrix itself is CRITICALITY_MATRIX in models/maintenance_equipment.py — a (value_tier, risk_tier) → color dict. Override the compute method _compute_roots_criticality_color if you need a different mapping.
roots_hemms_asset_master¶
Brand / Model master data and government Ref. No. uniqueness.
Models:
hemms.equipment.brand— Brand master (_inherit = ["mail.thread"]). Action methodsaction_model_list()andaction_equipment_list()for stat buttons.hemms.equipment.model— Model master, M2O to brand. Domain[('brand_id', '=', brand_id)]is enforced onmaintenance.equipment.equipment_model_id.
Model: maintenance.equipment (_inherit)
| Field | Type | Notes |
|---|---|---|
brand_id | M2O hemms.equipment.brand | Tracked, indexed |
equipment_model_id | M2O hemms.equipment.model | Domain-filtered by brand |
current_condition | Selection | in_use / broken / deteriorated / lost / unused. Bilingual labels; indexed; tracked |
SQL constraint: ref (Odoo's existing field, added by OCA maintenance_equipment_ref) is enforced unique here — the official รหัสครุภัณฑ์ identifier cannot collide across equipment records.
Workflow & PM¶
roots_hemms_workflow¶
The 5-stage Thai BME repair workflow with SLA tracking and an immutable audit trail.
Model: maintenance.stage (_inherit)
| Field | Type | Notes |
|---|---|---|
roots_description_th | Text (translate) | Thai-language description |
roots_sla_hours | Float | Max hours a request should spend in this stage |
roots_is_done_stage | Boolean | Marks terminal stages |
roots_responsible_role | Selection | ward_staff / bme_manager / bme_technician / bme_manager_signoff |
Model: maintenance.request (_inherit)
| Field | Type | Notes |
|---|---|---|
roots_stage_entered_at | Datetime | Set on stage change; default now; copy=False |
roots_time_in_current_stage_hours | Float | Non-stored compute |
roots_is_sla_breached | Boolean | Non-stored compute + custom search (_search_roots_is_sla_breached) that pushes the inequality into SQL |
roots_equipment_criticality_color | Selection (related, stored) | Mirror of equipment's criticality for kanban colouring |
roots_current_responsible_user_id | M2O res.users | Stored compute; does NOT fall back to env.user |
roots_stage_transition_log_ids | One2many | Audit-log entries |
Extension points on maintenance.request:
create()— logs an initial transition row fromFalse → stage_id.write()— whenstage_idis being changed, capturesfrom_stage,to_stage, time-in-previous-stage, and posts a chatter note. Runs super first so access errors abort before orphan logs.
Model: hemms.stage.transition.log — append-only audit row. Stored on maintenance.request.roots_stage_transition_log_ids. Each row records from_stage_id, to_stage_id, transitioned_at, transitioned_by_id, roots_time_in_previous_stage_hours.
Searching for SLA breaches
roots_hemms_pm¶
Hospital PM kinds (bilingual) and PM-aware stage routing.
Model: maintenance.kind (_inherit) — adds roots_name_th and roots_description.
Model: maintenance.stage (_inherit) — adds roots_is_pm_initial_stage (Boolean) with a SQL exclusion constraint so only one stage can be the PM initial.
Model: maintenance.request (_inherit)
The create() override routes any request that arrives with maintenance_plan_id to the PM-initial stage (unless the caller specifies stage_id explicitly). The stage_id default is overridden via _roots_default_stage_excluding_pm() — corrective requests skip PM stages on creation.
If you need to add a new PM kind, declare it in your own module's data/maintenance_kind_data.xml:
<record id="kind_calibration" model="maintenance.kind">
<field name="name">Calibration</field>
<field name="roots_name_th">การสอบเทียบ</field>
<field name="roots_description">Verify accuracy against a traceable reference.</field>
</record>
Annual asset inspection (ปจป.)¶
roots_hemms_asset_inspection¶
Model: hemms.asset.inspection — one record per (department_id, fiscal_year_be). The _sql_constraints block this combo from duplicating.
Key fields:
| Field | Type | Notes |
|---|---|---|
name | Char | Running ref AI/YYYY/NNNNN via ir.sequence |
fiscal_year_be | Selection | Thai BE fiscal year; window ±10 years |
department_id | M2O hr.department | Required, tracked |
chair_id, committee_member_1_id, committee_member_2_id | M2O hr.employee | Distinct-people constraint |
state | Selection | draft → in_progress → submitted |
line_ids | O2M | One row per equipment under inspection |
count_* | Integer | Stored computes: broken / deteriorated / lost / unused / in_use / unmarked / total |
Public action methods (button handlers):
action_open_add_equipment_wizard()— Draft-only.action_start_inspection()—draft→in_progress. Refuses if no line_ids.action_submit()—in_progress→submitted. Refuses if any line has noconditionset. Calls_propagate_condition_to_master().action_reset_to_draft()— Maintenance Manager only.action_print_report()— Renders the ปจป. PDF.
_propagate_condition_to_master() writes each line's final condition back onto maintenance.equipment.current_condition in bulk per condition — one UPDATE per distinct value.
Model: hemms.asset.inspection.line — equipment × inspection line with its observed condition.
Spare parts¶
roots_hemms_spare_parts¶
Model: hemms.equipment.spare.line — per-equipment spare-parts template.
| Field | Type | Notes |
|---|---|---|
equipment_id | M2O maintenance.equipment | Required, ondelete='cascade', indexed |
product_id | M2O product.product | Domain [('type', 'in', ['product', 'consu'])] |
default_qty | Float | UoM digits |
uom_id | Related (read-only) | From product |
kind_ids | M2M maintenance.kind | Restrict to specific PM kinds; empty = all |
roots_is_critical | Boolean | Stored compute: product flagged critical OR equipment is red |
qty_available | Related (read-only) | From product |
Cron: _cron_check_critical_low_stock — daily; raises a Warning activity on equipment for any critical spare with on-hand ≤ 0. De-duplicates against existing activities matching the product name.
Model: product.template (_inherit) — adds roots_is_critical_spare and roots_name_th.
Model: maintenance.request (_inherit) — auto-creates a draft Consumption picking on PM-cron requests via _roots_auto_create_spare_picking(). The picking filters spare lines by kind_ids (lines with no kind restriction are always included).
Also adds roots_open_picking_count (non-stored compute) and action_view_pending_stock_pickings() for the stat button.
Model: stock.picking (_inherit) — running Stock Request number via the hemms.stock.request ir.sequence on Consumption-type pickings.
Service contracts¶
roots_hemms_service_contracts¶
Model: hemms.contract.type — vocabulary (Warranty, Extended, MA, Spare-only, Labor-only, ...). Bilingual name + roots_name_th, unique code, ordered.
Model: hemms.service.contract — the contract header.
Key fields:
| Field | Type | Notes |
|---|---|---|
name, roots_name_th | Char | Bilingual; display_name is [REF] EN / TH |
reference | Char | Running ref via hemms.service.contract sequence (SC/YYYY/NNNN); unique |
partner_id | M2O res.partner | Vendor; supplier-flag filter deferred (see I1 in Release Notes) |
contract_type_id | M2O hemms.contract.type | ondelete='restrict' |
equipment_ids | M2M maintenance.equipment | hemms_contract_equipment_rel |
coverage_line_ids | O2M hemms.service.contract.coverage.line | Per-kind coverage |
date_start, date_end | Date | CHECK constraint: start ≤ end |
state | Selection (stored compute) | draft / active / expired / cancelled — cancelled is sticky |
roots_sla_tier | Selection | Bronze / Silver / Gold / Platinum (display-only in Q3) |
roots_renewal_alert_days | Integer | Default 60 |
roots_renewal_alerted | Boolean | Set by cron to avoid duplicates |
Public actions: action_set_cancelled(), action_set_draft(), action_view_equipment(), action_view_requests().
Cron: _cron_update_state_and_alert — daily; promotes draft → active and active → expired by date, then fires a mail.activity on the responsible user when roots_renewal_alert_date has been reached. Cancelled stays cancelled.
Bilingual search: _name_search() is overridden to match name or roots_name_th or reference.
Model: hemms.service.contract.coverage.line — per-kind matrix.
| Field | Type | Notes |
|---|---|---|
kind_id | M2O maintenance.kind | Required |
included_in_contract | Boolean | Default True |
billable_to_hospital | Boolean | Whose budget pays |
Model: maintenance.equipment (_inherit) — adds roots_active_contract_id (stored compute) and roots_active_contract_type_code (related). Useful for auto-linking new requests:
Model: maintenance.request (_inherit)
| Field | Type | Notes |
|---|---|---|
roots_service_contract_id | M2O (stored compute, writable) | Auto-filled when blank — manual override sticks (plan I8) |
roots_is_billable_to_hospital | Boolean (stored compute) | True unless covered AND not hospital-billable |
roots_contract_warning | Char (compute) | Banner copy |
roots_contract_warning_level | Selection (compute) | success / info / warning for the alert-class on the form |
Creating a contract programmatically
env["hemms.service.contract"].create({
"name": "MRI 1.5T - 5yr MA",
"partner_id": vendor.id,
"contract_type_id": env.ref(
"roots_hemms_service_contracts.contract_type_ma"
).id,
"date_start": "2026-01-01",
"date_end": "2031-12-31",
"equipment_ids": [(6, 0, [mri.id])],
"coverage_line_ids": [
(0, 0, {
"kind_id": env.ref("roots_hemms_pm.kind_calibration").id,
"included_in_contract": True,
"billable_to_hospital": False,
}),
],
})
e-Signature¶
roots_hemms_signature¶
A pure bridge module — adds no new models, only _inherits sign.oca.request, hemms.asset.inspection, and hemms.service.contract.
Model: sign.oca.request (_inherit)
Adds stored M2O computes from the polymorphic record_ref:
hemms_asset_inspection_idhemms_service_contract_id
These let the bridged hospital models declare One2many inverses, since Reference fields can't be inverse_name directly.
Extension point: _check_signed() — overridden. After super()._check_signed() may have transitioned state to 2_signed, the override looks at record_ref and, when it points to a bridged hospital model, calls _roots_attach_signed_pdf_to_parent(). That writes the final composited PDF to the parent's roots_signed_pdf / roots_signed_pdf_filename and posts it as a chatter attachment.
No state advance on the parent. Signing is a parallel facet — inspection stays submitted, contract stays in its own lifecycle state. (Plan §8 OQ3 + OQ2.)
Model: hemms.asset.inspection (_inherit) — adds sign_request_ids (O2M), sign_request_count (stored compute), roots_signed_pdf (Binary, attachment=True), roots_signed_pdf_filename (Char), and:
action_send_for_signature()— renders the ปจป. PDF, creates asign.oca.requestwith 3 signers (chair + 2 members). Refuses if the inspection is notsubmittedor any committee member is unresolvable (no linked Odoo user).action_view_sign_requests()— opens the related list.
Model: hemms.service.contract (_inherit) — same bridge pattern: sign_request_ids, sign_request_count, roots_signed_pdf*, action_send_for_signature(), action_view_sign_requests().
HA Annual PM Report¶
roots_hemms_ha_report¶
Model: hemms.ha.device.class — MoPH 82-device classification. Seeded as noupdate="1" so hospital edits survive upgrades. Fields: name_th, name_en, risk_tier, pm_freq_months, pm_duration_hours, display_name (compute).
Model: maintenance.equipment (_inherit) — risk-tier waterfall fields:
| Field | Type | Notes |
|---|---|---|
device_class_id | M2O hemms.ha.device.class | ondelete='restrict' |
roots_ha_risk_tier_override | Selection | Engineer override wins the waterfall |
roots_ha_pm_freq_months_override | Integer | Override default 0 = inherit |
roots_em_function, roots_em_application, roots_em_maintenance, roots_em_history | Integer | WHO Fennigkoh-Smith sliders |
roots_ha_risk_tier_effective | Selection (compute) | Resolved through the waterfall |
roots_ha_pm_freq_months_effective | Integer (compute) | Resolved through the waterfall |
roots_ha_risk_tier_source | Selection | Provenance: override / device_class / em_score |
Model: hemms.ha.report — one per (department_id, fiscal_year_be). State machine: draft → in_progress → submitted → archived. Signing is parallel (no separate signed state — see Q4 OQ3).
Public action methods:
action_start()—draft→in_progress.action_submit()—in_progress→submitted. Builds the immutable snapshot viahemms.ha.report.snapshot._build_snapshot_payload(), pins it viasnapshot_id. From this point, line writes raiseUserError(see line model_ensure_unlocked).action_archive_report()—submitted→archived.action_send_for_signature()— 3 signers (BME Head, Engineering Head, Director). Refuses if any signer slot is empty or has no linked partner.action_reset_to_draft()— Manager only; clears the snapshot.
Model: hemms.ha.report.line — per-equipment detail. Locked after parent submission. Helpers _ensure_unlocked() in write/unlink raise UserError once the parent is submitted.
Model: hemms.ha.report.snapshot — append-only JSON snapshot. _build_snapshot_payload() and _build_paperformat_hash() are the two helpers QWeb (Q5.8) reads from. The template renders from the snapshot for any submitted/archived report so reprints stay byte-identical.
Extension point: sign.oca.request._check_signed() — extended a second time here to handle hemms.ha.report parents (in addition to the inspection / contract handled by Q4).
Generating an HA report
wizard = env["hemms.ha.report.generate"].create({
"fiscal_year_be": "2569",
"department_id": dept.id,
})
action = wizard.action_generate() # opens the draft report
report = env["hemms.ha.report"].browse(action["res_id"])
report.action_start()
# ... engineer fills exception reasons on report.line_ids ...
report.action_submit() # builds snapshot, locks lines
report.action_send_for_signature() # spawns sign.oca.request
Mass import¶
roots_hemms_mass_import¶
Model: hemms.import.batch — one record per XLSX upload. States: draft → previewed → committed → (archived | deleted).
Key fields: name (running ref via sequence), object_type (equipment / vendor / location), file_data (Binary), file_name (Char), data_json (Text — parsed rows + per-row verdicts), committed_count, error_count, …
Service helpers (importable):
services.parser— XLSX → list of dicts.services.validator— per-row business-key checks (MoPH strict match, required fields, …).services.committer— transactional commit; on row failure rolls back the whole batch (plan D14).services.error_export— error-row XLSX export.
Bridge fields on the imported objects:
maintenance.equipment.roots_hemms_import_batch_id— M2Ohemms.import.batchres.partner.roots_hemms_import_batch_idhr.department.roots_hemms_import_batch_id
These let the batch's _check_delete_blockers confirm whether any tagged record has acquired a downstream Q1 PM event or Q3 service contract before the 7-day delete window closes.
Constants:
OBJECT_MODEL—{"equipment": "maintenance.equipment", "vendor": "res.partner", "location": "hr.department"}.DELETE_WINDOW_DAYS = 7.
Common ORM recipes¶
Resolve effective criticality for a request¶
def is_red_critical(request):
# `roots_equipment_criticality_color` is a stored related — no SQL roundtrip.
return request.roots_equipment_criticality_color == "red"
Find all open PM requests against red equipment that are SLA-breached¶
env["maintenance.request"].search([
("maintenance_type", "=", "preventive"),
("roots_equipment_criticality_color", "=", "red"),
("roots_is_sla_breached", "=", True),
("stage_id.roots_is_done_stage", "=", False),
])
Submit an inspection from code (e.g., a script during data migration)¶
inspection = env["hemms.asset.inspection"].create({
"department_id": dept.id,
"fiscal_year_be": "2569",
"chair_id": chair_emp.id,
"committee_member_1_id": m1_emp.id,
"committee_member_2_id": m2_emp.id,
})
# Add lines through the wizard or directly (line_ids o2m).
inspection.action_start_inspection()
# Set each line's condition.
inspection.action_submit()
Check a contract's coverage decision before billing¶
contract = equipment.roots_active_contract_id
if not contract:
bill_hospital()
return
line = contract.coverage_line_ids.filtered(
lambda L, k=kind: L.kind_id == k
)[:1]
if not line or not line.included_in_contract:
bill_hospital()
elif line.billable_to_hospital:
bill_hospital()
else:
bill_vendor(contract.partner_id)
(In practice you read maintenance_request.roots_is_billable_to_hospital directly — the compute above already runs.)
Related pages¶
- Architecture — the 3-layer model these APIs sit inside.
- Module Development — how to build a new module that uses these APIs.
- CI/CD — make sure your changes pass the same gates.