Skip to content

Workflows

This page covers the full lifecycle of a HA Annual PM Report, the parallel-facet signing flow, and the rejection / unwind path.

The 3-state lifecycle

stateDiagram-v2
    [*] --> draft
    draft --> in_progress: action_start() / wizard generate
    in_progress --> submitted: action_submit() (freezes snapshot)
    submitted --> archived: action_archive_report()
    submitted --> draft: action_reset_to_draft() (manager only)
    in_progress --> draft: action_reset_to_draft() (manager only)
    archived --> [*]

Three states, not four — no separate signed state. Signing is captured by the roots_signed_pdf field + the 🟢 ลงนามครบ badge, not by a state transition (matches the Q4 e-Signature parallel-facet decision).

State semantics

State Description Lines Signers Print
Draft Just created; not yet active editable editable live data
In Progress Engineer reviewing + filling exceptions editable editable live data
Submitted Locked + snapshotted, awaiting/post-signing frozen editable snapshot
Archived Year complete, locked permanently frozen locked snapshot

Generate → Review → Submit

The happy path:

  1. Generate Wizard — picks dept + FY, builds the report at In Progress. Lines are pre-populated with planned/actual counts; result='pass' is pre-seeded on 100%-compliance lines.
  2. Review — engineer scans the Equipment Lines tab, fills result + exception_reason for below-plan lines.
  3. Submit — clicks ส่งรายงาน (Submit). The system:
  4. Builds the immutable JSON snapshot containing every line's data, the full MoPH device-class table at this moment, and a SHA-256 paperformat fingerprint.
  5. Sets state = submitted, submitted_date = now().
  6. Posts a chatter message: 📋 Report submitted by [User] — snapshot #N frozen with M lines.

Submit guards

The submit refuses when:

  • State is not In Progress.
  • The report has zero lines (forces wizard-use or manual line creation).
  • Any line marked fail / not_done is missing an exception reason (raised by a @api.constrains on the line model).

Send for Signature

After submit, the header gains a 📝 Send for Signature button (visible only when sign_request_count == 0).

Pre-flight

The system calls _check_ha_signers_resolvable() first. It refuses when any of the three signer slots is empty or has no linked partner, with an actionable error like:

Cannot send for signature: • Engineering Head — not set • Hospital Director (Dr. Smith) — no linked partner; cannot resolve a sign_oca signer

The signing request

When pre-flight passes, the system:

  1. Renders the live QWeb PDF (snapshot-aware — reads from the frozen snapshot since state == submitted).
  2. Creates a sign.oca.request with three signers:
  3. BME Head — role_bme_head, partner from bme_head_id
  4. Engineering Head — role_engineering_head, partner from engineering_head_id
  5. Hospital Director — Q4's existing role_hospital_signer, partner from director_id
  6. Opens the request form so the engineer can review and click "Send".

Signing UX

  • Internal signers — see a pencil icon in their systray notifying them of pending signatures. Click → in-app signing.
  • Portal signers (typical for the off-site Hospital Director) — receive an email with a signing link that opens the portal. Sign there.

Completion → signed PDF

When the third signer completes, sign_oca transitions the request to 2_signed state. The Q5.9 _check_signed super-extension fires:

  1. super()._check_signed() runs Q4's chain (handles inspection + contract cases).
  2. This extension catches record_ref._name == 'hemms.ha.report'.
  3. Calls Q4's _roots_attach_signed_pdf_to_parent(request, record) helper (inherited via the sign.oca.request inheritance chain) — writes data (the composited signed PDF) to roots_signed_pdf + posts the PDF to chatter as an attachment.

On the report header:

  • The 🟡 รอลงนาม badge flips to 🟢 ลงนามครบ.
  • The signed PDF appears in chatter.
  • The state stays Submitted — parallel-facet semantics.

Rejection + re-sign

sign_oca 18.0 doesn't ship in-flow rejection (it's on the upstream roadmap). Until then, operator-driven rejection is the path:

  1. Manager opens the report.
  2. Clears the signed PDF — either via the smart button on the sign.oca.request itself (active = False) or directly:

report.sudo().write({
    "roots_signed_pdf": False,
    "roots_signed_pdf_filename": False,
})
old_request.active = False
3. Posts a chatter message explaining the reject — the badge flips back to 🟡 รอลงนาม. 4. Click 📝 Send for Signature again — a fresh sign.oca.request spawns. The pre-flight runs again so the manager can fix signer slots if needed.

There is no cycle cap — a report can be rejected and re-sent indefinitely (matches the Q3 + Q4 patterns). All previous requests remain in history (active=False) for audit.

Reset to Draft (manager-only)

When a more fundamental change is needed (typo in lines, wrong exception reason, late-arriving PM event), only Reset to Draft unfreezes the report:

  1. Manager clicks คืนสู่ Draft (Reset) in the header (visible only with maintenance.group_equipment_manager).
  2. The system:
  3. Deletes the linked snapshot record.
  4. Sets state = draft, submitted_date = False, snapshot_id = False.
  5. Posts a chatter message: ♻️ Reset to draft by [User] — snapshot cleared.
  6. Lines become editable again.
  7. On the next Submit, a fresh snapshot is built from current data.

Signing implications. If signatures had already been collected, resetting to draft does NOT automatically reject the existing signature request — you should archive (active = False) the sign.oca.request manually before resetting. Otherwise the chatter may end up with two signed PDFs from different snapshots.

Archive

When the next fiscal year arrives, archive the previous year's report:

  1. Click จัดเก็บ (Archive) on the submitted report.
  2. State transitions Submitted → Archived.
  3. The header gains an "Archived" web_ribbon as a visual cue.
  4. The report stays fully readable and printable — just locked permanently.

Archive is purely organisational — it doesn't delete or alter data. Surveyor reprints work just as well on archived reports as on submitted ones (both render from the snapshot).

Next steps