2025-08-12 13:33:25 +03:00

355 lines
16 KiB
Python

"""
Forms for Pharmacy app CRUD operations (corrected to match models).
"""
from django import forms
from django.core.exceptions import ValidationError
from django.utils import timezone
from datetime import date
from .models import (
Medication, Prescription, InventoryItem as PharmacyInventoryItem,
DispenseRecord, MedicationAdministration, DrugInteraction
)
from patients.models import PatientProfile
from accounts.models import User
class MedicationForm(forms.ModelForm):
"""
Form for medication management.
"""
class Meta:
model = Medication
fields = [
'generic_name', 'brand_name', 'dosage_form', 'strength',
'unit_of_measure', 'ndc_number', 'manufacturer', 'drug_class',
'controlled_substance_schedule', 'indications', 'contraindications',
'side_effects', 'warnings', 'is_active', 'is_available'
]
widgets = {
'generic_name': forms.TextInput(attrs={'class': 'form-control'}),
'brand_name': forms.TextInput(attrs={'class': 'form-control'}),
'dosage_form': forms.Select(attrs={'class': 'form-select'}),
'strength': forms.TextInput(attrs={'class': 'form-control'}),
'unit_of_measure': forms.Select(attrs={'class': 'form-select'}),
'ndc_number': forms.TextInput(attrs={'class': 'form-control'}),
'manufacturer': forms.TextInput(attrs={'class': 'form-control'}),
'drug_class': forms.TextInput(attrs={'class': 'form-control'}),
'controlled_substance_schedule': forms.Select(attrs={'class': 'form-select'}),
'indications': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'contraindications': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'side_effects': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'warnings': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'is_available': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
def clean_ndc_number(self):
ndc_number = self.cleaned_data.get('ndc_number')
if ndc_number:
digits = ''.join(filter(str.isdigit, ndc_number))
if len(digits) != 11:
raise ValidationError('NDC number must be 11 digits.')
return ndc_number
return ndc_number
class PrescriptionForm(forms.ModelForm):
"""
Form for prescription management.
"""
class Meta:
model = Prescription
fields = [
'patient', 'prescriber', 'medication',
'quantity_prescribed', 'quantity_unit', 'dosage_instructions',
'frequency', 'duration', 'refills_authorized',
'refills_remaining', 'date_prescribed', 'date_written',
'expiration_date', 'status', 'indication', 'diagnosis_code',
'pharmacy_notes', 'patient_instructions', 'prior_authorization_required',
'prior_authorization_number',
]
widgets = {
'patient': forms.Select(attrs={'class': 'form-select'}),
'prescriber': forms.Select(attrs={'class': 'form-select'}),
'medication': forms.Select(attrs={'class': 'form-select'}),
'quantity_prescribed': forms.NumberInput(attrs={'class': 'form-control', 'min': 1}),
'quantity_unit': forms.Select(attrs={'class': 'form-select'}),
'dosage_instructions': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'frequency': forms.TextInput(attrs={'class': 'form-control'}),
'duration': forms.TextInput(attrs={'class': 'form-control'}),
'refills_authorized': forms.NumberInput(attrs={'class': 'form-control', 'min': 0}),
'refills_remaining': forms.NumberInput(attrs={'class': 'form-control', 'min': 0}),
'date_prescribed': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'date_written': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'expiration_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'status': forms.Select(attrs={'class': 'form-select'}),
'indication': forms.TextInput(attrs={'class': 'form-control'}),
'diagnosis_code': forms.TextInput(attrs={'class': 'form-control'}),
'pharmacy_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
'patient_instructions': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
'prior_authorization_required': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'prior_authorization_number': forms.TextInput(attrs={'class': 'form-control'}),
# 'priority': forms.Select(attrs={'class': 'form-select'}),
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user and hasattr(user, 'tenant'):
self.fields['patient'].queryset = PatientProfile.objects.filter(
tenant=user.tenant, is_active=True
).order_by('last_name')
self.fields['medication'].queryset = Medication.objects.filter(
tenant=user.tenant, is_active=True
).order_by('generic_name')
self.fields['prescriber'].queryset = User.objects.filter(
tenant=user.tenant, is_active=True,
role__in=['DOCTOR', 'SPECIALIST']
).order_by('last_name')
def clean(self):
data = super().clean()
dpres = data.get('date_prescribed')
dwritten = data.get('date_written')
dexp = data.get('expiration_date')
if dpres and dpres > timezone.now():
self.add_error('date_prescribed', 'Cannot be in the future.')
if dexp and dwritten and dexp <= dwritten:
self.add_error('expiration_date', 'Must be after written date.')
auth = data.get('refills_authorized')
rem = data.get('refills_remaining')
if rem is not None and auth is not None and rem > auth:
self.add_error('refills_remaining', 'Cannot exceed authorized refills.')
return data
class InventoryItemForm(forms.ModelForm):
"""
Form for pharmacy inventory item management.
"""
class Meta:
model = PharmacyInventoryItem
fields = [
'medication', 'lot_number', 'expiration_date',
'quantity_on_hand', 'quantity_allocated',
'reorder_point', 'reorder_quantity', 'storage_location',
'bin_location',
]
widgets = {
'medication': forms.Select(attrs={'class': 'form-select'}),
'lot_number': forms.TextInput(attrs={'class': 'form-control'}),
'expiration_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'quantity_on_hand': forms.NumberInput(attrs={'class': 'form-control', 'min': 0}),
'quantity_allocated': forms.NumberInput(attrs={'class': 'form-control', 'min': 0}),
'reorder_point': forms.NumberInput(attrs={'class': 'form-control', 'min': 0}),
'reorder_quantity': forms.NumberInput(attrs={'class': 'form-control', 'min': 0}),
'storage_location': forms.TextInput(attrs={'class': 'form-control'}),
'bin_location': forms.TextInput(attrs={'class': 'form-control'}),
# 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
def clean_expiration_date(self):
exp = self.cleaned_data.get('expiration_date')
if exp and exp <= date.today():
raise ValidationError('Must be future date.')
return exp
def clean(self):
data = super().clean()
rp = data.get('reorder_point')
rq = data.get('reorder_quantity')
if rp is not None and rq is not None and rq <= rp:
self.add_error('reorder_quantity', 'Must exceed reorder point.')
return data
class DispenseRecordForm(forms.ModelForm):
"""
Form for dispense record management.
"""
class Meta:
model = DispenseRecord
fields = [
'prescription', 'inventory_item', 'quantity_dispensed',
'dispensed_by', 'date_dispensed', 'patient_counseled', 'counseling_notes'
]
widgets = {
'prescription': forms.Select(attrs={'class': 'form-select'}),
'inventory_item': forms.Select(attrs={'class': 'form-select'}),
'quantity_dispensed': forms.NumberInput(attrs={'class': 'form-control', 'min': 1}),
'dispensed_by': forms.Select(attrs={'class': 'form-select'}),
'date_dispensed': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'patient_counseled': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'counseling_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user and hasattr(user, 'tenant'):
self.fields['prescription'].queryset = Prescription.objects.filter(
tenant=user.tenant, status__in=['ACTIVE', 'PENDING']
).order_by('-date_prescribed')
self.fields['inventory_item'].queryset = PharmacyInventoryItem.objects.filter(
tenant=user.tenant, is_active=True, quantity_on_hand__gt=0
).order_by('medication__generic_name')
self.fields['dispensed_by'].queryset = User.objects.filter(
tenant=user.tenant, role__in=['PHARMACIST', 'PHARMACY_TECH']
).order_by('last_name')
def clean_date_dispensed(self):
dd = self.cleaned_data.get('date_dispensed')
if dd and dd > timezone.now():
raise ValidationError('Cannot be in future.')
return dd
def clean(self):
data = super().clean()
pres = data.get('prescription')
inv = data.get('inventory_item')
qty = data.get('quantity_dispensed')
if pres and inv and pres.medication != inv.medication:
self.add_error('inventory_item', 'Medication mismatch.')
if inv and qty and qty > inv.quantity_on_hand:
self.add_error('quantity_dispensed', 'Exceeds on-hand.')
return data
class MedicationAdministrationForm(forms.ModelForm):
"""
Form for medication administration management.
"""
class Meta:
model = MedicationAdministration
fields = [
'prescription', 'scheduled_datetime', 'actual_datetime',
'dose_given', 'route_given', 'status', 'administered_by',
'witnessed_by', 'reason_not_given', 'reason_notes',
'patient_response', 'side_effects_observed'
]
widgets = {
'prescription': forms.Select(attrs={'class': 'form-select'}),
'scheduled_datetime': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'actual_datetime': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}),
'dose_given': forms.TextInput(attrs={'class': 'form-control'}),
'route_given': forms.Select(attrs={'class': 'form-select'}),
'status': forms.Select(attrs={'class': 'form-select'}),
'administered_by': forms.Select(attrs={'class': 'form-select'}),
'witnessed_by': forms.Select(attrs={'class': 'form-select'}),
'reason_not_given': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
'reason_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
'patient_response': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
'side_effects_observed': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user and hasattr(user, 'tenant'):
self.fields['prescription'].queryset = Prescription.objects.filter(
tenant=user.tenant
)
self.fields['administered_by'].queryset = User.objects.filter(
tenant=user.tenant, role__in=['NURSE', 'PHARMACIST']
)
self.fields['witnessed_by'].queryset = User.objects.filter(
tenant=user.tenant, role='PHARMACIST'
)
def clean(self):
data = super().clean()
status = data.get('status')
reason = data.get('reason_not_given')
dose = data.get('dose_given')
if status == 'NOT_GIVEN' and not reason:
self.add_error('reason_not_given', 'Provide a reason.')
if status == 'GIVEN' and not dose:
self.add_error('dose_given', 'Dose is required.')
return data
class DrugInteractionForm(forms.ModelForm):
"""
Form for drug interaction management.
"""
class Meta:
model = DrugInteraction
fields = [
'medication_1', 'medication_2', 'interaction_type',
'severity', 'mechanism', 'clinical_effect',
'management_recommendations', 'monitoring_parameters', 'is_active'
]
widgets = {
'medication_1': forms.Select(attrs={'class': 'form-select'}),
'medication_2': forms.Select(attrs={'class': 'form-select'}),
'interaction_type': forms.Select(attrs={'class': 'form-select'}),
'severity': forms.Select(attrs={'class': 'form-select'}),
'mechanism': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'clinical_effect': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'management_recommendations': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'monitoring_parameters': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
}
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user and hasattr(user, 'tenant'):
meds = Medication.objects.filter(tenant=user.tenant, is_active=True)
self.fields['medication_1'].queryset = meds
self.fields['medication_2'].queryset = meds
def clean(self):
data = super().clean()
if data.get('medication_1') == data.get('medication_2'):
raise ValidationError('Medications must differ.')
return data
class PharmacySearchForm(forms.Form):
"""
Form for searching pharmacy data.
"""
search = forms.CharField(required=False, max_length=255,
widget=forms.TextInput(attrs={'class':'form-control','placeholder':'Search...'})
)
patient = forms.ModelChoiceField(
queryset=PatientProfile.objects.none(), required=False,
widget=forms.Select(attrs={'class':'form-select'})
)
medication = forms.ModelChoiceField(
queryset=Medication.objects.none(), required=False,
widget=forms.Select(attrs={'class':'form-select'})
)
prescriber = forms.ModelChoiceField(
queryset=User.objects.none(), required=False,
widget=forms.Select(attrs={'class':'form-select'})
)
status = forms.ChoiceField(
choices=[('', 'All')] + list(Prescription._meta.get_field('status').choices),
required=False, widget=forms.Select(attrs={'class':'form-select'})
)
date_from = forms.DateField(required=False,
widget=forms.DateInput(attrs={'type':'date','class':'form-control'})
)
date_to = forms.DateField(required=False,
widget=forms.DateInput(attrs={'type':'date','class':'form-control'})
)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user and hasattr(user, 'tenant'):
self.fields['patient'].queryset = PatientProfile.objects.filter(
tenant=user.tenant, is_active=True
).order_by('last_name')
self.fields['medication'].queryset = Medication.objects.filter(
tenant=user.tenant, is_active=True
).order_by('generic_name')
self.fields['prescriber'].queryset = User.objects.filter(
tenant=user.tenant, is_active=True,
role__in=['DOCTOR','SPECIALIST']
).order_by('last_name')