Marwan Alwali ab2c4a36c5 update
2025-10-02 10:13:03 +03:00

356 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, MedicationInventoryItem,
DispenseRecord, MedicationAdministration, DrugInteraction
)
from inventory.models import InventoryStock
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,
employee_profile__role__in=['DOCTOR', 'SPECIALIST', 'PHYSICIAN', 'PHYSICIAN_ASSISTANT', 'SURGEON']
).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 MedicationInventoryItemForm(forms.ModelForm):
"""
Form for medication inventory item management.
"""
class Meta:
model = MedicationInventoryItem
fields = [
'medication', 'inventory_item', 'formulary_tier',
'therapeutic_equivalent', 'auto_substitution_allowed',
'max_dispense_quantity', 'requires_counseling',
'requires_id_verification', 'pharmacy_notes'
]
widgets = {
'medication': forms.Select(attrs={'class': 'form-select'}),
'inventory_item': forms.Select(attrs={'class': 'form-select'}),
'formulary_tier': forms.TextInput(attrs={'class': 'form-control'}),
'therapeutic_equivalent': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'auto_substitution_allowed': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'max_dispense_quantity': forms.NumberInput(attrs={'class': 'form-control', 'min': 1}),
'requires_counseling': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'requires_id_verification': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
'pharmacy_notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
}
# Legacy alias for backward compatibility
InventoryItemForm = MedicationInventoryItemForm
class DispenseRecordForm(forms.ModelForm):
"""
Form for dispense record management.
"""
class Meta:
model = DispenseRecord
fields = [
'prescription', 'inventory_stock', 'quantity_dispensed',
'dispensed_by', 'date_dispensed', 'patient_counseled', 'counseling_notes'
]
widgets = {
'prescription': forms.Select(attrs={'class': 'form-select'}),
'inventory_stock': 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')
# Get inventory stocks for pharmacy medications with available quantity
self.fields['inventory_stock'].queryset = InventoryStock.objects.filter(
inventory_item__tenant=user.tenant,
inventory_item__category='PHARMACY_MEDICATIONS',
quantity_available__gt=0,
quality_status='GOOD'
).order_by('inventory_item__item_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_stock = data.get('inventory_stock')
qty = data.get('quantity_dispensed')
if pres and inv_stock:
# Check if the inventory stock is for the prescribed medication
med_inventory = inv_stock.inventory_item.medication_inventory_items.filter(
medication=pres.medication
).first()
if not med_inventory:
self.add_error('inventory_stock', 'Selected stock does not match prescribed medication.')
if inv_stock and qty and qty > inv_stock.quantity_available:
self.add_error('quantity_dispensed', 'Exceeds available quantity.')
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')