299 lines
13 KiB
Python
299 lines
13 KiB
Python
"""
|
|
QI Projects Forms
|
|
|
|
Forms for creating and managing Quality Improvement projects and tasks.
|
|
"""
|
|
from django import forms
|
|
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.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
|
|
|
|
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.user = kwargs.pop('user', 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()
|