Complaint Workflow Map

Status state machine, stepper, entry points & sub-workflows — auto-extracted from source

1. Status State Machine

8 statuses defined in ComplaintStatus (models.py:25). Transition map enforced at complaint_service.py:284 — only via ComplaintService.change_status. PX Admins bypass.

stateDiagram-v2
    [*] --> open

    open --> in_progress : activate
    open --> cancelled

    in_progress --> partially_resolved
    in_progress --> resolved
    in_progress --> cancelled
    in_progress --> pending_external
    in_progress --> ovr_pending : escalate

    partially_resolved --> resolved
    partially_resolved --> in_progress
    partially_resolved --> cancelled
    partially_resolved --> pending_external

    resolved --> closed
    resolved --> in_progress : reopen

    closed --> in_progress : reopen

    cancelled --> open
    cancelled --> in_progress

    pending_external --> resolved
    pending_external --> in_progress
    pending_external --> cancelled
    pending_external --> closed

    ovr_pending --> in_progress : approve / reject
    ovr_pending --> resolved
    ovr_pending --> cancelled

    closed --> [*]
                

Transition Table

FromAllowed Next Statuses
openin_progress, cancelled
in_progresspartially_resolved, resolved, cancelled, pending_external, ovr_pending
partially_resolvedresolved, in_progress, cancelled, pending_external
resolvedclosed, in_progress
closedin_progress
cancelledopen, in_progress
pending_externalresolved, in_progress, cancelled, closed
ovr_pendingin_progress, resolved, cancelled

Active vs Inactive (is_active_status)

ActiveInactive
open in_progress partially_resolved pending_external resolved closed cancelled ovr_pending ⚠️
Inconsistency: ovr_pending is treated as inactive by is_active_status even though it's a mid-lifecycle state. Service methods guarding on active status would reject operations on OVR-pending complaints.

2. Detail Page Stepper

6 ordered progress flags rendered inline in complaint_detail.html:147. Driven by a boolean dict built at ui_views.py:745. Hidden when status is cancelled.

1
Created
always ✓
2
Activate
activated_at set
3
Taxonomy
taxonomy_reviewed_at
4
Send to Dept
sent_to_department
5
Response
dept responded
6
Resolve
resolved / closed

Step Details

#StepFlagIconAction when not done
1Createdalways greencheck
2Activateactivated_atplay"Activate this complaint" → complaint_activate
3Taxonomytaxonomy_reviewed_atclipboard-check"Review taxonomy" → confirm_taxonomy
4Send to Deptsent_to_department / forwarded_to_dept_atsend"Send to department" modal
5Responseexplanation used OR dept respondedmessage-square"Awaiting department response"
6Resolvestatus in (resolved, closed)check-circle-2"Generate Resolution"
The stepper is flag-driven, not status-driven. E.g. "resolved" lights up green whenever status ∈ (resolved, closed), regardless of path taken.

3. Status-Mutating Entry Points

Code paths that modify complaint.status. Only ComplaintService.change_status enforces the transition map — the rest set status directly.

ActionLocationTransition enforced?
Generic change statusui_views.py:1365Yes
Activate (open→in_progress)ui_views.py:2125service guard
Assign / reopen-on-assignui_views.py:851service guard
Reopen as new complaintui_views.py:1777service guard
OVR toggleui_views.py:1549direct
OVR approveui_views.py:1640direct
OVR rejectui_views.py:1755direct
API auto-resolveviews.py:2040direct
Convert to appreciationviews.py:2198direct
Meeting auto-resolveviews.py:3279direct
Bulk status changeutils.py:263direct
close_stale_complaints cmdclose_stale_complaints.py:113direct (bulk UPDATE)
7 code paths bypass the transition map. This means invalid transitions can occur via OVR views, bulk operations, API auto-resolve, convert-to-appreciation, meeting-resolve, and the stale-close command.

4. Sub-Workflows

Independent state machines attached to a complaint, each with its own field/model.

graph TD
    C[COMPLAINT
status: 8 states] C --> DR["Dept Routing
routing_status"] C --> CI["Champion Investigation
status"] C --> MR["Manager Review
manager_review_status"] C --> PC["Patient Contact
patient_contact_status"] C --> SA["Satisfaction
satisfaction"] C --> AA["Adverse Action
verification_status"] DR --> |sent| DR2["accepted / rejected"] CI --> |questions_sent| CI2["answers_received"] CI2 --> CI3["reply_submitted / direct_reply_in_progress"] MR --> |pending| MR2["approved / rejected"] PC --> |not_contacted| PC2["contacted / no_response"] SA --> SA2["satisfied / neutral / dissatisfied / no_response"] AA --> |reported| AA2["under_investigation"] AA2 --> AA3["verified / unfounded / resolved"] style C fill:#0a1e3f,color:#fff,stroke:none style DR fill:#dbeafe,stroke:#007bbd style CI fill:#dcfce7,stroke:#16a34a style MR fill:#fef3c7,stroke:#d97706 style PC fill:#fce7f3,stroke:#db2777 style SA fill:#ede9fe,stroke:#7c3aed style AA fill:#fee2e2,stroke:#dc2626

Department Routing

ComplaintInvolvedDepartment.routing_status
sent accepted rejected

Champion Investigation

ChampionInvestigation.status
questions_sent answers_received reply_submitted direct_reply_in_progress

Manager Review

ComplaintInvolvedDepartment.manager_review_status
pending approved rejected

Patient Contact

Complaint.patient_contact_status
not_contacted contacted contacted_no_response

Satisfaction

Complaint.satisfaction
satisfied neutral dissatisfied no_response
🔒 Locked by patient submission · max 3 PX-team changes · 5-day window

Adverse Action

ComplaintAdverseAction.verification_status
reported under_investigation verified unfounded resolved