29 KiB
Inquiries — Workflow & Lifecycle
Source of truth:
apps/complaints/(theInquirymodel lives inside the complaints app, not a separate one). URL namespaceinquiries, mounted at/inquiries/. This document describes the current implementation only. Every claim is cited asfile:line.
⚠ Read first —
docs/workflows.mdis partly out of date for Inquiries. The most important change: thedept_response_acceptance_statusfield that backed the "PX accepts/rejects" loop was removed (migration0039), so that loop no longer exists in code. The model also has noclean()/CheckConstraintenforcing statuses.
1. Purpose
Inquiry is "for general questions/requests. Similar to complaints but for non-complaint inquiries." (apps/complaints/models.py:1528-1533).
Architectural differences vs. Complaint (intended, per docs/workflows.md:31-39):
- Single department, flat fields — Inquiry carries department-response data as flat columns on the row (
transferred_to_department,outgoing_department,department_response_en/ar,models.py:1824-1854). Complaint uses a multi-department join model. - One-level review — champion responds → PX accepts/rejects → resolve. No department-manager tier (
docs/workflows.md:34-35). (See §14 — the accept/reject field was removed.) - Resolve requires a PX
response— the PX team must writeinquiry.responsebefore resolving (ui_views.py:3356-3358). - No investigation sub-flow like Complaint's
InvestigationResponse.InquiryExplanationis a simple token-response mechanism (§8). - No signals —
apps/complaints/signals.pywires receivers only forComplaint/ComplaintUpdate/ComplaintInvolvedDepartment; zeroInquirysignal receivers.
Shared vocabulary with Complaints: open → in_progress → resolved → closed (models.py:1695-1705).
2. How a Case Starts
2.1 Who can create
- DRF/API:
CanCreateInquiry(apps/complaints/permissions.py:39-67) — PX Admins, Hospital Admins, Source Users withcan_create_inquiries, everyone else (incl. patients).InquiryViewSetrequiresIsAuthenticated(views.py:2895). - UI form:
@login_required(ui_views.py:2978).
2.2 Creation channels (4 entry points)
| Channel | View | URL name | Auth |
|---|---|---|---|
| Authenticated UI form | ui_views.inquiry_create (ui_views.py:2980) |
inquiries:inquiry_create (urls_inquiries.py:11) |
@login_required |
| Public portal (complaints app) | ui_views.public_inquiry_submit (ui_views.py:4707) |
inquiries:public_inquiry_submit (urls_inquiries.py:27) |
None |
| Public portal (core app) | apps.core.views.public_inquiry_submit (apps/core/views.py:170) |
core:public_inquiry_submit (apps/core/urls.py:38) |
None |
| DRF API | InquiryViewSet.perform_create (views.py:2909) |
— | IsAuthenticated + CanCreateInquiry |
Incoming vs. Outgoing — controlled by is_outgoing (models.py:1803-1805): False (default) = incoming (from a patient/source into the hospital); True = outgoing (hospital contacts an external/other department; paired with outgoing_department, models.py:1834). Set via hidden form field (forms.py:676-681), read in create view (ui_views.py:3001-3011). Public submissions are always incoming. Reports split on this flag (inquiry_export_incoming/outgoing, ui_views.py:4196,4216) — corresponding to "Reports - Incoming/Outgoing Inquiries.xlsx".
2.3 Information required before submission
InquiryForm (forms.py:564) — required: hospital, location_type, category, subject, message. Optional: patient, area, department, contact_name/phone/email, section, priority, source, is_outgoing, outgoing_department.
Public form (public_inquiry_submit) — hand-rolled validation (ui_views.py:4725-4735) requires: name, phone, hospital, subject, message.
⚠ Gap:
PublicInquiryForm(forms.py:857) exists and declaresname/phone/hospital/location_typerequired, butpublic_inquiry_submitvalidates manually and does not use it — may be vestigial.
2.4 What happens immediately after submission
Inquiry.save() (models.py:1980-2027):
- Loads previous status into
_status_was(models.py:1982). - Stamps
resolved_at/closed_atonly on transitions into those statuses, not on creation (models.py:1993-2000). - Generates reference number if missing:
generate_reference("INQ", hospital)→INQ-YYYYMM-NNNN(models.py:2002-2005). - Sets SLA deadline
due_at= now +sla_hoursfromInquirySLAConfig(models.py:2007-2012). - Auto-derives
timeline_slabucket (24/48/72/>72h) fromdue_at - created_at(models.py:2014-2025). - Default
status = OPEN(models.py:1703).
Background tasks after creation:
analyze_inquiry_with_ai.delay(...)— AI priority/department/taxonomy/emotion (tasks.py:2991).notify_staff_new_item.delay("inquiry", ...)— admin notification email (tasks.py:2490).link_inquiry_patient.delay(...)— async patient lookup (tasks.py:3314).InquiryUpdate"Inquiry created." timeline entry (ui_views.py:3046).
Activation gate: a new inquiry is
openand cannot be sent to a department until activated toin_progress. Send endpoints rejectstatus == "open"(ui_views.py:3594, 3841).
⚠ Gap — no creation acknowledgement to inquirer: unlike Complaint (which has a
send_complaint_creation_smspost_save signal), Inquiry has no creation-acknowledgement signal. The inquirer is not auto-notified on submission; only staff are notified vianotify_staff_new_item.
3. Complete Lifecycle
3.1 Status "enum"
There is no InquiryStatus enum class and no VALID_INQUIRY_TRANSITIONS map. Status is a plain CharField with inline choices (models.py:1695-1705): open, in_progress, resolved, closed.
3.2 Transition enforcement — effectively NONE
- No
clean()method onInquiry. - No
constraints/CheckConstraintinMetaor migrations (models.py:1971-1978) — only indexes.
⚠ Gap vs
docs/workflows.md:11-13: that doc claims invalid statuses are "rejected at the model (clean()) and DB (CheckConstraint) level." This is true for Complaint, but NOT for Inquiry.inquiry_change_statusaccepts any string the user POSTs and saves it (the only guard is resolved-requires-response). Thechoicesare not DB-enforced.
3.3 Where transitions actually happen
| From → To | Mechanism | File:line |
|---|---|---|
open → in_progress |
inquiry_activate (sets status + activated_at if open) |
ui_views.py:3207 |
open → in_progress |
inquiry_update_contact_stage stage under_process |
ui_views.py:4274 |
| resolved/closed → in_progress | inquiry_assign auto-reopens on reassignment |
ui_views.py:3282 |
| resolved/closed → in_progress | inquiry_reopen |
ui_views.py:3409 |
* → resolved |
inquiry_respond (sets response + status) — requires non-empty response |
ui_views.py:3486 |
* → resolved |
API InquiryViewSet.respond — requires response |
views.py:2963 |
* → resolved |
inquiry_change_status — blocks if no response |
ui_views.py:3356 |
* → closed |
inquiry_change_status with status="closed" (no dedicated close view) |
ui_views.py:3330 |
* → anything |
inquiry_change_status accepts arbitrary new_status |
ui_views.py:3345 |
save() stamps resolved_at/resolved_by/closed_at/closed_by on entry (models.py:1993-2000) using _acting_user (set by views).
4. Status Definitions
| Status | Purpose | When entered | Who moves forward | Next |
|---|---|---|---|---|
open |
Received; not yet owned/worked | On creation (default) | PX/Admin/Manager/Employee via inquiry_activate |
in_progress |
in_progress |
Being actively worked | Activation, reassignment of resolved/closed, reopen, under_process stage |
Assignee/PX/Champion | resolved, closed (or reopen) |
resolved |
A PX response has been written and sent | inquiry_respond / API respond / change_status (gated on response) |
PX/Admin/Hosp-Admin/Manager/Employee/Assignee/Champion | closed; reopen → in_progress |
closed |
Permanently completed | inquiry_change_status to closed |
PX/Admin/Hosp-Admin/Manager/Employee | reopen → in_progress (rare) |
is_active_status returns True for open/in_progress (models.py:2132).
⚠ Status-choice anomaly: the detail view exposes
status_choicesthat include"contacted"and"contacted_no_response"(ui_views.py:2758-2765). These are NOT valid Inquiry statuses (they'recontact_statusvalues mixed in). If selected via change-status, they persist as out-of-choices values — treat as a UI bug.
5. Workflow Actions
All UI inquiry URLs are in urls_inquiries.py (namespace inquiries); views in ui_views.py; API on InquiryViewSet (views.py:2890).
| Action | URL name | View (file:line) | Permission | Effect |
|---|---|---|---|---|
| Activate | inquiry_activate (<pk>/activate/) |
ui_views.py:3179 |
PX/Hosp-Admin/PX-Mgmt/PX-Employee | Assigns to self; open→in_progress + activated_at |
| Assign | inquiry_assign (<pk>/assign/) |
ui_views.py:3252 |
PX/Hosp-Admin/Dept-Manager-of-this-dept | Assign to user; auto-reopens resolved/closed → in_progress |
| Change status | inquiry_change_status (<pk>/change-status/) |
ui_views.py:3330 |
PX/Hosp-Admin/PX-Mgmt/PX-Employee | Arbitrary status; resolved blocked without response; only way to set closed |
| Reopen | inquiry_reopen (<pk>/reopen/) |
ui_views.py:3388 |
PX/Hosp-Admin/Dept-Manager | Only from resolved/closed; → in_progress |
| Add note | inquiry_add_note (<pk>/add-note/) |
ui_views.py:3439 |
@login_required (no role gate) |
Timeline note |
| Respond (PX) | inquiry_respond (<pk>/respond/) |
ui_views.py:3459 |
PX/Hosp-Admin/Assignee/Champion-of-(dept/outgoing_dept) | Writes inquiry.response, sets responded_at/_by, response_sent_at, → resolved; sends SMS+email to inquirer |
| Send to department | inquiry_transfer_to_department (<pk>/transfer-to-department/) |
ui_views.py:3578 |
PX/Hosp-Admin/Dept-Manager/PX-Mgmt/PX-Employee; rejects open | Full send + token (§14) |
| Send-to (AJAX) | inquiry_send_to (<pk>/send-to/) |
ui_views.py:3818 |
PX/Hosp-Admin/Dept-Manager/PX-Mgmt/PX-Employee; rejects open | Unified send to person OR department (§14) |
| Dept response (logged-in) | inquiry_department_response (<pk>/department-response/) |
ui_views.py:4003 |
PX/Hosp-Admin/Champion-of-(dept/outgoing_dept) | Champion submits department_response_en/ar + AI summary |
| Send dept-response reminder | inquiry_send_dept_response_reminder |
ui_views.py:4115 |
PX/Hosp-Admin/PX-Mgmt/PX-Employee | Manual reminder email to champion |
| Send to staff (token explanation) | inquiry_send_to_staff (<pk>/send-to-staff/) |
ui_views.py:2901 |
PX/Hosp-Admin/PX-Mgmt/PX-Employee | Token InquiryExplanation request to a staff member (§8) |
| Escalate | inquiry_escalate (<pk>/escalate/) |
ui_views.py:3728 |
PX/Hosp-Admin/PX-Mgmt/PX-Employee; blocks closed/cancelled | Stamps escalated_at, emails target |
| Update contact stage | inquiry_update_contact_stage (<pk>/update-contact/) |
ui_views.py:4227 |
PX/Hosp-Admin/Assignee | Updates 3-stage contact timeline + timeline_sla |
| Edit | inquiry_edit (<pk>/edit/) |
ui_views.py:3102 |
PX/Hosp-Admin/PX-Mgmt/PX-Employee; blocks closed |
Edit fields |
| Export incoming/outgoing | inquiry_export_* |
ui_views.py:4186,4206 |
@login_required (RBAC inside) |
Excel reports by is_outgoing |
| Public submit | public_inquiry_submit |
ui_views.py:4707 |
None | Public creation |
| Public track | public_inquiry_track |
ui_views.py:4843 |
None | Public status lookup by reference |
| Token response (dept) | inquiry_respond_with_token (<pk>/respond/<token>/) |
views.py:5141 |
None (token) | Champion submits dept response via token (§14) |
| Token explanation (staff) | inquiry_explanation_form (<id>/explain/<token>/) |
views.py:5077 |
None (token) | Staff submits InquiryExplanation |
| Restore | inquiry_restore |
ui_views.py:7227 |
PX/Hosp-Admin/PX-Mgmt/PX-Employee | Restores soft-deleted |
API (InquiryViewSet): respond (views.py:2962), generate_ai_response (views.py:2980 — bilingual AI reply draft), reanalyze_ai (views.py:3103), plus default ModelViewSet CRUD.
6. Decision Points
- Activation gate — every send endpoint rejects
status == "open"(ui_views.py:3594, 3841). Matchesdocs/workflows.md:7-9. - Resolve gate —
responsemust be non-empty (ui_views.py:3356, 3482; APIviews.py:2968). - Reopen gate — only from resolved/closed (
ui_views.py:3402); reassignment also reopens (ui_views.py:3282). - Escalate gate — blocked if closed/cancelled (
ui_views.py:3744). - Department must have champion/manager to be a transfer target (
ui_views.py:3610, 3916). - Need-more-info — no dedicated "request info" action; analogues are token
InquiryExplanation,inquiry_add_note, and the 3-stage contact tracking (contacted_no_response). - PX acceptance of dept response — ⚠ NO LONGER EXISTS (
docs/workflows.md:35is stale). See §14. - Patient confirmation — does not exist. Patient can only view status via public track. Reopen is staff-only.
7. Assignment Flow
- Initial owner — none.
assigned_to/assigned_atnull on creation.created_bystamped (models.py:1728). - Self-activation —
inquiry_activatesetsassigned_to = current_user,assigned_at, (if open)status=in_progress+activated_at(ui_views.py:3202). - Directed assignment —
inquiry_assign(ui_views.py:3268); emails assignee; notifies department. - Reassignment — same path; records old assignee; reopens if needed (
ui_views.py:3275). - Transfer / outgoing fields — two parallel concepts:
transferred_to_department(models.py:1824) andoutgoing_department(models.py:1834), both set together by transfer/send-to (ui_views.py:3638, 3923).transferred_at/transferred_by/transfer_counttrack the event. - Owner cascade —
Inquiry.get_owner()(models.py:2037): section(champion→supervisor→deputy_supervisor) → department(champion→deputy_manager→supervisor→deputy_supervisor→manager_2nd→manager_3rd). - Escalation —
inquiry_escalate: does NOT reassign; stampsescalated_at+ emails target. - Final owner — whoever performs
inquiry_respond(setsresponded_by) /change_statusto resolved/closed.
8. Investigation Process
InquiryExplanation (models.py:2380): "Staff/recipient response to an inquiry via token-based link. Mirrors ComplaintExplanation pattern." Fields: inquiry, staff, explanation, token (unique), is_used, submitted_via, SLA fields. InquiryExplanationAttachment (models.py:2460).
No investigation sub-flow (no Q&A loop). The mechanism is:
- PX/Admin triggers
inquiry_send_to_staff(ui_views.py:2901): creates anInquiryExplanationwith a fresh token; emails the staff a link/inquiries/<id>/explain/<token>/. - Staff opens
inquiry_explanation_form(views.py:5077), submits text + attachments;is_usedflipped,responded_atstamped. - The explanation is stored but not automatically promoted into
department_response_en— it's an advisory collection mechanism, distinct from the primary department-response path (§14).
⚠ Runtime bug:
inquiry_send_to_staffcallsInquirySLAConfig.get_active_config()(ui_views.py:2938) and readssla_config.dept_response_sla_hours(ui_views.py:2939). Neither exists —InquirySLAConfighas noget_active_configclassmethod (models.py:1432) and the field isdept_response_hours, notdept_response_sla_hours. This code path raisesAttributeError.
9. Communication Flow
| Event | Where |
|---|---|
| Acknowledgement/receipt | ⚠ none automatic (no creation signal); only staff notified via notify_staff_new_item |
| Progress update | timeline notes (inquiry_add_note); contact stages (inquiry_update_contact_stage) |
| Need-more-info | token explanation request to staff (inquiry_send_to_staff); inquirer "no response" = contacted_no_response stage |
| Department response | inquiry_department_response notifies inquirer SMS+email (ui_views.py:4075); token path inquiry_respond_with_token (views.py:5195) |
| Response sent/closure | inquiry_respond sends SMS+email to inquirer (ui_views.py:3512); in-app "resolved" notice to admins. No separate closure notification. |
| Public tracking | public_inquiry_track (ui_views.py:4843) — friendly labels/progress + department_response_en/ar |
Notification helpers (apps/notifications/settings_service.py): send_inquiry_department_assigned (:250), send_inquiry_assigned (:463), send_inquiry_resolved (:488), send_inquiry_reopened (:514).
10. Escalation Flow
Manual — inquiry_escalate (ui_views.py:3728): PX/Hosp-Admin/PX-Mgmt/PX-Employee; blocked if closed/cancelled; requires a valid active Staff; stamps escalated_at; does NOT change status/assignee; emails target (Hospital Admin/Dept Manager/role-holders, computed in inquiry_detail ui_views.py:2775).
Automatic (SLA breach) — InquirySLAConfig (models.py:1432): sla_hours, reminder timings, dept-response SLA fields. Celery beat:
check-overdue-inquiries→check_overdue_inquiries(tasks.py:2886) every 15 min: setsis_overdue/breached_at.send-inquiry-sla-reminders→send_inquiry_sla_reminders(tasks.py:2915) every 15 min: emailsassigned_tofirst + second reminders.
⚠ Department-response auto-escalation is DISABLED in code: check_overdue_inquiry_dept_responses (tasks.py:3126) flags dept_response_is_overdue but the auto-escalate branch unconditionally logs "Auto-escalation skipped ... (disabled)" and continues (tasks.py:3178), even though dept_response_auto_escalate_enabled defaults True (models.py:1487). So escalation is manual only.
11. Resolution Process
VERIFIED — docs/workflows.md:36-38 is correct: resolve requires a PX response.
inquiry_change_status:if new_status == "resolved" and not inquiry.response: reject(ui_views.py:3356).inquiry_respond: rejects empty (ui_views.py:3482); writesinquiry.response+ setsresolved(ui_views.py:3486); stampsresponse_sent_at+ notifies inquirer (ui_views.py:3512).- API
respond: same (views.py:2968).
Who can mark resolved: via inquiry_respond — PX/Hosp-Admin/assignee/Champion of department or outgoing_department (ui_views.py:3466); via change_status — PX/Hosp-Admin/PX-Mgmt/PX-Employee.
Approval required? No. Patient confirmation? Not required/captured. save() stamps resolved_at/resolved_by from _acting_user.
⚠ Gap:
inquiry_respondclearsresponse_en/response_arand writes only the aggregateresponsefield (ui_views.py:3487). The bilingual columns exist but this path doesn't populate them.
12. Closure Process
- No dedicated close view. Closure is via
inquiry_change_statuswithstatus="closed"(ui_views.py:3330). - Permission: PX/Hosp-Admin/PX-Mgmt/PX-Employee.
save()stampsclosed_at/closed_byon entry (models.py:1997).- Closed inquiries cannot be edited (
inquiry_editguard,ui_views.py:3116). - Reopen —
inquiry_reopenfrom resolved/closed → in_progress (ui_views.py:3388);inquiry_assignauto-reopens resolved/closed on reassignment (ui_views.py:3282). - Permanently completed? No permanent lock; a closed inquiry can always be reopened. Soft-deletion is separate, restorable.
13. Exception Flows
- Duplicate — no duplicate-detection.
- Withdrawn/Invalid — no withdrawn/invalid/cancelled status (those are Complaint statuses).
cancelledis referenced defensively (models.py:2167,ui_views.py:3744) but is never a valid Inquiry status. - Wrong department — handled by re-transfer (
transfer_countincrements); no explicit bounce-back. - Missing info —
inquiry_add_note/ tokenInquiryExplanation; no "pending info" status. - No patient response — 3-stage contact timeline (
contacted_nr_at,models.py:1886) +contact_status="contacted_no_response". No auto-close. - Transferred —
transfer_counttracks; both dept fields set;sent_to_department/sent_to_department_atcross-module signal set. - Merged — no merge feature.
- Reopened — see §12.
14. Department-Response Sub-Flow (CRITICAL)
14.1 Activation gate
inquiry_transfer_to_department (ui_views.py:3594) and inquiry_send_to (ui_views.py:3841) both reject status == "open" with "Activate this inquiry before sending it to a department." So open → in_progress (via inquiry_activate) is mandatory first.
14.2 How an inquiry is sent to a department
(A) inquiry_transfer_to_department (ui_views.py:3578) — the richer path:
- Validates: permission, not-open,
department_id, department active with champion/manager, valid contact person. - Sets:
outgoing_department,transferred_to_department = department,transferred_at,transferred_by,transfer_count += 1,sent_to_department = True,sent_to_department_at. - Computes dept-response SLA:
dept_response_sla_due_at = now + dept_response_hours; resets overdue/reminder/escalation flags. - Generates a one-time
response_token = secrets.token_urlsafe(32), setsresponse_token+response_token_sent_at. - Builds token link
https://{domain}/inquiries/{pk}/respond/{token}/; emails the contact person with deadline; callssend_inquiry_department_assigned. - Writes
InquiryUpdatetypetransferred_to_department.
(B) inquiry_send_to (ui_views.py:3818) — unified AJAX (person OR department):
- Department branch auto-targets champion+manager via
get_champion_and_manager. - Sets transfer fields + resets dept-response SLA.
- ⚠ Inconsistency: sets
sent_to_department = Trueonly ifinquiry.department_id == department.pk(ui_views.py:3929), unlike path (A) which always sets it. Sending to a different department via this endpoint does not set the cross-module "sent" signal. - Notifies champion+manager by email+SMS; does NOT generate a
response_token(no token link) — relies on in-app notification only.
14.3 Who receives it
The department's champion and/or manager (get_champion_and_manager). In path (A) the chosen contact_person (validated by department.is_valid_contact_person).
14.4 Champion's response submission (two ways)
(i) Token-response path — /inquiries/<pk>/respond/<token>/ → inquiry_respond_with_token (views.py:5141), no auth:
- Validates
response_tokenand not None; rejects ifresponse_token_used. - Accepts
response_en/response_ar(at least one required). - Writes
department_response_en/ar,department_responded_at, clearsdept_response_is_overdue,response_token_used = True. - Generates AI summary into
department_response_summary_en/ar. - Notifies the inquirer SMS+email with public track URL.
- ⚠ Dead code:
inquiry.dept_response_acceptance_status = "pending"atviews.py:5173— this field was removed (migration0039); silently dropped on save.
(ii) Authenticated champion response — /inquiries/<pk>/department-response/ → inquiry_department_response (ui_views.py:4003):
- Permission: PX/Hosp-Admin/Champion of
departmentoroutgoing_department. - Writes
department_response_en/ar,department_responded_at/_by, clears overdue; AI summary; notifies inquirer. - Does NOT set any acceptance status (field doesn't exist).
14.5 One-level review — accept/reject
⚠ STALE-DOC — this loop no longer exists in the data model. docs/workflows.md:34-35 and the field-name table reference dept_response_acceptance_status (pending/acceptable/not_acceptable). Migration 0001_initial.py:440 defined it; migration 0039 removed dept_response_acceptance_status, dept_response_acceptance_notes, dept_response_accepted_at, dept_response_accepted_by.
Consequences:
- No PX "accept"/"reject" view for Inquiry dept responses (no URL/function). The only
acceptance_statussetters are for Complaint. - The Inquiry model has no column to hold an accept/reject decision.
- The only surviving reference is the dead assignment at
views.py:5173.
So today the actual flow is: champion submits department_response_en/ar → it's simply available to PX (shown in detail, fed into generate_ai_response prompt) → PX writes their own patient-facing inquiry.response via inquiry_respond → status="resolved". No explicit accept/reject decision and no reject-loop. The champion's response is never "cleared" automatically; nothing returns a rejected response to the champion.
14.6 Reject loop
⚠ Not implemented for Inquiry (no backing field, no endpoint). docs/workflows.md:16-17 describes the loop generally; for Inquiry it's aspirational/dead.
Field-Name Reference (dept-response concept map)
| Concept | Field(s) on Inquiry |
File:line |
|---|---|---|
| Target dept | transferred_to_department / outgoing_department |
models.py:1824, 1834 |
| Sent flag | sent_to_department + sent_to_department_at |
models.py:1810, 1813 |
| Transfer meta | transferred_at, transferred_by, transfer_count |
models.py:1807, 1816, 1832 |
| Response text | department_response_en/ar + AI department_response_summary_en/ar |
models.py:1843-1846 |
| Response at/by | department_responded_at, department_responded_by |
models.py:1847-1854 |
| Acceptance | (REMOVED — migration 0039) |
was dept_response_acceptance_status |
| Token | response_token, response_token_used, response_token_sent_at |
models.py:1857-1862 |
| Dept SLA | dept_response_sla_due_at, dept_response_is_overdue, _reminder_sent_at, _escalated_at |
models.py:1865-1881 |
| PX reply | response, response_en/ar, response_sent_at, responded_at/_by |
models.py:1784-1791 |
15. End-to-End Example
Patient submits inquiry via public form
↓ (ui_views.py:4707) → status=open, ref INQ-202607-0001, due_at set,
AI analysis + admin notify dispatched
[⚠ inquirer NOT auto-acknowledged]
PX staff activates
↓ (ui_views.py:3179) → status=in_progress, activated_at, assigned_to=self
PX transfers to Department X
↓ (ui_views.py:3578) → transferred_to_department=X, sent_to_department=True,
response_token generated, token link emailed to champion,
dept_response_sla_due_at set
[DECISION: champion responds via token or logged-in]
Champion submits dept response via token link
↓ (views.py:5141) → department_response_en set, department_responded_at,
response_token_used=True, AI summary generated,
inquirer notified SMS+email
[⚠ NO PX accept/reject — field removed]
[DECISION POINT: PX must write patient-facing response]
PX writes inquiry.response (generate_ai_response optional)
↓ (ui_views.py:3459) → response set, responded_at/by, response_sent_at,
status=resolved, inquirer notified SMS+email,
admins notified
PX closes
↓ (ui_views.py:3330) → status=closed, closed_at/by
[PERMANENT unless reopened → in_progress]
Appendix — Flagged gaps vs docs/workflows.md
dept_response_acceptance_statusremoved (0039); the "PX accepts/rejects" loop is no longer backed by code. Dead assignment atviews.py:5173.- No
clean()/CheckConstrainton Inquiry; status enforced only ad-hoc in views; arbitrary strings persistable via change-status. - Bug in
inquiry_send_to_staff(ui_views.py:2938) — calls nonexistentInquirySLAConfig.get_active_config()/dept_response_sla_hours. - Auto dept-response escalation disabled (
tasks.py:3178). - Inconsistent
sent_to_departmentbetween transfer (always sets) and send-to (only when target == primary dept). - Invalid status choices exposed in detail view (
contacted/contacted_no_response). - No creation acknowledgement to inquirer.
inquiry_respondclearsresponse_en/ar, fills only aggregateresponse.is_straightforwardfield defined/serialized but never used in any decision.cancelled/partially_resolvedreferenced defensively but never valid Inquiry statuses.