PHASE 1: Add HospitalFieldMixin to forms without it - apps/complaints/forms.py: SLAConfigForm, EscalationRuleForm - apps/feedback/forms.py: FeedbackForm - apps/rca/forms.py: RootCauseAnalysisForm PHASE 2: Update templates to conditionally hide hospital field labels - templates/complaints/complaint_form.html - templates/complaints/inquiry_form.html - templates/complaints/complaint_threshold_form.html - templates/complaints/escalation_rule_form.html - templates/feedback/feedback_form.html PHASE 3: Remove redundant manual hospital filtering code - Removed manual __init__ hospital logic from forms now using mixin Behavior: - PX Admin: Sees hospital dropdown (can select any hospital) - Hospital Admin/Staff: Hospital field hidden, auto-set to their hospital - Cleaner code: Mixin handles all role-based filtering automatically
253 lines
9.2 KiB
Python
253 lines
9.2 KiB
Python
"""
|
|
Feedback forms - Forms for feedback management
|
|
"""
|
|
|
|
from django import forms
|
|
|
|
from apps.organizations.models import Department, Hospital, Patient, Staff
|
|
from apps.core.form_mixins import HospitalFieldMixin
|
|
from .models import Feedback, FeedbackResponse, FeedbackStatus, FeedbackType, FeedbackCategory
|
|
|
|
|
|
class FeedbackForm(HospitalFieldMixin, forms.ModelForm):
|
|
"""Form for creating and editing feedback"""
|
|
|
|
class Meta:
|
|
model = Feedback
|
|
fields = [
|
|
"patient",
|
|
"is_anonymous",
|
|
"contact_name",
|
|
"contact_email",
|
|
"contact_phone",
|
|
"hospital",
|
|
"department",
|
|
"staff",
|
|
"feedback_type",
|
|
"title",
|
|
"message",
|
|
"category",
|
|
"subcategory",
|
|
"rating",
|
|
"priority",
|
|
"encounter_id",
|
|
]
|
|
widgets = {
|
|
"patient": forms.Select(attrs={"class": "form-select", "id": "id_patient"}),
|
|
"is_anonymous": forms.CheckboxInput(attrs={"class": "form-check-input", "id": "id_is_anonymous"}),
|
|
"contact_name": forms.TextInput(attrs={"class": "form-control", "placeholder": "Enter contact name"}),
|
|
"contact_email": forms.EmailInput(attrs={"class": "form-control", "placeholder": "Enter email address"}),
|
|
"contact_phone": forms.TextInput(attrs={"class": "form-control", "placeholder": "Enter phone number"}),
|
|
"hospital": forms.Select(attrs={"class": "form-select", "required": True}),
|
|
"department": forms.Select(attrs={"class": "form-select"}),
|
|
"staff": forms.Select(attrs={"class": "form-select"}),
|
|
"feedback_type": forms.Select(attrs={"class": "form-select", "required": True}),
|
|
"title": forms.TextInput(
|
|
attrs={"class": "form-control", "placeholder": "Enter feedback title", "required": True}
|
|
),
|
|
"message": forms.Textarea(
|
|
attrs={
|
|
"class": "form-control",
|
|
"rows": 5,
|
|
"placeholder": "Enter your feedback message...",
|
|
"required": True,
|
|
}
|
|
),
|
|
"category": forms.Select(attrs={"class": "form-select", "required": True}),
|
|
"subcategory": forms.TextInput(
|
|
attrs={"class": "form-control", "placeholder": "Enter subcategory (optional)"}
|
|
),
|
|
"rating": forms.NumberInput(
|
|
attrs={"class": "form-control", "min": 1, "max": 5, "placeholder": "Rate from 1 to 5"}
|
|
),
|
|
"priority": forms.Select(attrs={"class": "form-select"}),
|
|
"encounter_id": forms.TextInput(
|
|
attrs={"class": "form-control", "placeholder": "Enter encounter ID (optional)"}
|
|
),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
user = kwargs.pop("user", None)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Filter hospitals based on user permissions
|
|
if user:
|
|
if not user.is_px_admin() and user.hospital:
|
|
self.fields["hospital"].queryset = Hospital.objects.filter(id=user.hospital.id, status="active")
|
|
else:
|
|
self.fields["hospital"].queryset = Hospital.objects.filter(status="active")
|
|
|
|
# Set initial hospital if user has one
|
|
if user and user.hospital and not self.instance.pk:
|
|
self.fields["hospital"].initial = user.hospital
|
|
|
|
# Filter departments and physicians based on selected hospital
|
|
if self.instance.pk and hasattr(self.instance, "hospital") and self.instance.hospital_id:
|
|
self.fields["department"].queryset = Department.objects.filter(
|
|
hospital=self.instance.hospital, status="active"
|
|
)
|
|
self.fields["staff"].queryset = Staff.objects.filter(hospital=self.instance.hospital, status="active")
|
|
else:
|
|
self.fields["department"].queryset = Department.objects.none()
|
|
self.fields["staff"].queryset = Staff.objects.none()
|
|
|
|
# Make patient optional if anonymous
|
|
if self.data.get("is_anonymous"):
|
|
self.fields["patient"].required = False
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
is_anonymous = cleaned_data.get("is_anonymous")
|
|
patient = cleaned_data.get("patient")
|
|
contact_name = cleaned_data.get("contact_name")
|
|
|
|
# Validate anonymous feedback
|
|
if is_anonymous:
|
|
if not contact_name:
|
|
raise forms.ValidationError("Contact name is required for anonymous feedback.")
|
|
else:
|
|
if not patient:
|
|
raise forms.ValidationError("Please select a patient or mark as anonymous.")
|
|
|
|
# Validate rating
|
|
rating = cleaned_data.get("rating")
|
|
if rating is not None and (rating < 1 or rating > 5):
|
|
raise forms.ValidationError("Rating must be between 1 and 5.")
|
|
|
|
return cleaned_data
|
|
|
|
|
|
class FeedbackResponseForm(forms.ModelForm):
|
|
"""Form for adding responses to feedback"""
|
|
|
|
class Meta:
|
|
model = FeedbackResponse
|
|
fields = ["response_type", "message", "is_internal"]
|
|
widgets = {
|
|
"response_type": forms.Select(attrs={"class": "form-select", "required": True}),
|
|
"message": forms.Textarea(
|
|
attrs={"class": "form-control", "rows": 4, "placeholder": "Enter your response...", "required": True}
|
|
),
|
|
"is_internal": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
|
}
|
|
|
|
|
|
class FeedbackFilterForm(forms.Form):
|
|
"""Form for filtering feedback list"""
|
|
|
|
search = forms.CharField(
|
|
required=False,
|
|
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "Search by title, message, patient..."}),
|
|
)
|
|
|
|
feedback_type = forms.ChoiceField(
|
|
required=False,
|
|
choices=[("", "All Types")] + list(FeedbackType.choices),
|
|
widget=forms.Select(attrs={"class": "form-select"}),
|
|
)
|
|
|
|
status = forms.ChoiceField(
|
|
required=False,
|
|
choices=[("", "All Statuses")] + list(FeedbackStatus.choices),
|
|
widget=forms.Select(attrs={"class": "form-select"}),
|
|
)
|
|
|
|
category = forms.ChoiceField(
|
|
required=False,
|
|
choices=[("", "All Categories")] + list(FeedbackCategory.choices),
|
|
widget=forms.Select(attrs={"class": "form-select"}),
|
|
)
|
|
|
|
sentiment = forms.ChoiceField(
|
|
required=False,
|
|
choices=[
|
|
("", "All Sentiments"),
|
|
("positive", "Positive"),
|
|
("neutral", "Neutral"),
|
|
("negative", "Negative"),
|
|
],
|
|
widget=forms.Select(attrs={"class": "form-select"}),
|
|
)
|
|
|
|
priority = forms.ChoiceField(
|
|
required=False,
|
|
choices=[
|
|
("", "All Priorities"),
|
|
("low", "Low"),
|
|
("medium", "Medium"),
|
|
("high", "High"),
|
|
("urgent", "Urgent"),
|
|
],
|
|
widget=forms.Select(attrs={"class": "form-select"}),
|
|
)
|
|
|
|
hospital = forms.ModelChoiceField(
|
|
required=False,
|
|
queryset=Hospital.objects.filter(status="active"),
|
|
widget=forms.Select(attrs={"class": "form-select"}),
|
|
empty_label="All Hospitals",
|
|
)
|
|
|
|
department = forms.ModelChoiceField(
|
|
required=False,
|
|
queryset=Department.objects.filter(status="active"),
|
|
widget=forms.Select(attrs={"class": "form-select"}),
|
|
empty_label="All Departments",
|
|
)
|
|
|
|
rating_min = forms.IntegerField(
|
|
required=False,
|
|
min_value=1,
|
|
max_value=5,
|
|
widget=forms.NumberInput(attrs={"class": "form-control", "placeholder": "Min rating"}),
|
|
)
|
|
|
|
rating_max = forms.IntegerField(
|
|
required=False,
|
|
min_value=1,
|
|
max_value=5,
|
|
widget=forms.NumberInput(attrs={"class": "form-control", "placeholder": "Max rating"}),
|
|
)
|
|
|
|
date_from = forms.DateField(required=False, widget=forms.DateInput(attrs={"class": "form-control", "type": "date"}))
|
|
|
|
date_to = forms.DateField(required=False, widget=forms.DateInput(attrs={"class": "form-control", "type": "date"}))
|
|
|
|
is_featured = forms.BooleanField(required=False, widget=forms.CheckboxInput(attrs={"class": "form-check-input"}))
|
|
|
|
requires_follow_up = forms.BooleanField(
|
|
required=False, widget=forms.CheckboxInput(attrs={"class": "form-check-input"})
|
|
)
|
|
|
|
|
|
class FeedbackStatusChangeForm(forms.Form):
|
|
"""Form for changing feedback status"""
|
|
|
|
status = forms.ChoiceField(
|
|
choices=FeedbackStatus.choices, widget=forms.Select(attrs={"class": "form-select", "required": True})
|
|
)
|
|
|
|
note = forms.CharField(
|
|
required=False,
|
|
widget=forms.Textarea(
|
|
attrs={
|
|
"class": "form-control",
|
|
"rows": 3,
|
|
"placeholder": "Add a note about this status change (optional)...",
|
|
}
|
|
),
|
|
)
|
|
|
|
|
|
class FeedbackAssignForm(forms.Form):
|
|
"""Form for assigning feedback to a user"""
|
|
|
|
user_id = forms.UUIDField(widget=forms.Select(attrs={"class": "form-select", "required": True}))
|
|
|
|
note = forms.CharField(
|
|
required=False,
|
|
widget=forms.Textarea(
|
|
attrs={"class": "form-control", "rows": 2, "placeholder": "Add a note about this assignment (optional)..."}
|
|
),
|
|
)
|