fix: token-response forms, inquiry dept-response 500, get_email_header_html, lucide
All checks were successful
Build and Push Docker Image / build (push) Successful in 2m15s
All checks were successful
Build and Push Docker Image / build (push) Successful in 2m15s
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.
This commit is contained in:
parent
8c75baf30a
commit
adff7dd8b5
@ -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:
|
||||
|
||||
@ -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/<id>/respond/<token>/` and `/observations/<id>/respond/<token>/` 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 `<form>`).
|
||||
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/<id>/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.
|
||||
|
||||
88
templates/complaints/inquiry_department_response.html
Normal file
88
templates/complaints/inquiry_department_response.html
Normal file
@ -0,0 +1,88 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ inquiry.reference_number }} - {% trans "Department Response" %} - PX360{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<header class="mb-6">
|
||||
<div class="flex items-center gap-2 text-sm text-slate mb-2">
|
||||
<a href="{% url 'inquiries:inquiry_list' %}" class="hover:text-navy">{% trans "Inquiries" %}</a>
|
||||
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||
<a href="{% url 'inquiries:inquiry_detail' pk=inquiry.pk %}" class="hover:text-navy">{{ inquiry.reference_number }}</a>
|
||||
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||
<span class="font-bold text-navy">{% trans "Department Response" %}</span>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-navy">
|
||||
{{ inquiry.subject }}
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-slate-100 p-6">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<i data-lucide="message-square" class="w-5 h-5 text-navy"></i>
|
||||
<h2 class="text-lg font-bold text-navy">{% trans "Submit Department Response" %}</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-50 rounded-xl p-4 border border-slate-200 mb-6">
|
||||
<p class="text-[10px] text-slate-500 uppercase font-bold mb-1">{% trans "Reference" %}</p>
|
||||
<p class="font-mono text-sm font-bold text-navy">{{ inquiry.reference_number }}</p>
|
||||
{% if inquiry.contact_name %}
|
||||
<p class="text-[10px] text-slate-500 uppercase font-bold mb-1 mt-3">{% trans "From" %}</p>
|
||||
<p class="text-sm text-slate-700">{{ inquiry.contact_name }}</p>
|
||||
{% endif %}
|
||||
<p class="text-[10px] text-slate-500 uppercase font-bold mb-1 mt-3">{% trans "Message" %}</p>
|
||||
<p class="text-sm text-slate-700 leading-relaxed">{{ inquiry.message|truncatewords:50 }}</p>
|
||||
</div>
|
||||
|
||||
{% if messages %}
|
||||
<div class="mb-4">
|
||||
{% for message in messages %}
|
||||
<div class="px-4 py-3 rounded-lg text-sm font-semibold {% if message.tags == 'error' %}bg-red-50 text-red-700 border border-red-200{% else %}bg-green-50 text-green-700 border border-green-200{% endif %}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{% url 'inquiries:inquiry_department_response' pk=inquiry.pk %}">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-slate-700 mb-2">
|
||||
{% trans "Response (English)" %}
|
||||
</label>
|
||||
<textarea name="response_en" rows="5"
|
||||
class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl text-slate-700 focus:outline-none focus:border-navy focus:ring-2 focus:ring-navy/20 resize-none text-sm"
|
||||
placeholder="{% trans 'Enter your response in English...' %}">{{ inquiry.department_response_en|default:'' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-slate-700 mb-2">
|
||||
{% trans "Response (Arabic)" %}
|
||||
</label>
|
||||
<textarea name="response_ar" rows="5" dir="rtl"
|
||||
class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl text-slate-700 focus:outline-none focus:border-navy focus:ring-2 focus:ring-navy/20 resize-none text-sm"
|
||||
placeholder="{% trans 'أدخل ردك باللغة العربية...' %}">{{ inquiry.department_response_ar|default:'' }}</textarea>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-slate-500">
|
||||
<i data-lucide="info" class="w-3 h-3 inline mr-1"></i>
|
||||
{% trans "At least one language is required." %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 mt-6 pt-4 border-t border-slate-100">
|
||||
<button type="submit"
|
||||
class="px-6 py-2.5 bg-navy text-white font-semibold rounded-xl hover:bg-blue transition flex items-center gap-2 text-sm">
|
||||
<i data-lucide="send" class="w-4 h-4"></i>
|
||||
{% trans "Submit Response" %}
|
||||
</button>
|
||||
<a href="{% url 'inquiries:inquiry_detail' pk=inquiry.pk %}"
|
||||
class="px-6 py-2.5 bg-slate-100 text-slate-700 font-medium rounded-xl hover:bg-slate-200 transition text-sm">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "template.html" %}
|
||||
{% extends "layouts/public_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Response Already Submitted" %}{% endblock %}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "template.html" %}
|
||||
{% extends "layouts/public_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Respond to Inquiry" %} - {{ inquiry.reference_number }}{% endblock %}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "template.html" %}
|
||||
{% extends "layouts/public_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Response Submitted" %}{% endblock %}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "template.html" %}
|
||||
{% extends "layouts/public_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Invalid Link" %}{% endblock %}
|
||||
|
||||
@ -154,7 +154,7 @@
|
||||
}
|
||||
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
if (typeof lucide !== 'undefined') { lucide.createIcons(); }
|
||||
|
||||
// Mobile sidebar toggle
|
||||
function toggleSidebar() {
|
||||
|
||||
@ -223,6 +223,7 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/js/tom-select.complete.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (typeof lucide !== 'undefined') { lucide.createIcons(); }
|
||||
document.querySelectorAll('select[data-tomselect]').forEach(function(el) {
|
||||
var instance = new TomSelect(el, {
|
||||
create: false,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "template.html" %}
|
||||
{% extends "layouts/public_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Response Already Submitted" %}{% endblock %}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "template.html" %}
|
||||
{% extends "layouts/public_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Respond to Observation" %} - {{ observation.tracking_code }}{% endblock %}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "template.html" %}
|
||||
{% extends "layouts/public_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Response Submitted" %}{% endblock %}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "template.html" %}
|
||||
{% extends "layouts/public_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Invalid Link" %}{% endblock %}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user