430 lines
18 KiB
Python
430 lines
18 KiB
Python
from django import forms
|
|
from accounts.models import User
|
|
from django.utils import timezone
|
|
from datetime import timedelta
|
|
from patients.models import PatientProfile
|
|
from hr.models import Department
|
|
from .models import (
|
|
BloodGroup, Donor, BloodComponent, BloodUnit, BloodTest, CrossMatch,
|
|
BloodRequest, BloodIssue, Transfusion, AdverseReaction, InventoryLocation,
|
|
QualityControl
|
|
)
|
|
|
|
|
|
class DonorForm(forms.ModelForm):
|
|
"""Form for donor registration and updates"""
|
|
|
|
class Meta:
|
|
model = Donor
|
|
fields = [
|
|
'donor_id', 'first_name', 'last_name', 'date_of_birth',
|
|
'gender', 'blood_group', 'phone', 'email', 'address',
|
|
'emergency_contact_name', 'emergency_contact_phone',
|
|
'donor_type', 'status', 'weight', 'height', 'notes'
|
|
]
|
|
widgets = {
|
|
'date_of_birth': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
|
'first_name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'last_name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'donor_id': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'gender': forms.Select(attrs={'class': 'form-select'}),
|
|
'blood_group': forms.Select(attrs={'class': 'form-select'}),
|
|
'phone': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'email': forms.EmailInput(attrs={'class': 'form-control'}),
|
|
'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'emergency_contact_name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'emergency_contact_phone': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'donor_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'weight': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1'}),
|
|
'height': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1'}),
|
|
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|
|
|
|
def clean_weight(self):
|
|
weight = self.cleaned_data.get('weight')
|
|
if weight and weight < 45.0:
|
|
raise forms.ValidationError("Minimum weight for donation is 45 kg.")
|
|
return weight
|
|
|
|
def clean_date_of_birth(self):
|
|
dob = self.cleaned_data.get('date_of_birth')
|
|
if dob:
|
|
today = timezone.now().date()
|
|
age = today.year - dob.year - ((today.month, today.day) < (dob.month, dob.day))
|
|
if age < 18:
|
|
raise forms.ValidationError("Donor must be at least 18 years old.")
|
|
if age > 65:
|
|
raise forms.ValidationError("Donor must be under 65 years old.")
|
|
return dob
|
|
|
|
|
|
class BloodUnitForm(forms.ModelForm):
|
|
"""Form for blood unit registration"""
|
|
|
|
class Meta:
|
|
model = BloodUnit
|
|
fields = [
|
|
'unit_number', 'donor', 'component', 'blood_group',
|
|
'collection_date', 'volume_ml', 'location', 'bag_type',
|
|
'anticoagulant', 'collection_site', 'notes'
|
|
]
|
|
widgets = {
|
|
'unit_number': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'donor': forms.Select(attrs={'class': 'form-select'}),
|
|
'component': forms.Select(attrs={'class': 'form-select'}),
|
|
'blood_group': forms.Select(attrs={'class': 'form-select'}),
|
|
'collection_date': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
|
'volume_ml': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
'location': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'bag_type': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'anticoagulant': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'collection_site': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Filter active donors only
|
|
self.fields['donor'].queryset = Donor.objects.filter(status='active')
|
|
self.fields['component'].queryset = BloodComponent.objects.filter(is_active=True)
|
|
|
|
def save(self, commit=True):
|
|
instance = super().save(commit=False)
|
|
if not instance.expiry_date:
|
|
# Calculate expiry date based on component
|
|
component = instance.component
|
|
instance.expiry_date = instance.collection_date + timedelta(days=component.shelf_life_days)
|
|
if commit:
|
|
instance.save()
|
|
return instance
|
|
|
|
|
|
class BloodTestForm(forms.ModelForm):
|
|
"""Form for blood test results"""
|
|
|
|
class Meta:
|
|
model = BloodTest
|
|
fields = [
|
|
'blood_unit', 'test_type', 'result', 'test_date',
|
|
'equipment_used', 'lot_number', 'notes'
|
|
]
|
|
widgets = {
|
|
'blood_unit': forms.Select(attrs={'class': 'form-select'}),
|
|
'test_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'result': forms.Select(attrs={'class': 'form-select'}),
|
|
'test_date': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
|
'equipment_used': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'lot_number': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Filter blood units that are in testing or quarantine
|
|
self.fields['blood_unit'].queryset = BloodUnit.objects.filter(
|
|
status__in=['collected', 'testing', 'quarantine']
|
|
)
|
|
|
|
|
|
class CrossMatchForm(forms.ModelForm):
|
|
"""Form for crossmatch testing"""
|
|
|
|
class Meta:
|
|
model = CrossMatch
|
|
fields = [
|
|
'blood_unit', 'recipient', 'test_type', 'compatibility',
|
|
'test_date', 'temperature', 'incubation_time', 'notes'
|
|
]
|
|
widgets = {
|
|
'blood_unit': forms.Select(attrs={'class': 'form-select'}),
|
|
'recipient': forms.Select(attrs={'class': 'form-select'}),
|
|
'test_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'compatibility': forms.Select(attrs={'class': 'form-select'}),
|
|
'test_date': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
|
'temperature': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'incubation_time': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Filter available blood units
|
|
self.fields['blood_unit'].queryset = BloodUnit.objects.filter(
|
|
status__in=['available', 'quarantine']
|
|
)
|
|
|
|
|
|
class BloodRequestForm(forms.ModelForm):
|
|
"""Form for blood transfusion requests"""
|
|
|
|
class Meta:
|
|
model = BloodRequest
|
|
fields = [
|
|
'patient', 'requesting_department', 'component_requested',
|
|
'units_requested', 'urgency', 'indication', 'special_requirements',
|
|
'patient_blood_group', 'hemoglobin_level', 'platelet_count',
|
|
'required_by'
|
|
]
|
|
widgets = {
|
|
'patient': forms.Select(attrs={'class': 'form-select'}),
|
|
'requesting_department': forms.Select(attrs={'class': 'form-select'}),
|
|
'component_requested': forms.Select(attrs={'class': 'form-select'}),
|
|
'units_requested': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
|
|
'urgency': forms.Select(attrs={'class': 'form-select'}),
|
|
'indication': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'special_requirements': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
|
|
'patient_blood_group': forms.Select(attrs={'class': 'form-select'}),
|
|
'hemoglobin_level': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.1'}),
|
|
'platelet_count': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
'required_by': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields['component_requested'].queryset = BloodComponent.objects.filter(is_active=True)
|
|
|
|
def clean_required_by(self):
|
|
required_by = self.cleaned_data.get('required_by')
|
|
if required_by and required_by <= timezone.now():
|
|
raise forms.ValidationError("Required by date must be in the future.")
|
|
return required_by
|
|
|
|
|
|
class BloodIssueForm(forms.ModelForm):
|
|
"""Form for blood unit issuance"""
|
|
|
|
class Meta:
|
|
model = BloodIssue
|
|
fields = [
|
|
'blood_request', 'blood_unit', 'crossmatch',
|
|
'issued_to', 'expiry_time', 'notes'
|
|
]
|
|
widgets = {
|
|
'blood_request': forms.Select(attrs={'class': 'form-select'}),
|
|
'blood_unit': forms.Select(attrs={'class': 'form-select'}),
|
|
'crossmatch': forms.Select(attrs={'class': 'form-select'}),
|
|
'issued_to': forms.Select(attrs={'class': 'form-select'}),
|
|
'expiry_time': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
|
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Filter pending/processing blood requests
|
|
self.fields['blood_request'].queryset = BloodRequest.objects.filter(
|
|
status__in=['pending', 'processing', 'ready']
|
|
)
|
|
# Filter available blood units
|
|
self.fields['blood_unit'].queryset = BloodUnit.objects.filter(status='available')
|
|
# Filter staff users
|
|
self.fields['issued_to'].queryset = User.objects.filter(is_staff=True)
|
|
|
|
|
|
class TransfusionForm(forms.ModelForm):
|
|
"""Form for transfusion administration"""
|
|
|
|
class Meta:
|
|
model = Transfusion
|
|
fields = [
|
|
'blood_issue', 'start_time', 'end_time', 'status',
|
|
'volume_transfused', 'transfusion_rate', 'witnessed_by',
|
|
'patient_consent', 'consent_date', 'notes'
|
|
]
|
|
widgets = {
|
|
'blood_issue': forms.Select(attrs={'class': 'form-select'}),
|
|
'start_time': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
|
'end_time': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'volume_transfused': forms.NumberInput(attrs={'class': 'form-control'}),
|
|
'transfusion_rate': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'witnessed_by': forms.Select(attrs={'class': 'form-select'}),
|
|
'patient_consent': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'consent_date': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
|
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Filter issued blood units
|
|
self.fields['blood_issue'].queryset = BloodIssue.objects.filter(returned=False)
|
|
# Filter staff users for witness
|
|
self.fields['witnessed_by'].queryset = User.objects.filter(is_staff=True)
|
|
|
|
|
|
class AdverseReactionForm(forms.ModelForm):
|
|
"""Form for adverse reaction reporting"""
|
|
|
|
class Meta:
|
|
model = AdverseReaction
|
|
fields = [
|
|
'transfusion', 'reaction_type', 'severity', 'onset_time',
|
|
'symptoms', 'treatment_given', 'outcome', 'investigation_notes',
|
|
'regulatory_reported'
|
|
]
|
|
widgets = {
|
|
'transfusion': forms.Select(attrs={'class': 'form-select'}),
|
|
'reaction_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'severity': forms.Select(attrs={'class': 'form-select'}),
|
|
'onset_time': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
|
'symptoms': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
|
|
'treatment_given': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
|
|
'outcome': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'investigation_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'regulatory_reported': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
}
|
|
|
|
|
|
class InventoryLocationForm(forms.ModelForm):
|
|
"""Form for inventory location management"""
|
|
|
|
class Meta:
|
|
model = InventoryLocation
|
|
fields = [
|
|
'name', 'location_type', 'temperature_range',
|
|
'capacity', 'is_active', 'notes'
|
|
]
|
|
widgets = {
|
|
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'location_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'temperature_range': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'capacity': forms.NumberInput(attrs={'class': 'form-control', 'min': '1'}),
|
|
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
}
|
|
|
|
|
|
class QualityControlForm(forms.ModelForm):
|
|
"""Form for quality control testing"""
|
|
|
|
class Meta:
|
|
model = QualityControl
|
|
fields = [
|
|
'test_type', 'test_date', 'equipment_tested',
|
|
'parameters_tested', 'expected_results', 'actual_results',
|
|
'status', 'corrective_action', 'next_test_date'
|
|
]
|
|
widgets = {
|
|
'test_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'test_date': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
|
'equipment_tested': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'parameters_tested': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'expected_results': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'actual_results': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'corrective_action': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
|
'next_test_date': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
|
}
|
|
|
|
|
|
class DonorEligibilityForm(forms.Form):
|
|
"""Form for donor eligibility screening"""
|
|
|
|
# Basic health questions
|
|
feeling_well = forms.BooleanField(
|
|
label="Are you feeling well today?",
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
|
|
adequate_sleep = forms.BooleanField(
|
|
label="Did you get adequate sleep last night?",
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
|
|
eaten_today = forms.BooleanField(
|
|
label="Have you eaten within the last 4 hours?",
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
|
|
# Medical history
|
|
recent_illness = forms.BooleanField(
|
|
required=False,
|
|
label="Have you had any illness in the past 2 weeks?",
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
|
|
medications = forms.BooleanField(
|
|
required=False,
|
|
label="Are you currently taking any medications?",
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
|
|
recent_travel = forms.BooleanField(
|
|
required=False,
|
|
label="Have you traveled outside the country in the past 3 months?",
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
|
|
# Risk factors
|
|
recent_tattoo = forms.BooleanField(
|
|
required=False,
|
|
label="Have you had a tattoo or piercing in the past 12 months?",
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
|
|
recent_surgery = forms.BooleanField(
|
|
required=False,
|
|
label="Have you had any surgery in the past 12 months?",
|
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
)
|
|
|
|
# Additional notes
|
|
notes = forms.CharField(
|
|
required=False,
|
|
label="Additional notes or concerns",
|
|
widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
|
|
)
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
|
|
# Check mandatory requirements
|
|
if not cleaned_data.get('feeling_well'):
|
|
raise forms.ValidationError("Donor must be feeling well to donate.")
|
|
|
|
if not cleaned_data.get('adequate_sleep'):
|
|
raise forms.ValidationError("Donor must have adequate sleep before donation.")
|
|
|
|
if not cleaned_data.get('eaten_today'):
|
|
raise forms.ValidationError("Donor must have eaten within the last 4 hours.")
|
|
|
|
return cleaned_data
|
|
|
|
|
|
class BloodInventorySearchForm(forms.Form):
|
|
"""Form for searching blood inventory"""
|
|
|
|
blood_group = forms.ModelChoiceField(
|
|
queryset=BloodGroup.objects.all(),
|
|
required=False,
|
|
empty_label="All Blood Groups",
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
|
|
component = forms.ModelChoiceField(
|
|
queryset=BloodComponent.objects.filter(is_active=True),
|
|
required=False,
|
|
empty_label="All Components",
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
|
|
status = forms.ChoiceField(
|
|
choices=[('', 'All Statuses')] + BloodUnit.BloodUnitStatus.choices,
|
|
required=False,
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
|
|
location = forms.ModelChoiceField(
|
|
queryset=InventoryLocation.objects.filter(is_active=True),
|
|
required=False,
|
|
empty_label="All Locations",
|
|
widget=forms.Select(attrs={'class': 'form-select'})
|
|
)
|
|
|
|
expiry_days = forms.IntegerField(
|
|
required=False,
|
|
label="Expiring within (days)",
|
|
widget=forms.NumberInput(attrs={'class': 'form-control', 'min': '0'})
|
|
)
|
|
|