diff --git a/apps/notifications/settings_service.py b/apps/notifications/settings_service.py index b613968..a446941 100644 --- a/apps/notifications/settings_service.py +++ b/apps/notifications/settings_service.py @@ -29,7 +29,7 @@ def _get_quiet_hours_end_datetime(settings): @shared_task def _send_deferred_sms(phone, message, related_object_app=None, related_object_model=None, related_object_id=None): - from .services import NotificationService + from .services import NotificationService, get_email_header_html related_object = None if related_object_app and related_object_model and related_object_id: @@ -43,7 +43,7 @@ def _send_deferred_sms(phone, message, related_object_app=None, related_object_m @shared_task def _send_deferred_whatsapp(phone, message, related_object_app=None, related_object_model=None, related_object_id=None): - from .services import NotificationService + from .services import NotificationService, get_email_header_html related_object = None if related_object_app and related_object_model and related_object_id: @@ -144,7 +144,7 @@ class NotificationServiceWithSettings: @staticmethod def _defer_sms_if_quiet_hours(settings, phone, message, related_object): - from .services import NotificationService + from .services import NotificationService, get_email_header_html if settings.is_quiet_hours(): eta = _get_quiet_hours_end_datetime(settings) @@ -165,7 +165,7 @@ class NotificationServiceWithSettings: @staticmethod def _defer_whatsapp_if_quiet_hours(settings, phone, message, related_object): - from .services import NotificationService + from .services import NotificationService, get_email_header_html if settings.is_quiet_hours(): eta = _get_quiet_hours_end_datetime(settings) @@ -249,7 +249,7 @@ class NotificationServiceWithSettings: @staticmethod def send_inquiry_department_assigned(department, inquiry, context_note_en="", context_note_ar="", recipient_type="staff"): from apps.accounts.models import User - from .services import NotificationService + from .services import NotificationService, get_email_header_html context_note_parts = [] if context_note_en: @@ -355,7 +355,7 @@ class NotificationServiceWithSettings: @staticmethod def send_observation_department_assigned(department, observation, context_note_en="", context_note_ar="", recipient_type="staff"): from apps.accounts.models import User - from .services import NotificationService + from .services import NotificationService, get_email_header_html context_note_parts = [] if context_note_en: @@ -541,7 +541,7 @@ class NotificationServiceWithSettings: @staticmethod def send_explanation_reminder(staff_email, complaint): """Send explanation reminder notification""" - from .services import NotificationService + from .services import NotificationService, get_email_header_html hospital_id = NotificationServiceWithSettings._get_hospital_id_from_complaint(complaint) if not hospital_id: @@ -587,7 +587,7 @@ class NotificationServiceWithSettings: @staticmethod def send_explanation_overdue(manager_email, complaint, staff_name): """Send explanation overdue/escalation notification to manager""" - from .services import NotificationService + from .services import NotificationService, get_email_header_html hospital_id = NotificationServiceWithSettings._get_hospital_id_from_complaint(complaint) if not hospital_id: @@ -627,7 +627,7 @@ class NotificationServiceWithSettings: @staticmethod def send_explanation_received(assignee_email, complaint, explanation): """Send notification when explanation is received""" - from .services import NotificationService + from .services import NotificationService, get_email_header_html hospital_id = NotificationServiceWithSettings._get_hospital_id_from_complaint(complaint) if not hospital_id: @@ -660,7 +660,7 @@ class NotificationServiceWithSettings: """Send notification when complaint is assigned""" from django.template.loader import render_to_string - from .services import NotificationService + from .services import NotificationService, get_email_header_html # Build template context context = { @@ -721,7 +721,7 @@ class NotificationServiceWithSettings: Send onboarding invitation to new provisional user. Respects hospital notification settings. """ - from .services import NotificationService + from .services import NotificationService, get_email_header_html hospital_id = getattr(provisional_user, "hospital_id", None) if not hospital_id: @@ -766,7 +766,7 @@ class NotificationServiceWithSettings: @staticmethod def send_onboarding_reminder(user_email, provisional_user): """Send onboarding reminder notification""" - from .services import NotificationService + from .services import NotificationService, get_email_header_html hospital_id = getattr(provisional_user, "hospital_id", None) if not hospital_id: @@ -807,7 +807,7 @@ class NotificationServiceWithSettings: @staticmethod def send_onboarding_completion_notification(admin_email, completed_user): """Send notification to admin when user completes onboarding""" - from .services import NotificationService + from .services import NotificationService, get_email_header_html hospital_id = getattr(completed_user, "hospital_id", None) if not hospital_id: diff --git a/reports/feedback-modules-qa-report.md b/reports/feedback-modules-qa-report.md index 1f837fa..ee79331 100644 --- a/reports/feedback-modules-qa-report.md +++ b/reports/feedback-modules-qa-report.md @@ -235,15 +235,15 @@ These two modules use a **simpler** flow than complaints: **PX-team sends to dep 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 -1. **Token-response form page is broken (both modules).** GET `/inquiries//respond//` and `/observations//respond//` render the **dashboard chrome with no response form** (no `response_en` textarea) instead of `inquiry_response_form_token.html` / `response_form_token.html`. The backend POST still accepts the token response, but a **truly anonymous visitor can't submit** (the broken page renders no `{% csrf_token %}`, so they get a 403 on POST). High-impact for the email-link UX since champions receive this link by email. Reproduced via Django test client (GET returns a 13176-char page titled "PX360 Dashboard - Blue Edition" with no `
`). -2. **`NameError: get_email_header_html` during inquiry transfer** (`inquiry_transfer_to_department`) — the department-assigned notification email silently fails ("Failed to send department notification: name 'get_email_header_html' is not defined"). Caught/logged, so the transfer succeeds, but the notification isn't sent. -3. **Observation can't jump `new → resolved`** — its status machine requires intermediate steps (new → triaged/assigned/in_progress → resolved). `observation_change_status` accepts the POST (302) but the status stays `new`. (Reported as WARN, not FAIL — resolution is outside the dept-response flow's core.) +## ⚠️ Issues found → ✅ fixed (re-verified, 0 FAIL) +1. **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-render` now PASS (form + csrf render), anonymous token POST works. +2. **`NameError: get_email_header_html` during inquiry transfer** — ✅ **FIXED.** Root cause: `apps/notifications/settings_service.py` imported `NotificationService` but not `get_email_header_html`. Fix: added `get_email_header_html` to the lazy imports. Re-verified: no NameError in the re-run. +3. **Inquiry dept-response page 500 (`DoesNotExist`)** — ✅ **FIXED.** Root cause: `templates/complaints/inquiry_department_response.html` was missing. Fix: created it (mirrors the observation template). Re-verified: GET renders, no 500. +4. **`lucide is not defined` (icons broken)** — ✅ **FIXED.** Root cause: unguarded `lucide.createIcons()` in `layouts/base.html:157` threw when lucide hadn't initialized during rapid navigation. Fix: guarded that call + added a guarded init to `layouts/public_base.html`. +5. **`/organizations/departments//analytics/` XHR failure** — ✅ **not a bug.** Endpoint returns 200; the `ERR_ABORTED` was a test-navigation artifact. -## 🔧 Improvements recommended -- **Fix the token-response form pages** so they render the `response_en`/`response_ar` form (and a csrf token) — the templates exist and are correct; the view/template-wiring is returning the dashboard instead. This unblocks the email-link response path for champions. -- **Fix `get_email_header_html` import** in `inquiry_transfer_to_department` (mirror the fix pattern used elsewhere, e.g. complaints). -- Confirm whether observation resolution should be reachable directly from the dept-response flow (add an `activate`-equivalent or allow `new → resolved` if appropriate). +## ⚠️ Remaining (behavior, not bugs) +- **Observation can't jump `new → resolved`** — its status machine requires intermediate steps (new → triaged/assigned/in_progress → resolved). `observation_change_status` accepts the POST (302) but the status stays `new`. 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. diff --git a/templates/complaints/inquiry_department_response.html b/templates/complaints/inquiry_department_response.html new file mode 100644 index 0000000..6bcd492 --- /dev/null +++ b/templates/complaints/inquiry_department_response.html @@ -0,0 +1,88 @@ +{% extends "layouts/base.html" %} +{% load i18n %} + +{% block title %}{{ inquiry.reference_number }} - {% trans "Department Response" %} - PX360{% endblock %} + +{% block content %} +
+
+ {% trans "Inquiries" %} + + {{ inquiry.reference_number }} + + {% trans "Department Response" %} +
+

+ {{ inquiry.subject }} +

+
+ +
+
+ +

{% trans "Submit Department Response" %}

+
+ +
+

{% trans "Reference" %}

+

{{ inquiry.reference_number }}

+ {% if inquiry.contact_name %} +

{% trans "From" %}

+

{{ inquiry.contact_name }}

+ {% endif %} +

{% trans "Message" %}

+

{{ inquiry.message|truncatewords:50 }}

+
+ + {% if messages %} +
+ {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} +
+ {% endif %} + + + {% csrf_token %} + +
+
+ + +
+ +
+ + +
+ +

+ + {% trans "At least one language is required." %} +

+
+ +
+ + + {% trans "Cancel" %} + +
+ +
+{% endblock %} \ No newline at end of file diff --git a/templates/complaints/inquiry_response_already_submitted.html b/templates/complaints/inquiry_response_already_submitted.html index ea771b2..e1466e9 100644 --- a/templates/complaints/inquiry_response_already_submitted.html +++ b/templates/complaints/inquiry_response_already_submitted.html @@ -1,4 +1,4 @@ -{% extends "template.html" %} +{% extends "layouts/public_base.html" %} {% load i18n %} {% block title %}{% trans "Response Already Submitted" %}{% endblock %} diff --git a/templates/complaints/inquiry_response_form_token.html b/templates/complaints/inquiry_response_form_token.html index d3c2715..0bc7081 100644 --- a/templates/complaints/inquiry_response_form_token.html +++ b/templates/complaints/inquiry_response_form_token.html @@ -1,4 +1,4 @@ -{% extends "template.html" %} +{% extends "layouts/public_base.html" %} {% load i18n %} {% block title %}{% trans "Respond to Inquiry" %} - {{ inquiry.reference_number }}{% endblock %} diff --git a/templates/complaints/inquiry_response_success_token.html b/templates/complaints/inquiry_response_success_token.html index cbce6fe..30101d0 100644 --- a/templates/complaints/inquiry_response_success_token.html +++ b/templates/complaints/inquiry_response_success_token.html @@ -1,4 +1,4 @@ -{% extends "template.html" %} +{% extends "layouts/public_base.html" %} {% load i18n %} {% block title %}{% trans "Response Submitted" %}{% endblock %} diff --git a/templates/complaints/inquiry_response_token_invalid.html b/templates/complaints/inquiry_response_token_invalid.html index 6904ce5..4671d9e 100644 --- a/templates/complaints/inquiry_response_token_invalid.html +++ b/templates/complaints/inquiry_response_token_invalid.html @@ -1,4 +1,4 @@ -{% extends "template.html" %} +{% extends "layouts/public_base.html" %} {% load i18n %} {% block title %}{% trans "Invalid Link" %}{% endblock %} diff --git a/templates/layouts/base.html b/templates/layouts/base.html index 84553f9..b5cb57d 100644 --- a/templates/layouts/base.html +++ b/templates/layouts/base.html @@ -154,7 +154,7 @@ } // Initialize Lucide icons - lucide.createIcons(); + if (typeof lucide !== 'undefined') { lucide.createIcons(); } // Mobile sidebar toggle function toggleSidebar() { diff --git a/templates/layouts/public_base.html b/templates/layouts/public_base.html index 4605ef9..6bd1a47 100644 --- a/templates/layouts/public_base.html +++ b/templates/layouts/public_base.html @@ -223,6 +223,7 @@