HH/apps/surveys/forms.py
2026-03-15 23:48:45 +03:00

332 lines
12 KiB
Python

"""
Survey forms for CRUD operations
"""
from django import forms
from django.utils.translation import gettext_lazy as _
from apps.organizations.models import Patient, Staff, Hospital
from apps.core.form_mixins import HospitalFieldMixin
from .models import SurveyInstance, SurveyTemplate, SurveyQuestion
class SurveyTemplateForm(HospitalFieldMixin, forms.ModelForm):
"""
Form for creating/editing survey templates.
Hospital field visibility:
- PX Admins: See dropdown with all hospitals
- Others: Hidden field, auto-set to user's hospital
"""
class Meta:
model = SurveyTemplate
fields = ["name", "name_ar", "hospital", "survey_type", "scoring_method", "negative_threshold", "is_active"]
widgets = {
"name": forms.TextInput(attrs={"class": "form-control", "placeholder": "e.g., MD Consultation Feedback"}),
"name_ar": forms.TextInput(attrs={"class": "form-control", "placeholder": "الاسم بالعربية"}),
"hospital": forms.Select(attrs={"class": "form-select"}),
"survey_type": forms.Select(attrs={"class": "form-select"}),
"scoring_method": forms.Select(attrs={"class": "form-select"}),
"negative_threshold": forms.NumberInput(
attrs={"class": "form-control", "step": "0.1", "min": "1", "max": "5"}
),
"is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}),
}
class SurveyQuestionForm(forms.ModelForm):
"""Form for creating/editing survey questions"""
class Meta:
model = SurveyQuestion
fields = ["text", "text_ar", "question_type", "order", "is_required", "choices_json"]
widgets = {
"text": forms.Textarea(
attrs={"class": "form-control", "rows": 2, "placeholder": "Enter question in English"}
),
"text_ar": forms.Textarea(
attrs={"class": "form-control", "rows": 2, "placeholder": "أدخل السؤال بالعربية"}
),
"question_type": forms.Select(attrs={"class": "form-select"}),
"order": forms.NumberInput(attrs={"class": "form-control", "min": "0"}),
"is_required": forms.CheckboxInput(attrs={"class": "form-check-input"}),
"choices_json": forms.Textarea(
attrs={
"class": "form-control",
"rows": 5,
"placeholder": '[{"value": "1", "label": "Option 1", "label_ar": "خيار 1"}]',
}
),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["choices_json"].required = False
self.fields["choices_json"].help_text = _(
"JSON array of choices for multiple choice questions. "
'Format: [{"value": "1", "label": "Option 1", "label_ar": "خيار 1"}]'
)
SurveyQuestionFormSet = forms.inlineformset_factory(
SurveyTemplate, SurveyQuestion, form=SurveyQuestionForm, extra=1, can_delete=True, min_num=1, validate_min=True
)
class ManualSurveySendForm(forms.Form):
"""Form for manually sending surveys to patients or staff"""
RECIPIENT_TYPE_CHOICES = [
("patient", _("Patient")),
("staff", _("Staff")),
]
DELIVERY_CHANNEL_CHOICES = [
("email", _("Email")),
("sms", _("SMS")),
]
def __init__(self, request=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
self.user = request.user if request else None
# Determine hospital context
hospital = None
if self.user and self.user.is_px_admin():
hospital = getattr(request, "tenant_hospital", None)
elif self.user and self.user.hospital:
hospital = self.user.hospital
# Filter survey templates by hospital
if hospital:
self.fields["survey_template"].queryset = SurveyTemplate.objects.filter(hospital=hospital, is_active=True)
survey_template = forms.ModelChoiceField(
queryset=SurveyTemplate.objects.filter(is_active=True),
label=_("Survey Template"),
widget=forms.Select(attrs={"class": "form-select", "data-placeholder": _("Select a survey template")}),
)
recipient_type = forms.ChoiceField(
choices=RECIPIENT_TYPE_CHOICES,
label=_("Recipient Type"),
widget=forms.RadioSelect(attrs={"class": "form-check-input"}),
)
recipient = forms.CharField(
label=_("Recipient"),
widget=forms.TextInput(
attrs={
"class": "form-control",
"placeholder": _("Search by name or ID..."),
"data-search-url": "/api/recipients/search/",
}
),
help_text=_("Start typing to search for patient or staff"),
)
delivery_channel = forms.ChoiceField(
choices=DELIVERY_CHANNEL_CHOICES,
label=_("Delivery Channel"),
widget=forms.Select(attrs={"class": "form-select"}),
)
custom_message = forms.CharField(
label=_("Custom Message (Optional)"),
required=False,
widget=forms.Textarea(
attrs={
"class": "form-control",
"rows": 3,
"placeholder": _("Add a custom message to the survey invitation..."),
}
),
)
class ManualPhoneSurveySendForm(forms.Form):
"""Form for sending surveys to a manually entered phone number"""
def __init__(self, request=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
self.user = request.user if request else None
# Determine hospital context
hospital = None
if self.user and self.user.is_px_admin():
hospital = getattr(request, "tenant_hospital", None)
elif self.user and self.user.hospital:
hospital = self.user.hospital
# Filter survey templates by hospital
if hospital:
self.fields["survey_template"].queryset = SurveyTemplate.objects.filter(hospital=hospital, is_active=True)
survey_template = forms.ModelChoiceField(
queryset=SurveyTemplate.objects.filter(is_active=True),
label=_("Survey Template"),
widget=forms.Select(attrs={"class": "form-select"}),
)
phone_number = forms.CharField(
label=_("Phone Number"),
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("+966501234567")}),
help_text=_("Enter phone number with country code (e.g., +966...)"),
)
recipient_name = forms.CharField(
label=_("Recipient Name (Optional)"),
required=False,
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Patient Name")}),
)
custom_message = forms.CharField(
label=_("Custom Message (Optional)"),
required=False,
widget=forms.Textarea(
attrs={
"class": "form-control",
"rows": 3,
"placeholder": _("Add a custom message to the survey invitation..."),
}
),
)
def clean_phone_number(self):
phone = self.cleaned_data["phone_number"].strip()
# Remove spaces, dashes, parentheses
phone = phone.replace(" ", "").replace("-", "").replace("(", "").replace(")", "")
if not phone.startswith("+"):
raise forms.ValidationError(_("Phone number must start with country code (e.g., +966)"))
return phone
class BulkCSVSurveySendForm(forms.Form):
"""Form for bulk sending surveys via CSV upload"""
survey_template = forms.ModelChoiceField(
queryset=SurveyTemplate.objects.filter(is_active=True),
label=_("Survey Template"),
widget=forms.Select(attrs={"class": "form-select"}),
)
csv_file = forms.FileField(
label=_("CSV File"),
widget=forms.FileInput(attrs={"class": "form-control", "accept": ".csv"}),
help_text=_("Upload CSV with phone numbers. Format: phone_number,name(optional)"),
)
custom_message = forms.CharField(
label=_("Custom Message (Optional)"),
required=False,
widget=forms.Textarea(
attrs={
"class": "form-control",
"rows": 3,
"placeholder": _("Add a custom message to the survey invitation..."),
}
),
)
def __init__(self, request=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
self.user = request.user if request else None
# Determine hospital context
hospital = None
if self.user and self.user.is_px_admin():
hospital = getattr(request, "tenant_hospital", None)
elif self.user and self.user.hospital:
hospital = self.user.hospital
# Filter survey templates by hospital
if hospital:
self.fields["survey_template"].queryset = SurveyTemplate.objects.filter(hospital=hospital, is_active=True)
class HISPatientImportForm(HospitalFieldMixin, forms.Form):
"""
Form for importing patient data from HIS/MOH Statistics CSV.
Hospital field visibility:
- PX Admins: See dropdown with all hospitals
- Others: Hidden field, auto-set to user's hospital
"""
hospital = forms.ModelChoiceField(
queryset=Hospital.objects.filter(status="active"),
label=_("Hospital"),
widget=forms.Select(attrs={"class": "form-select"}),
help_text=_("Select the hospital for these patient records"),
)
csv_file = forms.FileField(
label=_("HIS Statistics CSV File"),
widget=forms.FileInput(attrs={"class": "form-control", "accept": ".csv"}),
help_text=_("Upload MOH Statistics CSV with patient visit data"),
)
skip_header_rows = forms.IntegerField(
label=_("Skip Header Rows"),
initial=5,
min_value=0,
max_value=10,
widget=forms.NumberInput(attrs={"class": "form-control"}),
help_text=_("Number of metadata/header rows to skip before data rows"),
)
class HISSurveySendForm(forms.Form):
"""Form for sending surveys to imported HIS patients"""
survey_template = forms.ModelChoiceField(
queryset=SurveyTemplate.objects.filter(is_active=True),
label=_("Survey Template"),
widget=forms.Select(attrs={"class": "form-select"}),
)
delivery_channel = forms.ChoiceField(
choices=[
("sms", _("SMS")),
("email", _("Email")),
("both", _("Both SMS and Email")),
],
label=_("Delivery Channel"),
initial="sms",
widget=forms.Select(attrs={"class": "form-select"}),
)
custom_message = forms.CharField(
label=_("Custom Message (Optional)"),
required=False,
widget=forms.Textarea(
attrs={
"class": "form-control",
"rows": 3,
"placeholder": _("Add a custom message to the survey invitation..."),
}
),
)
patient_ids = forms.CharField(widget=forms.HiddenInput(), required=True)
def __init__(self, request=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.request = request
self.user = request.user if request else None
# Determine hospital context
hospital = None
if self.user and self.user.is_px_admin():
hospital = getattr(request, "tenant_hospital", None)
elif self.user and self.user.hospital:
hospital = self.user.hospital
# Filter survey templates by hospital
if hospital:
self.fields["survey_template"].queryset = SurveyTemplate.objects.filter(hospital=hospital, is_active=True)