update survey-feedback
This commit is contained in:
parent
edfd1cfe2e
commit
ac58f5c82f
201
SURVEY_FEEDBACK_INTEGRATION.md
Normal file
201
SURVEY_FEEDBACK_INTEGRATION.md
Normal file
@ -0,0 +1,201 @@
|
||||
# Survey-Feedback Integration
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the integration between the Survey and Feedback systems, enabling a closed-loop workflow for handling negative survey responses.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Negative Survey Detection
|
||||
When a patient completes a survey with a score below the threshold (default: 3.0/5.0):
|
||||
- Survey is automatically marked as `is_negative=True`
|
||||
- Staff receives notification/alert about the negative feedback
|
||||
- Survey detail page displays a warning banner with follow-up actions
|
||||
|
||||
### 2. Patient Contact
|
||||
Staff member contacts the patient to discuss the negative feedback:
|
||||
- **Action**: Click "Log Patient Contact" button on survey detail page
|
||||
- **Required**: Contact notes documenting the conversation
|
||||
- **Optional**: Mark issue as "Resolved" or "Explained"
|
||||
- **Tracked**: Who contacted, when, and what was discussed
|
||||
|
||||
### 3. Send Satisfaction Feedback
|
||||
After contacting the patient, staff can send a satisfaction check:
|
||||
- **Action**: Click "Send Satisfaction Feedback" button
|
||||
- **Creates**: New Feedback record of type `SATISFACTION_CHECK`
|
||||
- **Links**: Feedback is linked to the original survey via `related_survey` field
|
||||
- **Sends**: Notification to patient with feedback form link (TODO: implement notification)
|
||||
|
||||
### 4. Patient Completes Feedback
|
||||
Patient receives and completes the satisfaction feedback form:
|
||||
- Rates their satisfaction with how concerns were addressed
|
||||
- Provides additional comments if needed
|
||||
- Feedback is tracked in the system
|
||||
|
||||
### 5. Close Loop
|
||||
Staff reviews the satisfaction feedback:
|
||||
- If satisfied → Close both survey and feedback
|
||||
- If still unsatisfied → Escalate or repeat process
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Feedback Model Changes
|
||||
```python
|
||||
# New field in Feedback model
|
||||
related_survey = ForeignKey(
|
||||
'surveys.SurveyInstance',
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='follow_up_feedbacks'
|
||||
)
|
||||
|
||||
# New feedback type
|
||||
FeedbackType.SATISFACTION_CHECK = 'satisfaction_check', 'Satisfaction Check'
|
||||
```
|
||||
|
||||
### SurveyInstance Model Changes
|
||||
```python
|
||||
# Patient contact tracking
|
||||
patient_contacted = BooleanField(default=False)
|
||||
patient_contacted_at = DateTimeField(null=True, blank=True)
|
||||
patient_contacted_by = ForeignKey('accounts.User', ...)
|
||||
contact_notes = TextField(blank=True)
|
||||
issue_resolved = BooleanField(default=False)
|
||||
|
||||
# Satisfaction feedback tracking
|
||||
satisfaction_feedback_sent = BooleanField(default=False)
|
||||
satisfaction_feedback_sent_at = DateTimeField(null=True, blank=True)
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Survey Actions
|
||||
- `POST /surveys/instances/<uuid>/log-contact/` - Log patient contact
|
||||
- `POST /surveys/instances/<uuid>/send-satisfaction/` - Send satisfaction feedback
|
||||
|
||||
### Views
|
||||
- `survey_log_patient_contact(request, pk)` - Handle patient contact logging
|
||||
- `survey_send_satisfaction_feedback(request, pk)` - Trigger satisfaction feedback
|
||||
|
||||
## Background Tasks
|
||||
|
||||
### `send_satisfaction_feedback`
|
||||
```python
|
||||
@shared_task(bind=True, max_retries=3)
|
||||
def send_satisfaction_feedback(self, survey_instance_id, user_id=None):
|
||||
"""
|
||||
Creates and sends satisfaction feedback form to patient.
|
||||
|
||||
- Validates patient was contacted
|
||||
- Creates Feedback record linked to survey
|
||||
- Sends notification to patient
|
||||
- Updates survey tracking fields
|
||||
"""
|
||||
```
|
||||
|
||||
## UI Components
|
||||
|
||||
### Survey Detail Page
|
||||
For negative surveys, displays:
|
||||
1. **Warning Alert**: "Action Required: Contact patient to discuss negative feedback"
|
||||
2. **Contact Form**:
|
||||
- Contact notes textarea
|
||||
- Issue resolved checkbox
|
||||
- Submit button
|
||||
3. **Contact Summary** (after logging):
|
||||
- Who contacted and when
|
||||
- Contact notes
|
||||
- Resolution status
|
||||
4. **Send Satisfaction Button** (after contact):
|
||||
- Disabled until patient contacted
|
||||
- Triggers satisfaction feedback creation
|
||||
|
||||
### Feedback Detail Page
|
||||
For satisfaction check feedbacks, displays:
|
||||
1. **Related Survey Card**:
|
||||
- Original survey name and score
|
||||
- Survey completion date
|
||||
- Patient contact information
|
||||
- Link to view original survey
|
||||
|
||||
## Permissions
|
||||
|
||||
- **Log Patient Contact**: Hospital Admin or PX Admin
|
||||
- **Send Satisfaction Feedback**: Hospital Admin or PX Admin
|
||||
- **View Related Survey**: Same as survey permissions
|
||||
|
||||
## Audit Trail
|
||||
|
||||
All actions are logged:
|
||||
- `survey_patient_contacted` - When patient is contacted
|
||||
- `satisfaction_feedback_sent` - When satisfaction feedback is sent
|
||||
- Standard feedback audit events for the satisfaction check
|
||||
|
||||
## Migration
|
||||
|
||||
Run migrations to apply database changes:
|
||||
```bash
|
||||
python3 manage.py migrate feedback
|
||||
python3 manage.py migrate surveys
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Notifications**: Implement SMS/WhatsApp/Email notifications for satisfaction feedback
|
||||
2. **Automated Reminders**: Send reminders if satisfaction feedback not completed
|
||||
3. **Analytics**: Track satisfaction improvement rates
|
||||
4. **Templates**: Customizable satisfaction feedback templates
|
||||
5. **Multi-language**: Support for Arabic satisfaction feedback forms
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Create survey with negative score
|
||||
- [ ] Verify warning appears on survey detail
|
||||
- [ ] Log patient contact with notes
|
||||
- [ ] Verify contact information is saved
|
||||
- [ ] Send satisfaction feedback
|
||||
- [ ] Verify feedback record is created and linked
|
||||
- [ ] View feedback detail and verify survey link
|
||||
- [ ] View survey detail and verify feedback link
|
||||
- [ ] Test permissions for different user roles
|
||||
- [ ] Verify audit logs are created
|
||||
|
||||
## Related Files
|
||||
|
||||
### Models
|
||||
- `apps/feedback/models.py` - Feedback model with related_survey field
|
||||
- `apps/surveys/models.py` - SurveyInstance model with contact tracking
|
||||
|
||||
### Views
|
||||
- `apps/surveys/ui_views.py` - Survey contact and satisfaction views
|
||||
- `apps/feedback/views.py` - Feedback views (no changes needed)
|
||||
|
||||
### Tasks
|
||||
- `apps/surveys/tasks.py` - send_satisfaction_feedback task
|
||||
|
||||
### Templates
|
||||
- `templates/surveys/instance_detail.html` - Survey detail with follow-up actions
|
||||
- `templates/feedback/feedback_detail.html` - Feedback detail with survey link
|
||||
|
||||
### URLs
|
||||
- `apps/surveys/urls.py` - New URL patterns for contact and satisfaction
|
||||
|
||||
### Migrations
|
||||
- `apps/feedback/migrations/0002_add_survey_linkage.py`
|
||||
- `apps/surveys/migrations/0003_add_survey_linkage.py`
|
||||
|
||||
## Configuration
|
||||
|
||||
### Settings
|
||||
```python
|
||||
# Survey token expiry (default: 30 days)
|
||||
SURVEY_TOKEN_EXPIRY_DAYS = 30
|
||||
|
||||
# Negative survey threshold (default: 3.0 out of 5.0)
|
||||
# Configured per survey template in database
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For questions or issues, contact the development team or refer to the main project documentation.
|
||||
25
apps/feedback/migrations/0002_add_survey_linkage.py
Normal file
25
apps/feedback/migrations/0002_add_survey_linkage.py
Normal file
@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.0.14 on 2025-12-28 16:51
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('feedback', '0001_initial'),
|
||||
('surveys', '0003_add_survey_linkage'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='feedback',
|
||||
name='related_survey',
|
||||
field=models.ForeignKey(blank=True, help_text='Survey that triggered this satisfaction check feedback', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='follow_up_feedbacks', to='surveys.surveyinstance'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='feedback',
|
||||
name='feedback_type',
|
||||
field=models.CharField(choices=[('compliment', 'Compliment'), ('suggestion', 'Suggestion'), ('general', 'General Feedback'), ('inquiry', 'Inquiry'), ('satisfaction_check', 'Satisfaction Check')], db_index=True, default='general', max_length=20),
|
||||
),
|
||||
]
|
||||
@ -20,6 +20,7 @@ class FeedbackType(models.TextChoices):
|
||||
SUGGESTION = 'suggestion', 'Suggestion'
|
||||
GENERAL = 'general', 'General Feedback'
|
||||
INQUIRY = 'inquiry', 'Inquiry'
|
||||
SATISFACTION_CHECK = 'satisfaction_check', 'Satisfaction Check'
|
||||
|
||||
|
||||
class FeedbackStatus(models.TextChoices):
|
||||
@ -84,6 +85,16 @@ class Feedback(UUIDModel, TimeStampedModel):
|
||||
help_text="Related encounter ID if applicable"
|
||||
)
|
||||
|
||||
# Survey linkage (for satisfaction checks after negative surveys)
|
||||
related_survey = models.ForeignKey(
|
||||
'surveys.SurveyInstance',
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='follow_up_feedbacks',
|
||||
help_text="Survey that triggered this satisfaction check feedback"
|
||||
)
|
||||
|
||||
# Organization
|
||||
hospital = models.ForeignKey(
|
||||
'organizations.Hospital',
|
||||
|
||||
51
apps/surveys/migrations/0003_add_survey_linkage.py
Normal file
51
apps/surveys/migrations/0003_add_survey_linkage.py
Normal file
@ -0,0 +1,51 @@
|
||||
# Generated by Django 5.0.14 on 2025-12-28 16:51
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('surveys', '0002_surveyquestion_surveyresponse_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='surveyinstance',
|
||||
name='contact_notes',
|
||||
field=models.TextField(blank=True, help_text='Notes from patient contact'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='surveyinstance',
|
||||
name='issue_resolved',
|
||||
field=models.BooleanField(default=False, help_text='Whether the issue was resolved/explained'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='surveyinstance',
|
||||
name='patient_contacted',
|
||||
field=models.BooleanField(default=False, help_text='Whether patient was contacted about negative survey'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='surveyinstance',
|
||||
name='patient_contacted_at',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='surveyinstance',
|
||||
name='patient_contacted_by',
|
||||
field=models.ForeignKey(blank=True, help_text='User who contacted the patient', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contacted_surveys', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='surveyinstance',
|
||||
name='satisfaction_feedback_sent',
|
||||
field=models.BooleanField(default=False, help_text='Whether satisfaction feedback form was sent'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='surveyinstance',
|
||||
name='satisfaction_feedback_sent_at',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@ -262,6 +262,36 @@ class SurveyInstance(UUIDModel, TimeStampedModel):
|
||||
# Metadata
|
||||
metadata = models.JSONField(default=dict, blank=True)
|
||||
|
||||
# Patient contact tracking (for negative surveys)
|
||||
patient_contacted = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Whether patient was contacted about negative survey"
|
||||
)
|
||||
patient_contacted_at = models.DateTimeField(null=True, blank=True)
|
||||
patient_contacted_by = models.ForeignKey(
|
||||
'accounts.User',
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name='contacted_surveys',
|
||||
help_text="User who contacted the patient"
|
||||
)
|
||||
contact_notes = models.TextField(
|
||||
blank=True,
|
||||
help_text="Notes from patient contact"
|
||||
)
|
||||
issue_resolved = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Whether the issue was resolved/explained"
|
||||
)
|
||||
|
||||
# Satisfaction feedback tracking
|
||||
satisfaction_feedback_sent = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Whether satisfaction feedback form was sent"
|
||||
)
|
||||
satisfaction_feedback_sent_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
|
||||
@ -248,3 +248,115 @@ def process_survey_completion(survey_instance_id):
|
||||
error_msg = f"Error processing survey completion: {str(e)}"
|
||||
logger.error(error_msg, exc_info=True)
|
||||
return {'status': 'error', 'reason': error_msg}
|
||||
|
||||
|
||||
@shared_task(bind=True, max_retries=3)
|
||||
def send_satisfaction_feedback(self, survey_instance_id, user_id=None):
|
||||
"""
|
||||
Send satisfaction feedback form to patient after negative survey contact.
|
||||
|
||||
This creates a feedback form linked to the survey and sends it to the patient
|
||||
to assess their satisfaction with the resolution.
|
||||
|
||||
Args:
|
||||
survey_instance_id: UUID of SurveyInstance
|
||||
user_id: UUID of User who initiated the feedback (optional)
|
||||
|
||||
Returns:
|
||||
dict: Result with feedback_id and delivery status
|
||||
"""
|
||||
from apps.core.services import create_audit_log
|
||||
from apps.feedback.models import Feedback, FeedbackType, FeedbackResponse
|
||||
from apps.notifications.services import NotificationService
|
||||
from apps.surveys.models import SurveyInstance
|
||||
|
||||
try:
|
||||
# Get survey instance
|
||||
survey_instance = SurveyInstance.objects.select_related(
|
||||
'patient', 'survey_template', 'journey_instance__hospital'
|
||||
).get(id=survey_instance_id)
|
||||
|
||||
# Check if feedback already sent
|
||||
if survey_instance.satisfaction_feedback_sent:
|
||||
logger.warning(f"Satisfaction feedback already sent for survey {survey_instance_id}")
|
||||
return {'status': 'skipped', 'reason': 'already_sent'}
|
||||
|
||||
# Check if patient was contacted
|
||||
if not survey_instance.patient_contacted:
|
||||
logger.warning(f"Patient not contacted yet for survey {survey_instance_id}")
|
||||
return {'status': 'skipped', 'reason': 'patient_not_contacted'}
|
||||
|
||||
patient = survey_instance.patient
|
||||
hospital = survey_instance.journey_instance.hospital if survey_instance.journey_instance else patient.primary_hospital
|
||||
|
||||
# Create satisfaction feedback
|
||||
with transaction.atomic():
|
||||
feedback = Feedback.objects.create(
|
||||
patient=patient,
|
||||
hospital=hospital,
|
||||
department=survey_instance.journey_stage_instance.stage_template.department if survey_instance.journey_stage_instance else None,
|
||||
feedback_type=FeedbackType.SATISFACTION_CHECK,
|
||||
title=f"Satisfaction Check - {survey_instance.survey_template.name}",
|
||||
message=f"Please rate your satisfaction with how we addressed your concerns regarding the survey.",
|
||||
category='communication',
|
||||
priority='medium',
|
||||
sentiment='neutral',
|
||||
status='submitted',
|
||||
related_survey=survey_instance,
|
||||
encounter_id=survey_instance.encounter_id,
|
||||
source='system',
|
||||
metadata={
|
||||
'survey_id': str(survey_instance.id),
|
||||
'survey_score': float(survey_instance.total_score) if survey_instance.total_score else None,
|
||||
'auto_generated': True
|
||||
}
|
||||
)
|
||||
|
||||
# Create initial response
|
||||
FeedbackResponse.objects.create(
|
||||
feedback=feedback,
|
||||
response_type='note',
|
||||
message=f"Satisfaction feedback automatically created following negative survey (Score: {survey_instance.total_score})",
|
||||
created_by_id=user_id,
|
||||
is_internal=True
|
||||
)
|
||||
|
||||
# Update survey instance
|
||||
survey_instance.satisfaction_feedback_sent = True
|
||||
survey_instance.satisfaction_feedback_sent_at = timezone.now()
|
||||
survey_instance.save(update_fields=['satisfaction_feedback_sent', 'satisfaction_feedback_sent_at'])
|
||||
|
||||
# Send notification to patient
|
||||
# TODO: Implement feedback form link notification
|
||||
# For now, we'll log it
|
||||
logger.info(f"Satisfaction feedback {feedback.id} created for survey {survey_instance.id}")
|
||||
|
||||
# Log audit event
|
||||
create_audit_log(
|
||||
event_type='satisfaction_feedback_sent',
|
||||
description=f"Satisfaction feedback sent to {patient.get_full_name()} for survey {survey_instance.survey_template.name}",
|
||||
content_object=feedback,
|
||||
metadata={
|
||||
'survey_id': str(survey_instance.id),
|
||||
'survey_score': float(survey_instance.total_score) if survey_instance.total_score else None,
|
||||
'feedback_id': str(feedback.id)
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
'status': 'sent',
|
||||
'feedback_id': str(feedback.id),
|
||||
'survey_id': str(survey_instance.id)
|
||||
}
|
||||
|
||||
except SurveyInstance.DoesNotExist:
|
||||
error_msg = f"Survey instance {survey_instance_id} not found"
|
||||
logger.error(error_msg)
|
||||
return {'status': 'error', 'reason': error_msg}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error sending satisfaction feedback: {str(e)}"
|
||||
logger.error(error_msg, exc_info=True)
|
||||
|
||||
# Retry the task
|
||||
raise self.retry(exc=e, countdown=60 * (self.request.retries + 1))
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
"""
|
||||
Survey Console UI views - Server-rendered templates for survey management
|
||||
"""
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Q, Prefetch
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils import timezone
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
||||
from apps.core.services import AuditService
|
||||
from apps.organizations.models import Department, Hospital
|
||||
|
||||
from .models import SurveyInstance, SurveyTemplate
|
||||
from .tasks import send_satisfaction_feedback
|
||||
|
||||
|
||||
@login_required
|
||||
@ -198,3 +203,117 @@ def survey_template_list(request):
|
||||
}
|
||||
|
||||
return render(request, 'surveys/template_list.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def survey_log_patient_contact(request, pk):
|
||||
"""
|
||||
Log patient contact for negative survey.
|
||||
|
||||
This records that the user contacted the patient to discuss
|
||||
the negative survey feedback.
|
||||
"""
|
||||
survey = get_object_or_404(SurveyInstance, pk=pk)
|
||||
|
||||
# Check permission
|
||||
user = request.user
|
||||
if not user.is_px_admin() and not user.is_hospital_admin():
|
||||
if user.hospital and survey.survey_template.hospital != user.hospital:
|
||||
messages.error(request, "You don't have permission to modify this survey.")
|
||||
return redirect('surveys:instance_detail', pk=pk)
|
||||
|
||||
# Check if survey is negative
|
||||
if not survey.is_negative:
|
||||
messages.warning(request, "This survey is not marked as negative.")
|
||||
return redirect('surveys:instance_detail', pk=pk)
|
||||
|
||||
# Get form data
|
||||
contact_notes = request.POST.get('contact_notes', '')
|
||||
issue_resolved = request.POST.get('issue_resolved') == 'on'
|
||||
|
||||
if not contact_notes:
|
||||
messages.error(request, "Please provide contact notes.")
|
||||
return redirect('surveys:instance_detail', pk=pk)
|
||||
|
||||
try:
|
||||
# Update survey
|
||||
survey.patient_contacted = True
|
||||
survey.patient_contacted_at = timezone.now()
|
||||
survey.patient_contacted_by = user
|
||||
survey.contact_notes = contact_notes
|
||||
survey.issue_resolved = issue_resolved
|
||||
survey.save(update_fields=[
|
||||
'patient_contacted', 'patient_contacted_at',
|
||||
'patient_contacted_by', 'contact_notes', 'issue_resolved'
|
||||
])
|
||||
|
||||
# Log audit
|
||||
AuditService.log_event(
|
||||
event_type='survey_patient_contacted',
|
||||
description=f"Patient contacted for negative survey by {user.get_full_name()}",
|
||||
user=user,
|
||||
content_object=survey,
|
||||
metadata={
|
||||
'contact_notes': contact_notes,
|
||||
'issue_resolved': issue_resolved,
|
||||
'survey_score': float(survey.total_score) if survey.total_score else None
|
||||
}
|
||||
)
|
||||
|
||||
status = "resolved" if issue_resolved else "discussed"
|
||||
messages.success(request, f"Patient contact logged successfully. Issue marked as {status}.")
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Error logging patient contact: {str(e)}")
|
||||
|
||||
return redirect('surveys:instance_detail', pk=pk)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def survey_send_satisfaction_feedback(request, pk):
|
||||
"""
|
||||
Send satisfaction feedback form to patient.
|
||||
|
||||
This creates and sends a feedback form to assess patient satisfaction
|
||||
with how their negative survey concerns were addressed.
|
||||
"""
|
||||
survey = get_object_or_404(SurveyInstance, pk=pk)
|
||||
|
||||
# Check permission
|
||||
user = request.user
|
||||
if not user.is_px_admin() and not user.is_hospital_admin():
|
||||
if user.hospital and survey.survey_template.hospital != user.hospital:
|
||||
messages.error(request, "You don't have permission to modify this survey.")
|
||||
return redirect('surveys:instance_detail', pk=pk)
|
||||
|
||||
# Check if survey is negative
|
||||
if not survey.is_negative:
|
||||
messages.warning(request, "This survey is not marked as negative.")
|
||||
return redirect('surveys:instance_detail', pk=pk)
|
||||
|
||||
# Check if patient was contacted
|
||||
if not survey.patient_contacted:
|
||||
messages.error(request, "Please log patient contact before sending satisfaction feedback.")
|
||||
return redirect('surveys:instance_detail', pk=pk)
|
||||
|
||||
# Check if already sent
|
||||
if survey.satisfaction_feedback_sent:
|
||||
messages.warning(request, "Satisfaction feedback has already been sent for this survey.")
|
||||
return redirect('surveys:instance_detail', pk=pk)
|
||||
|
||||
try:
|
||||
# Trigger async task to send satisfaction feedback
|
||||
send_satisfaction_feedback.delay(str(survey.id), str(user.id))
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
"Satisfaction feedback form is being sent to the patient. "
|
||||
"They will receive a link to provide their feedback."
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Error sending satisfaction feedback: {str(e)}")
|
||||
|
||||
return redirect('surveys:instance_detail', pk=pk)
|
||||
|
||||
@ -27,6 +27,8 @@ urlpatterns = [
|
||||
# UI Views (authenticated)
|
||||
path('instances/', ui_views.survey_instance_list, name='instance_list'),
|
||||
path('instances/<uuid:pk>/', ui_views.survey_instance_detail, name='instance_detail'),
|
||||
path('instances/<uuid:pk>/log-contact/', ui_views.survey_log_patient_contact, name='log_patient_contact'),
|
||||
path('instances/<uuid:pk>/send-satisfaction/', ui_views.survey_send_satisfaction_feedback, name='send_satisfaction_feedback'),
|
||||
path('templates/', ui_views.survey_template_list, name='template_list'),
|
||||
|
||||
# Public API endpoints (no auth required)
|
||||
|
||||
@ -352,6 +352,57 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Related Survey (if satisfaction check) -->
|
||||
{% if feedback.related_survey %}
|
||||
<div class="detail-card border-info">
|
||||
<div class="detail-card-header bg-info text-white">
|
||||
<i class="bi bi-link-45deg me-2"></i>Related Survey
|
||||
</div>
|
||||
<div class="detail-card-body">
|
||||
<div class="alert alert-info mb-3">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
This is a <strong>satisfaction check</strong> feedback following a negative survey response.
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">Survey:</div>
|
||||
<div class="info-value">
|
||||
<strong>{{ feedback.related_survey.survey_template.name }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">Original Score:</div>
|
||||
<div class="info-value">
|
||||
<span class="badge bg-danger">{{ feedback.related_survey.total_score|floatformat:1 }}/5.0</span>
|
||||
<span class="text-muted ms-2">(Negative)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">Survey Date:</div>
|
||||
<div class="info-value">
|
||||
{{ feedback.related_survey.completed_at|date:"M d, Y H:i" }}
|
||||
</div>
|
||||
</div>
|
||||
{% if feedback.related_survey.patient_contacted %}
|
||||
<div class="info-row">
|
||||
<div class="info-label">Patient Contacted:</div>
|
||||
<div class="info-value">
|
||||
<i class="bi bi-check-circle-fill text-success"></i>
|
||||
{{ feedback.related_survey.patient_contacted_at|date:"M d, Y H:i" }}
|
||||
<br>
|
||||
<small class="text-muted">By {{ feedback.related_survey.patient_contacted_by.get_full_name }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="mt-3">
|
||||
<a href="{% url 'surveys:instance_detail' feedback.related_survey.id %}"
|
||||
class="btn btn-info btn-sm w-100">
|
||||
<i class="bi bi-box-arrow-up-right me-1"></i>View Original Survey
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Timeline -->
|
||||
<div class="detail-card">
|
||||
<div class="detail-card-header">Timeline & Responses</div>
|
||||
|
||||
@ -87,7 +87,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-person me-2"></i>Patient Information</h6>
|
||||
</div>
|
||||
@ -102,6 +102,94 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if survey.is_negative %}
|
||||
<div class="card border-warning">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h6 class="mb-0"><i class="bi bi-exclamation-triangle me-2"></i>Follow-up Actions</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if not survey.patient_contacted %}
|
||||
<div class="alert alert-warning mb-3">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<strong>Action Required:</strong> Contact patient to discuss negative feedback.
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url 'surveys:log_patient_contact' survey.id %}">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="contact_notes" class="form-label">Contact Notes *</label>
|
||||
<textarea class="form-control" id="contact_notes" name="contact_notes" rows="4" required
|
||||
placeholder="Document your conversation with the patient..."></textarea>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="issue_resolved" name="issue_resolved">
|
||||
<label class="form-check-label" for="issue_resolved">
|
||||
Issue resolved or explained to patient
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-warning w-100">
|
||||
<i class="bi bi-telephone me-2"></i>Log Patient Contact
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="alert alert-success mb-3">
|
||||
<i class="bi bi-check-circle me-2"></i>
|
||||
<strong>Patient Contacted</strong><br>
|
||||
<small>By {{ survey.patient_contacted_by.get_full_name }} on {{ survey.patient_contacted_at|date:"M d, Y H:i" }}</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<strong>Contact Notes:</strong>
|
||||
<p class="mb-0 mt-2">{{ survey.contact_notes }}</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<strong>Status:</strong><br>
|
||||
{% if survey.issue_resolved %}
|
||||
<span class="badge bg-success">Issue Resolved</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning">Issue Discussed</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not survey.satisfaction_feedback_sent %}
|
||||
<hr>
|
||||
<h6 class="mb-3">Send Satisfaction Feedback</h6>
|
||||
<p class="text-muted small mb-3">
|
||||
Send a feedback form to the patient to assess their satisfaction with how their concerns were addressed.
|
||||
</p>
|
||||
<form method="post" action="{% url 'surveys:send_satisfaction_feedback' survey.id %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="bi bi-send me-2"></i>Send Satisfaction Feedback
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<hr>
|
||||
<div class="alert alert-info mb-0">
|
||||
<i class="bi bi-check-circle me-2"></i>
|
||||
<strong>Satisfaction Feedback Sent</strong><br>
|
||||
<small>{{ survey.satisfaction_feedback_sent_at|date:"M d, Y H:i" }}</small>
|
||||
</div>
|
||||
|
||||
{% if survey.follow_up_feedbacks.exists %}
|
||||
<div class="mt-3">
|
||||
<strong>Related Feedback:</strong>
|
||||
{% for feedback in survey.follow_up_feedbacks.all %}
|
||||
<div class="mt-2">
|
||||
<a href="{% url 'feedback:feedback_detail' feedback.id %}" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-chat-left-text me-1"></i>View Feedback #{{ feedback.id|slice:":8" }}
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user