Bugs 4-8 from the QA audit, all re-verified (re-run: 0 FAIL):
- token-response form pages (inquiry + observation, 8 templates) extended the
static dashboard base (no {% block content %}) -> form was dropped. Switched
to layouts/public_base.html; form + csrf now render, anonymous token POST works
- inquiry dept-response GET 500 (missing template) -> created
complaints/inquiry_department_response.html (mirrors the observation one)
- NameError get_email_header_html in notifications/settings_service.py
(send_inquiry_department_assigned/_resolved/_reopened) -> added to the imports
- "lucide is not defined" -> guarded the unguarded lucide.createIcons() in
layouts/base.html and added a guarded init to layouts/public_base.html
- dept analytics XHR ERR_ABORTED -> verified endpoint returns 200 (test artifact)
Report updated: §13 issues marked fixed.
20 KiB
Feedback Modules QA Audit Report
Modules: Complaint · Inquiry · Observation · Suggestion · Appreciation
Date: 2026-06-14
Environment: Local dev (runserver :8000 → PostgreSQL px360_db), headed Chromium (DISPLAY=:0), console email backend, isolated E2E Test Hospital mirroring HH-N (Al Nuzha) hierarchy.
Method: Exploratory, run-to-completion Playwright spec (e2e/tests/workflows/feedback-modules-audit.spec.ts); every step guarded; console/page-error/HTTP-error listeners captured observations into e2e/results/audit-observations.json. Roles tested deeply: px_admin, hospital_admin, dept_manager, px_employee, staff; access-reachability for all 10 roles.
Headline numbers (clean run, server stable)
- 26/26 audit tests completed (0 hard failures). 8.1 min.
- 124 observations: 50 PASS · 49 WARN · 10 INFO · 5 SKIP · 0 FAIL (server up)
- Baseline lifecycle specs: inquiry 7/7 pass, observation 5/6 pass, complaint 0/10 (suite broken — see §5).
1. Complaint
Routes: public /complaints/public/submit/, list /complaints/, detail /complaints/<id>/, track /complaints/public/track/, export /complaints/export/csv/.
✅ Handled correctly
- Public form submits successfully and creates a
Complaint(verified in DB: 4 records in E2E-HOSP). - Authenticated list renders table + status badges for
hospital_admin, dept_manager, px_employee, staff(24 badges each). - Detail page opens; add-note posts successfully; CSV export returns
200 text/csv(6 lines, valid header). - Authenticated create form (
/complaints/new/) renders.
⚠️ Issues found
- Reference number not surfaced/extractable on the public success page — the test couldn't capture a
CMP-…ref from body or URL, so the subsequent track test ran with a dummy ref. activateaction did not reflectin_progressin the page text after submit (status text present: false).px_adminlist renders no table (see cross-cutting §4.1).- One
net::ERR_ABORTEDon a detail navigation (test re-clicked; benign).
🔧 Improvements
- Display the reference number prominently on the public success page (and in the success URL) so submitters can track.
- Verify the activate/self-assign flow updates the visible status badge immediately.
2. Inquiry
Routes: public /inquiries/public/submit/, list /inquiries/, detail /inquiries/<id>/.
✅ Handled correctly
- Public form submits and creates an
Inquiry(3 records in E2E-HOSP). - List + detail open for
hospital_admin, dept_manager, px_employee, staff. - Respond modal opens for
hospital_admin(form present, closeable). - Status badges render.
⚠️ Issues found
- Respond action only visible to
hospital_admin—dept_manager,px_employee,staffsee no respond button on inquiry detail. Confirm whether this is intended RBAC or a gap. - Public submit did not surface an
INQ-…reference (regex didn't match the success body) — minor. px_adminlist renders no table (§4.1).
🔧 Improvements
- Clarify/extend inquiry "respond" permissions to the roles expected to action inquiries.
- Show the inquiry reference on the public success page.
3. Observation
Routes: public /observations/new/, success /observations/submitted/<code>/, track /observations/track/, list /observations/, detail /observations/<id>/.
✅ Handled correctly
- Public form renders, and a valid POST (with
incident_datetimeinYYYY-MM-DD HH:MM:SS) returns 302 with a tracking code (e.g.OBS-R208ER) — 3 records created in E2E-HOSP. - Public track page responds.
- List + detail open for
hospital_admin, dept_manager, px_employee, staff(all pass).
⚠️ Issues found
- UI cascade is fragile & format-sensitive: the public form's
hospital → location_type → area → category → department → sectionis pure AJAX; submission silently re-renders (200, no redirect) unlessincident_datetimeincludes seconds (2026-06-13 12:00fails,2026-06-13 12:00:00works). A real user using the date picker is fine, but raw entry is unforgiving. px_adminlist renders no table (§4.1).
🔧 Improvements
- Add a proper datetime picker so the canonical format is always sent.
- Show client-side validation errors clearly when the cascade fields are incomplete.
4. Suggestion (/suggestions/, feedback app)
Routes: public /suggestions/public/suggestion/ (POST-only), list /suggestions/, create /suggestions/create/, detail /suggestions/<id>/.
✅ Handled correctly
- Authenticated list renders for
hospital_admin, dept_manager, px_employee, staff. - Public endpoint correctly returns 405 on GET (POST-only) and 400 on empty POST — i.e. the endpoint is wired and validating.
⚠️ Issues found (highest-impact)
- No public GET form for suggestions — anonymous users have no UI to submit a suggestion (only a POST endpoint). Inconsistent with Complaint/Inquiry/Observation which all have public GET forms.
- Authenticated create did NOT persist a record. The test reported a "success indicator" but 0
Feedbackrows exist in E2E-HOSP (only 2 stale rows in HH-N from prior manual testing). The create form (feedback_form.html) fields arecomm_req, contact_name, contact_phone, hospital(select), message— notitle,category, orfeedback_typeinputs, yet the test (and theFeedbackFormModelForm) assume them. The customfeedback_createview reads only those 5 fields. → The authenticated suggestion-create flow needs manual verification; the automated harness could not reliably drive it.
🔧 Improvements
- Add a public GET form for suggestions (parity with other modules), or document that suggestions are internal-only.
- Reconcile the create form's actual fields with the
FeedbackFormModelForm (title/category/feedback_type are model fields but absent from the create UI) and verify an authenticated user can actually save a suggestion end-to-end.
5. Appreciation (/appreciation/)
Routes: public /appreciation/public/submit/ (JSON POST), list /appreciation/, detail /appreciation/detail/<id>/, activate /appreciation/detail/<id>/activate/, send /appreciation/detail/<id>/send/, acknowledge /appreciation/acknowledge/<id>/.
✅ Handled correctly
- Public JSON POST creates a DRAFT (
APR-…ref returned, CSRF handled) — 2 records in E2E-HOSP. - List + detail open for
hospital_admin. - Full internal workflow works:
activate(select staff → activated, AI analysis triggered) →send(status → sent, notification dispatched to console). ✅
⚠️ Issues found
- No public GET form — like suggestions, appreciation public submission is POST-only (GET → 405). No anonymous-facing UI.
acknowledgewas not exercised (only the recipient can acknowledge; out of scope for a non-recipient session).- One
net::ERR_ABORTEDon a detail navigation (benign test artifact).
🔧 Improvements
- Add a public GET form (parity), or document that appreciation submission is JSON-API-only.
- Add an automated path to test
acknowledge(login as the recipient staff's user).
6. Cross-cutting findings (prioritized)
🔴 P1 — px_admin sees empty lists across ALL modules
For Complaint, Inquiry, Observation, and Suggestion, px_admin (confirmed logged in, scoped to E2E Test Hospital, is_px_admin()=True) renders no data table, while every other role sees populated tables. Records demonstrably exist in E2E-HOSP. This points to the tenant_hospital session-scoping logic for PX Admins not resolving E2E-HOSP (the list views branch on if user.is_px_admin(): if selected_hospital: …). Needs investigation — PX Admin is the primary oversight role and currently appears to get a blank view.
🔴 P1 — Suggestion authenticated create doesn't persist
See §4. No Feedback record is saved from the authenticated create flow. The create form's fields don't match the model/form assumptions.
🟡 P2 — Inconsistent public-form parity
| Module | Public GET form | Public submit |
|---|---|---|
| Complaint | ✅ | ✅ |
| Inquiry | ✅ | ✅ |
| Observation | ✅ | ✅ |
| Suggestion | ❌ (POST-only) | ⚠️ endpoint exists |
| Appreciation | ❌ (POST-only) | ⚠️ JSON-only |
Anonymous UX is inconsistent. Either add GET forms for the last two or document them as internal/API-only.
🟡 P2 — Inconsistent reference-number formats
CMP-YYYYMMDD-NNNNNN, INQ-<8 hex>, OBS-<6 alnum>, APR-<8 hex>. Consider a unified scheme for tracking/UX consistency.
🟢 P3 — Reference numbers not surfaced on success pages
Complaint & Inquiry public success pages don't expose the reference in an easily extractable way (affects tracking UX and the public track flow).
🟢 P3 — Inquiry "respond" limited to hospital_admin
Other actioning roles (dept_manager, px_employee, staff) see no respond button. Confirm intent.
🟢 P3 — Observation public form date format & cascade fragility
Requires incident_datetime with seconds; cascade gives no inline validation feedback.
⚪ Ops — Dev server stability under automation
During one run the runserver autoreloader restarted mid-suite (connection reset → refused), invalidating that pass. For repeatable automation, run a dedicated QA server with --noreload (or a gunicorn/uvicorn instance) instead of the autoreloading dev server.
7. Role / access matrix (observed)
All 10 roles were able to reach every module root (no incorrect login-redirect blocks). The audit's PASS/WARN labels for the matrix are spurious (a comparison-flaw in the exploratory status logic: onLogin === expected mixed "blocked?" with "expected-allowed?"), so the matrix should be treated as inconclusive for fine-grained RBAC. The documented e2e/tests/roles/access-matrix.spec.ts remains the source of truth for reachability; this audit did not contradict it. Recommended: fix the audit's matrix status comparison and re-run for a clean RBAC signal.
8. Baseline lifecycle comparison (existing specs)
| Spec | Result | Notes |
|---|---|---|
complaint-lifecycle.spec.ts |
0/10 run | BROKEN — uses outdated #id_complainant_name / #id_… selectors; the public complaint form now exposes fields by name= only. Test 1 fails → 9 skipped (serial). Update selectors. |
inquiry-lifecycle.spec.ts |
7/7 pass | ✅ Healthy. |
observation-lifecycle.spec.ts |
5/6 pass | 1 fail: "severity badges render correctly". Public submit works here. |
The complaint-lifecycle staleness is itself a finding — the public complaint form's field IDs were removed but the test wasn't updated.
9. What was changed for this audit (all reversible)
- New:
apps/core/management/commands/create_e2e_isolated_env.py— builds isolated E2E-HOSP (mirrors HH-N: 27 Areas, 37 Departments, 171 Sections, 1 SubSection) + 10 role users. - New:
e2e/tests/workflows/feedback-modules-audit.spec.ts+e2e/helpers/audit.ts. - Edited:
.env→EMAIL_BACKEND=console(backup at.env.bak_qa);playwright.config.ts→ env-overridabletimeout/actionTimeout/navigationTimeout/slowMo(defaults preserve prior behavior). - Created: isolated hospital
E2E-HOSP(kept for future QA), orgE2E-ORG, sourceE2E-TEST, 10e2e-*@px360.testusers (passwordDev@123456), plus audit-generated records inside E2E-HOSP. - No migrations, no commits.
10. Reproduce
# 1. (one-time) build the isolated sandbox mirroring HH-N
uv run manage.py create_e2e_isolated_env --delete-existing
# 2. email -> console (already applied; .env.bak_qa has the SMTP original)
# 3. run the audit headed
E2E_TIMEOUT=120000 E2E_ACTION_TIMEOUT=12000 E2E_NAV_TIMEOUT=20000 E2E_SLOWMO=60 \
npx playwright test e2e/tests/workflows/feedback-modules-audit.spec.ts --workers=1 --headed
# 4. observations + report inputs
cat e2e/results/audit-observations.json
11. Recommended next steps (for these 5 modules)
- Fix P1
px_adminempty-list scoping. - Fix P1 suggestion authenticated create persistence; reconcile form fields.
- Decide public-form parity for suggestion & appreciation (add GET forms or document as internal).
- Update
complaint-lifecycle.spec.tsselectors (name=instead of#id_). - Surface reference numbers on all public success pages.
- Re-run this audit after fixes (sandbox is retained); then proceed to the next module group.
12. Champion / Manager Workflow Audit (send-to-department lifecycle)
Covers the full multi-actor loop the original audit missed: PX-team sends → champion investigates → manager reviews → PX-team accepts → resolve, plus the token investigation sub-flow and both rejection loops. Spec: e2e/tests/workflows/champion-manager-workflow.spec.ts. State asserted after every step via the get_e2e_workflow_state CLI helper (Node can't read the DB).
Result: 4/4 flows pass. 32 observations: 26 PASS · 1 FAIL · 3 WARN · 2 INFO. Two real bugs were found and fixed during the run.
✅ Handled correctly (verified end-to-end)
| Flow | Path | Result |
|---|---|---|
| A — happy path | PX send → champion response → manager approve → PX accept → resolve | ✅ all 11 state transitions correct (sent → response_submitted+pending → approved → acceptable → resolved) |
| B — token investigation | champion creates Qs (token) → staff answers (token) → champion reviews + writes response | ✅ response_submitted=True, manager_review_status=pending |
| C1 — manager reject loop | manager rejects → response cleared + response_submitted=False → champion re-responds → approve → accept → resolve |
✅ reject correctly returns the complaint to the champion |
| C2 — PX not-acceptable loop | PX marks not-acceptable → response cleared → champion re-responds → approve → accept → resolve | ✅ correctly returns to champion |
The ComplaintInvolvedDepartment state machine behaves exactly as designed: sent → response_submitted(pending) → approved → acceptable, and both rejection branches reset response_submitted=False + clear the response, sending it back to the champion.
🔧 Bugs found & fixed during this audit
NameError: name 'InvestigationAnswer' is not definedatapps/complaints/views.py:4021—champion_start_investigationusedInvestigationAnswerwithout importing it, so the champion's "create questions" POST returned 500 (DB writes before the crash still persisted, masking the error). Fixed: addedInvestigationAnswerto the view's imports.AttributeErrorwaiting to happen atapps/complaints/views.py:4064—staff_member.phone_number, but theStaffmodel field isphone. Would have crashed the moment the NameError above was fixed. Fixed:phone_number→phone.
⚠️ Remaining issues
ReferenceError: lucide is not defined(frontend, Flow B token pages) — the icon library (lucide) isn't loaded on the investigation templates (investigation_questions.html/investigation_respond.html/investigation_review.html). Cosmetic only; the forms still work. Fix: include the lucide script on those templates (or use the icon partial the rest of the app uses).GET /organizations/departments/<id>/analytics/→ ERR_ABORTED (Flows C1/C2) — the department-analytics XHR on the dept pages fails (likely 404/500). Minor; doesn't block the workflow.- SMS gateway (Mshastra) "IP address not allowed" — external provider config, not app code; expected in dev.
🔩 Test harness added
apps/core/management/commands/create_e2e_isolated_env.pyextended — bindse2e-dept-managerasdepartment.managerand creates aStaffprofile fore2e-staffin the champion's department (Contact Center), so all three workflow roles exist.apps/core/management/commands/seed_e2e_complaint.py(new) — seeds an open complaint + accused staff, prints ids.apps/core/management/commands/get_e2e_workflow_state.py(new) — prints the full workflow state (idept fields + tokens) for assertions.e2e/tests/workflows/champion-manager-workflow.spec.ts(new) — Flows A/B/C1/C2, drives the real HTTP endpoints (AJAX + form POSTs), asserts state after each step.
Reproduce
uv run manage.py create_e2e_isolated_env --delete-existing
E2E_TIMEOUT=120000 E2E_ACTION_TIMEOUT=12000 E2E_NAV_TIMEOUT=20000 \
npx playwright test e2e/tests/workflows/champion-manager-workflow.spec.ts --workers=1 --headed
13. Inquiry & Observation dept-response workflow (simpler flow)
These two modules use a simpler flow than complaints: PX-team sends to department → champion responds → PX reviews (accept/reject). No manager review, no investigation questions. State is flat fields on the Inquiry/Observation model (no join table). Spec: e2e/tests/workflows/inquiry-observation-dept-workflow.spec.ts, 6 flows (3 per module), driven via the real HTTP endpoints with state asserted via get_e2e_dept_response_state.
Result: 6/6 flows pass. 59 observations: 50 PASS · 5 WARN · 0 FAIL.
✅ Handled correctly (verified)
| Flow | Inquiry | Observation |
|---|---|---|
| A — happy path (send → champion response → PX accept → resolve) | ✅ | ✅ (resolve best-effort — see below) |
| B — token path (send mints token → champion token response → PX accept) | ✅ backend | ✅ backend |
| C — reject loop (PX not-acceptable → response cleared → champion re-responds → accept → resolve) | ✅ | ✅ |
State machines behave correctly: send sets transferred_to_department (inquiry) / sent_to_department=True (observation); champion response sets department_response_en + department_responded_at + dept_response_acceptance_status=pending; PX acceptable → acceptance_status=acceptable; PX not_acceptable → clears the response and returns it to the champion (verified — response_en_set=False after reject). Inquiry resolve requires a PX response first (inquiry_respond) — handled.
⚠️ Issues found → ✅ fixed (re-verified, 0 FAIL)
- Token-response form page is broken (both modules) — ✅ FIXED. Root cause: the 8 public/token templates
{% extends "template.html" %}, a static dashboard with no{% block content %}, so the form block was dropped. Fix: changed all 8 to{% extends "layouts/public_base.html" %}. Re-verified:B-token-form-rendernow PASS (form + csrf render), anonymous token POST works. NameError: get_email_header_htmlduring inquiry transfer — ✅ FIXED. Root cause:apps/notifications/settings_service.pyimportedNotificationServicebut notget_email_header_html. Fix: addedget_email_header_htmlto the lazy imports. Re-verified: no NameError in the re-run.- Inquiry dept-response page 500 (
DoesNotExist) — ✅ FIXED. Root cause:templates/complaints/inquiry_department_response.htmlwas missing. Fix: created it (mirrors the observation template). Re-verified: GET renders, no 500. lucide is not defined(icons broken) — ✅ FIXED. Root cause: unguardedlucide.createIcons()inlayouts/base.html:157threw when lucide hadn't initialized during rapid navigation. Fix: guarded that call + added a guarded init tolayouts/public_base.html./organizations/departments/<id>/analytics/XHR failure — ✅ not a bug. Endpoint returns 200; theERR_ABORTEDwas a test-navigation artifact.
⚠️ Remaining (behavior, not bugs)
- Observation can't jump
new → resolved— its status machine requires intermediate steps (new → triaged/assigned/in_progress → resolved).observation_change_statusaccepts the POST (302) but the status staysnew. Confirm whether the dept-response flow should drive the status to a resolvable state (e.g., auto-activate on send). Reported as WARN, not FAIL.
🔩 Test harness added
apps/core/management/commands/seed_e2e_dept_response.py— seeds an open inquiry/observation, prints ids.apps/core/management/commands/get_e2e_dept_response_state.py— prints the dept-response state + token for assertions.playwright.config.ts—E2E_MAXIMIZED=1launches the browser maximized/fullscreen (viewport=null +--start-maximized).
Reproduce
E2E_MAXIMIZED=1 E2E_TIMEOUT=120000 E2E_ACTION_TIMEOUT=12000 E2E_NAV_TIMEOUT=20000 E2E_SLOWMO=70 \
npx playwright test e2e/tests/workflows/inquiry-observation-dept-workflow.spec.ts --workers=1 --headed