diff --git a/apps/feedback/test_workflow_realignment.py b/apps/feedback/test_workflow_realignment.py
new file mode 100644
index 0000000..309a179
--- /dev/null
+++ b/apps/feedback/test_workflow_realignment.py
@@ -0,0 +1,91 @@
+"""Tests for the suggestion workflow realignment (optional implemented-notice SMS)."""
+from unittest.mock import patch
+from django.contrib.auth.models import Group
+
+import pytest
+from django.test import TestCase
+from django.urls import reverse
+
+from apps.accounts.models import User
+from apps.organizations.models import Hospital
+from apps.feedback.models import Feedback, FeedbackStatus, FeedbackType
+
+
+def _ensure_group(name):
+ """Get or create a role group (pytest runs with --nomigrations)."""
+ grp, _ = Group.objects.get_or_create(name=name)
+ return grp
+
+
+@pytest.mark.django_db
+class TestSuggestionImplementedNotice(TestCase):
+ """Closing a suggestion with implemented_notice=True sends the high-value SMS.
+
+ Uses self.client because feedback_change_status calls messages.success/error
+ (needs MessageMiddleware) and redirects (302) — no template rendering.
+ """
+
+ def setUp(self):
+ self.hospital = Hospital.objects.create(name="Test Hospital", code="TH09")
+ # Use PX Employee (not PX Admin) so HospitalSelectionMiddleware uses
+ # user.hospital directly instead of requiring session['selected_hospital_id'].
+ _ensure_group("PX Employee")
+ self.user = User.objects.create_user(
+ username="pxsug", email="pxsug@test", password="x", hospital=self.hospital
+ )
+ self.user.groups.add(Group.objects.get(name="PX Employee"))
+ self.client.force_login(self.user)
+
+ self.feedback = Feedback.objects.create(
+ hospital=self.hospital,
+ feedback_type=FeedbackType.SUGGESTION,
+ status=FeedbackStatus.REVIEWED,
+ contact_phone="+966500000000",
+ contact_name="Patient",
+ message="Add online check-in",
+ )
+
+ @patch("apps.feedback.views.NotificationService.send_sms")
+ def test_close_with_implemented_notice_sends_implemented_sms(self, mock_sms):
+ """When implemented_notice=on and status=closed, SMS body mentions 'implemented'."""
+ response = self.client.post(
+ reverse("feedback:feedback_change_status", kwargs={"pk": self.feedback.pk}),
+ data={"status": "closed", "implemented_notice": "on"},
+ )
+ assert response.status_code == 302, f"expected redirect, got {response.status_code}"
+ self.feedback.refresh_from_db()
+ assert self.feedback.status == FeedbackStatus.CLOSED
+ assert mock_sms.called, "send_sms should have been called"
+ last_call_body = mock_sms.call_args[0][1]
+ assert "implemented" in last_call_body.lower(), \
+ f"Expected 'implemented' in SMS body, got: {last_call_body}"
+
+ @patch("apps.feedback.views.NotificationService.send_sms")
+ def test_close_without_implemented_notice_sends_generic_sms(self, mock_sms):
+ """Without the flag, the generic closed message fires (no 'implemented')."""
+ response = self.client.post(
+ reverse("feedback:feedback_change_status", kwargs={"pk": self.feedback.pk}),
+ data={"status": "closed"},
+ )
+ assert response.status_code == 302
+ self.feedback.refresh_from_db()
+ assert self.feedback.status == FeedbackStatus.CLOSED
+ assert mock_sms.called
+ last_call_body = mock_sms.call_args[0][1]
+ assert "implemented" not in last_call_body.lower(), \
+ f"Did not expect 'implemented' in generic SMS, got: {last_call_body}"
+
+ @patch("apps.feedback.views.NotificationService.send_sms")
+ def test_implemented_notice_ignored_when_status_not_closed(self, mock_sms):
+ """The flag has no effect when transitioning to a non-closed status (e.g. acknowledged)."""
+ response = self.client.post(
+ reverse("feedback:feedback_change_status", kwargs={"pk": self.feedback.pk}),
+ data={"status": "acknowledged", "implemented_notice": "on"},
+ )
+ assert response.status_code == 302
+ self.feedback.refresh_from_db()
+ assert self.feedback.status == FeedbackStatus.ACKNOWLEDGED
+ # SMS fires for acknowledged too (existing behavior), but must NOT say 'implemented'.
+ if mock_sms.called:
+ last_call_body = mock_sms.call_args[0][1]
+ assert "implemented" not in last_call_body.lower()
diff --git a/apps/feedback/views.py b/apps/feedback/views.py
index 62a7ed7..930e261 100644
--- a/apps/feedback/views.py
+++ b/apps/feedback/views.py
@@ -913,13 +913,30 @@ def feedback_change_status(request, pk):
# Notify the suggester on acknowledge/close (closed-loop communication).
# Only for suggestions with a patient-facing contact channel; internal /
# source-user submissions have no patient to notify.
+ implemented_notice = (
+ request.POST.get("implemented_notice") in ("on", "true", "True", "1")
+ and new_status == FeedbackStatus.CLOSED
+ )
if new_status in (FeedbackStatus.ACKNOWLEDGED, FeedbackStatus.CLOSED) and feedback.contact_phone:
try:
- label = "acknowledged" if new_status == FeedbackStatus.ACKNOWLEDGED else "closed"
+ if implemented_notice:
+ body = (
+ f"Good news — your suggestion (ref {feedback.reference_number}) "
+ f"has been implemented. Thank you for helping us improve."
+ )
+ elif new_status == FeedbackStatus.ACKNOWLEDGED:
+ body = (
+ f"Your suggestion (ref {feedback.reference_number}) has been acknowledged. "
+ f"Thank you for helping us improve."
+ )
+ else: # CLOSED without implemented flag
+ body = (
+ f"Your suggestion (ref {feedback.reference_number}) has been closed. "
+ f"Thank you for helping us improve."
+ )
NotificationService.send_sms(
feedback.contact_phone,
- f"Your suggestion (ref {feedback.reference_number}) has been {label}. "
- f"Thank you for helping us improve.",
+ body,
related_object=feedback,
)
except Exception:
diff --git a/templates/feedback/feedback_detail.html b/templates/feedback/feedback_detail.html
index f6dd2c5..173d5b1 100644
--- a/templates/feedback/feedback_detail.html
+++ b/templates/feedback/feedback_detail.html
@@ -352,6 +352,10 @@
{% endfor %}
+