332 lines
12 KiB
Python
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)
|