""" QI Projects Forms Forms for creating and managing Quality Improvement projects and tasks. """ from django import forms from django.forms import inlineformset_factory from django.utils.translation import gettext_lazy as _ from apps.core.form_mixins import HospitalFieldMixin from apps.accounts.models import User from apps.organizations.models import Department, Hospital from .models import QIProject, QIProjectTask class QIProjectForm(HospitalFieldMixin, forms.ModelForm): """ Form for creating and editing QI Projects. Hospital field visibility: - PX Admins: See dropdown with all hospitals - Others: Hidden field, auto-set to user's hospital """ class Meta: model = QIProject fields = [ "name", "name_ar", "description", "hospital", "department", "project_lead", "team_members", "status", "start_date", "target_completion_date", "outcome_description", ] widgets = { "name": forms.TextInput( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm", "placeholder": _("Project name"), } ), "name_ar": forms.TextInput( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm", "placeholder": _("اسم المشروع"), "dir": "rtl", } ), "description": forms.Textarea( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm resize-none", "rows": 4, "placeholder": _("Describe the project objectives and scope..."), } ), "hospital": forms.Select( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white" } ), "department": forms.Select( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white" } ), "project_lead": forms.Select( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white" } ), "team_members": forms.SelectMultiple( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white h-40" } ), "status": forms.Select( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white" } ), "start_date": forms.DateInput( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm", "type": "date", } ), "target_completion_date": forms.DateInput( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm", "type": "date", } ), "outcome_description": forms.Textarea( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm resize-none", "rows": 3, "placeholder": _("Document project outcomes and results..."), } ), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Filter department choices based on hospital hospital_id = None if self.data.get("hospital"): hospital_id = self.data.get("hospital") elif self.initial.get("hospital"): hospital_id = self.initial.get("hospital") elif self.instance and self.instance.pk and self.instance.hospital: hospital_id = self.instance.hospital.id elif self.user and self.user.is_px_admin(): tenant_hospital = getattr(self.request, "tenant_hospital", None) if tenant_hospital: hospital_id = tenant_hospital.id elif self.user and self.user.hospital: hospital_id = self.user.hospital.id if hospital_id: self.fields["department"].queryset = Department.objects.filter( hospital_id=hospital_id, status="active" ).order_by("name") # Filter user choices based on hospital self.fields["project_lead"].queryset = User.objects.filter( hospital_id=hospital_id, is_active=True ).order_by("first_name", "last_name") self.fields["team_members"].queryset = User.objects.filter( hospital_id=hospital_id, is_active=True ).order_by("first_name", "last_name") else: self.fields["department"].queryset = Department.objects.none() self.fields["project_lead"].queryset = User.objects.none() self.fields["team_members"].queryset = User.objects.none() class QIProjectTaskForm(forms.ModelForm): """ Form for creating and editing QI Project tasks. """ class Meta: model = QIProjectTask fields = ["title", "description", "assigned_to", "status", "due_date", "order"] widgets = { "title": forms.TextInput( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm", "placeholder": _("Task title"), } ), "description": forms.Textarea( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm resize-none", "rows": 3, "placeholder": _("Task description..."), } ), "assigned_to": forms.Select( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white" } ), "status": forms.Select( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white" } ), "due_date": forms.DateInput( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm", "type": "date", } ), "order": forms.NumberInput( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm", "min": 0, } ), } def __init__(self, *args, **kwargs): self.project = kwargs.pop("project", None) super().__init__(*args, **kwargs) # Make order field not required (has default value of 0) self.fields["order"].required = False # Filter assigned_to choices based on project hospital if self.project and self.project.hospital: self.fields["assigned_to"].queryset = User.objects.filter( hospital=self.project.hospital, is_active=True ).order_by("first_name", "last_name") else: self.fields["assigned_to"].queryset = User.objects.none() class QIProjectTemplateForm(HospitalFieldMixin, forms.ModelForm): """ Form for creating and editing QI Project templates. Hospital field visibility: - PX Admins: See dropdown with all hospitals - Others: Hidden field, auto-set to user's hospital Templates can be: - Global (hospital=None) - available to all - Hospital-specific - available only to that hospital """ class Meta: model = QIProject fields = ["name", "name_ar", "description", "hospital", "department", "target_completion_date"] widgets = { "name": forms.TextInput( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm", "placeholder": _("Template name"), } ), "name_ar": forms.TextInput( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm", "placeholder": _("اسم القالب"), "dir": "rtl", } ), "description": forms.Textarea( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm resize-none", "rows": 4, "placeholder": _("Describe the project template..."), } ), "hospital": forms.Select( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white" } ), "department": forms.Select( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white" } ), "target_completion_date": forms.DateInput( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm", "type": "date", } ), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Make hospital optional for templates (global templates) self.fields["hospital"].required = False self.fields["hospital"].empty_label = _("Global (All Hospitals)") # Filter department choices based on hospital hospital_id = None if self.data.get("hospital"): hospital_id = self.data.get("hospital") elif self.initial.get("hospital"): hospital_id = self.initial.get("hospital") elif self.instance and self.instance.pk and self.instance.hospital: hospital_id = self.instance.hospital.id elif self.user and self.user.is_px_admin(): tenant_hospital = getattr(self.request, "tenant_hospital", None) if tenant_hospital: hospital_id = tenant_hospital.id elif self.user and self.user.hospital: hospital_id = self.user.hospital.id if hospital_id: self.fields["department"].queryset = Department.objects.filter( hospital_id=hospital_id, status="active" ).order_by("name") else: self.fields["department"].queryset = Department.objects.none() class ConvertToProjectForm(forms.Form): """ Form for converting a PX Action to a QI Project. Allows selecting a template and customizing the project details. """ template = forms.ModelChoiceField( queryset=QIProject.objects.none(), required=False, empty_label=_("Blank Project"), label=_("Project Template"), widget=forms.Select( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white" } ), ) project_name = forms.CharField( max_length=200, label=_("Project Name"), widget=forms.TextInput( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm", "placeholder": _("Enter project name"), } ), ) project_lead = forms.ModelChoiceField( queryset=User.objects.none(), required=True, label=_("Project Lead"), widget=forms.Select( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm bg-white" } ), ) target_completion_date = forms.DateField( required=False, label=_("Target Completion Date"), widget=forms.DateInput( attrs={ "class": "w-full px-4 py-2.5 rounded-xl border border-slate-200 focus:border-navy focus:ring-2 focus:ring-navy/20 transition text-sm", "type": "date", } ), ) def __init__(self, *args, **kwargs): self.request = kwargs.pop("request", None) self.user = self.request.user if self.request else None self.action = kwargs.pop("action", None) super().__init__(*args, **kwargs) if self.user and self.user.hospital: # Filter templates by hospital (or global) from django.db.models import Q self.fields["template"].queryset = QIProject.objects.filter( Q(hospital=self.user.hospital) | Q(hospital__isnull=True), status="template", # We'll add this status or use metadata ).order_by("name") # Filter project lead by hospital self.fields["project_lead"].queryset = User.objects.filter( hospital=self.user.hospital, is_active=True ).order_by("first_name", "last_name") else: self.fields["template"].queryset = QIProject.objects.none() self.fields["project_lead"].queryset = User.objects.none() # Inline formset for task templates (used with QIProject templates) class TaskTemplateForm(forms.ModelForm): """Simplified form for task templates (no project field needed)""" class Meta: model = QIProjectTask fields = ["title", "description"] widgets = { "title": forms.TextInput( attrs={ "class": "form-control", "placeholder": _("Task title"), } ), "description": forms.TextInput( attrs={ "class": "form-control", "placeholder": _("Description"), } ), } TaskTemplateFormSet = inlineformset_factory( QIProject, QIProjectTask, form=TaskTemplateForm, fields=["title", "description"], extra=1, can_delete=True, )