556 lines
16 KiB
Python
556 lines
16 KiB
Python
"""
|
|
Admin configuration for pharmacy app.
|
|
"""
|
|
|
|
from django.contrib import admin
|
|
from django.utils.html import format_html
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from .models import (
|
|
Medication, Prescription, MedicationInventoryItem, DispenseRecord,
|
|
MedicationAdministration, DrugInteraction
|
|
)
|
|
|
|
|
|
@admin.register(Medication)
|
|
class MedicationAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for Medication model.
|
|
"""
|
|
list_display = [
|
|
'generic_name', 'brand_name', 'strength', 'dosage_form',
|
|
'controlled_substance_schedule', 'formulary_status', 'is_active'
|
|
]
|
|
list_filter = [
|
|
'dosage_form', 'controlled_substance_schedule', 'formulary_status',
|
|
'is_active', 'drug_class', 'created_at'
|
|
]
|
|
search_fields = [
|
|
'generic_name', 'brand_name', 'ndc_number', 'rxcui', 'drug_class'
|
|
]
|
|
readonly_fields = ['medication_id', 'created_at', 'updated_at']
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': (
|
|
'medication_id', 'generic_name', 'brand_name', 'drug_class'
|
|
)
|
|
}),
|
|
('Drug Identification', {
|
|
'fields': (
|
|
'ndc_number', 'rxcui', 'controlled_substance_schedule'
|
|
)
|
|
}),
|
|
('Formulation', {
|
|
'fields': (
|
|
'dosage_form', 'strength', 'unit_of_measure'
|
|
)
|
|
}),
|
|
('Clinical Information', {
|
|
'fields': (
|
|
'indications', 'contraindications', 'side_effects', 'warnings'
|
|
)
|
|
}),
|
|
('Dosing Information', {
|
|
'fields': (
|
|
'adult_dose_range', 'pediatric_dose_range', 'max_daily_dose',
|
|
'routes_of_administration', 'administration_instructions'
|
|
)
|
|
}),
|
|
('Storage and Handling', {
|
|
'fields': (
|
|
'storage_requirements', 'special_handling'
|
|
)
|
|
}),
|
|
('Formulary and Availability', {
|
|
'fields': (
|
|
'formulary_status', 'is_active', 'is_available'
|
|
)
|
|
}),
|
|
('Pricing', {
|
|
'fields': (
|
|
'unit_cost', 'awp'
|
|
)
|
|
}),
|
|
('Manufacturer', {
|
|
'fields': (
|
|
'manufacturer', 'manufacturer_ndc'
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at', 'created_by'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def get_queryset(self, request):
|
|
"""Filter by tenant."""
|
|
qs = super().get_queryset(request)
|
|
if hasattr(request.user, 'tenant'):
|
|
return qs.filter(tenant=request.user.tenant)
|
|
return qs.none()
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
"""Set tenant and created_by."""
|
|
if not change:
|
|
obj.tenant = request.user.tenant
|
|
obj.created_by = request.user
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
class DispenseRecordInline(admin.TabularInline):
|
|
"""
|
|
Inline admin for dispense records.
|
|
"""
|
|
model = DispenseRecord
|
|
extra = 0
|
|
readonly_fields = ['dispense_id', 'created_at']
|
|
fields = [
|
|
'inventory_stock', 'quantity_dispensed', 'date_dispensed',
|
|
'dispensed_by', 'status', 'patient_counseled'
|
|
]
|
|
|
|
|
|
@admin.register(Prescription)
|
|
class PrescriptionAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for Prescription model.
|
|
"""
|
|
list_display = [
|
|
'prescription_number', 'patient_name', 'medication_name',
|
|
'prescriber', 'status', 'date_prescribed', 'expiration_date'
|
|
]
|
|
list_filter = [
|
|
'status', 'date_prescribed', 'verified', 'electronic_prescription',
|
|
'generic_substitution_allowed', 'prior_authorization_required'
|
|
]
|
|
search_fields = [
|
|
'prescription_number', 'patient__first_name', 'patient__last_name',
|
|
'patient__mrn', 'medication__generic_name', 'medication__brand_name'
|
|
]
|
|
readonly_fields = [
|
|
'prescription_id', 'prescription_number', 'created_at', 'updated_at'
|
|
]
|
|
inlines = [DispenseRecordInline]
|
|
|
|
fieldsets = (
|
|
('Prescription Information', {
|
|
'fields': (
|
|
'prescription_id', 'prescription_number', 'patient',
|
|
'prescriber', 'encounter'
|
|
)
|
|
}),
|
|
('Medication', {
|
|
'fields': (
|
|
'medication', 'quantity_prescribed', 'quantity_unit'
|
|
)
|
|
}),
|
|
('Dosing Instructions', {
|
|
'fields': (
|
|
'dosage_instructions', 'frequency', 'duration'
|
|
)
|
|
}),
|
|
('Refills', {
|
|
'fields': (
|
|
'refills_authorized', 'refills_remaining'
|
|
)
|
|
}),
|
|
('Dates', {
|
|
'fields': (
|
|
'date_prescribed', 'date_written', 'expiration_date'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': (
|
|
'status', 'verified', 'verified_by', 'verified_datetime'
|
|
)
|
|
}),
|
|
('Clinical Information', {
|
|
'fields': (
|
|
'indication', 'diagnosis_code'
|
|
)
|
|
}),
|
|
('Instructions', {
|
|
'fields': (
|
|
'pharmacy_notes', 'patient_instructions'
|
|
)
|
|
}),
|
|
('Prior Authorization', {
|
|
'fields': (
|
|
'prior_authorization_required', 'prior_authorization_number',
|
|
'prior_authorization_expiry'
|
|
)
|
|
}),
|
|
('Substitution', {
|
|
'fields': (
|
|
'generic_substitution_allowed', 'dispense_as_written'
|
|
)
|
|
}),
|
|
('Electronic Prescription', {
|
|
'fields': (
|
|
'electronic_prescription', 'e_prescription_id'
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def patient_name(self, obj):
|
|
"""Get patient name."""
|
|
return obj.patient.get_full_name()
|
|
patient_name.short_description = 'Patient'
|
|
|
|
def medication_name(self, obj):
|
|
"""Get medication name."""
|
|
return obj.medication.display_name
|
|
medication_name.short_description = 'Medication'
|
|
|
|
def get_queryset(self, request):
|
|
"""Filter by tenant."""
|
|
qs = super().get_queryset(request)
|
|
if hasattr(request.user, 'tenant'):
|
|
return qs.filter(tenant=request.user.tenant)
|
|
return qs.none()
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
"""Set tenant."""
|
|
if not change:
|
|
obj.tenant = request.user.tenant
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
@admin.register(MedicationInventoryItem)
|
|
class MedicationInventoryItemAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for MedicationInventoryItem model.
|
|
"""
|
|
list_display = [
|
|
'medication_name', 'inventory_item_name', 'formulary_tier',
|
|
'requires_counseling', 'requires_id_verification'
|
|
]
|
|
list_filter = [
|
|
'formulary_tier', 'therapeutic_equivalent', 'auto_substitution_allowed',
|
|
'requires_counseling', 'requires_id_verification'
|
|
]
|
|
search_fields = [
|
|
'medication__generic_name', 'medication__brand_name',
|
|
'inventory_item__item_name', 'inventory_item__item_code'
|
|
]
|
|
readonly_fields = [
|
|
'medication_inventory_id', 'created_at', 'updated_at'
|
|
]
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': (
|
|
'medication_inventory_id', 'medication', 'inventory_item'
|
|
)
|
|
}),
|
|
('Pharmacy Settings', {
|
|
'fields': (
|
|
'formulary_tier', 'therapeutic_equivalent', 'auto_substitution_allowed'
|
|
)
|
|
}),
|
|
('Dispensing Rules', {
|
|
'fields': (
|
|
'max_dispense_quantity', 'requires_counseling', 'requires_id_verification'
|
|
)
|
|
}),
|
|
('Notes', {
|
|
'fields': (
|
|
'pharmacy_notes',
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at', 'created_by'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def medication_name(self, obj):
|
|
"""Get medication name."""
|
|
return obj.medication.display_name
|
|
medication_name.short_description = 'Medication'
|
|
|
|
def inventory_item_name(self, obj):
|
|
"""Get inventory item name."""
|
|
return obj.inventory_item.item_name
|
|
inventory_item_name.short_description = 'Inventory Item'
|
|
|
|
def get_queryset(self, request):
|
|
"""Filter by tenant."""
|
|
qs = super().get_queryset(request)
|
|
if hasattr(request.user, 'tenant'):
|
|
return qs.filter(tenant=request.user.tenant)
|
|
return qs.none()
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
"""Set tenant and created_by."""
|
|
if not change:
|
|
obj.tenant = request.user.tenant
|
|
obj.created_by = request.user
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
@admin.register(DispenseRecord)
|
|
class DispenseRecordAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for DispenseRecord model.
|
|
"""
|
|
list_display = [
|
|
'prescription_number', 'patient_name', 'medication_name',
|
|
'quantity_dispensed', 'date_dispensed', 'dispensed_by', 'status'
|
|
]
|
|
list_filter = [
|
|
'status', 'date_dispensed', 'is_refill', 'patient_counseled',
|
|
'quality_check_performed'
|
|
]
|
|
search_fields = [
|
|
'prescription__prescription_number', 'prescription__patient__first_name',
|
|
'prescription__patient__last_name'
|
|
]
|
|
readonly_fields = [
|
|
'dispense_id', 'total_price', 'quantity_remaining',
|
|
'created_at', 'updated_at'
|
|
]
|
|
|
|
fieldsets = (
|
|
('Dispense Information', {
|
|
'fields': (
|
|
'dispense_id', 'prescription', 'inventory_stock'
|
|
)
|
|
}),
|
|
('Quantity', {
|
|
'fields': (
|
|
'quantity_dispensed', 'quantity_remaining'
|
|
)
|
|
}),
|
|
('Date and Staff', {
|
|
'fields': (
|
|
'date_dispensed', 'dispensed_by', 'verified_by'
|
|
)
|
|
}),
|
|
('Pricing', {
|
|
'fields': (
|
|
'unit_price', 'total_price', 'copay_amount', 'insurance_amount'
|
|
)
|
|
}),
|
|
('Patient Information', {
|
|
'fields': (
|
|
'patient_counseled', 'counseling_notes'
|
|
)
|
|
}),
|
|
('Refill Information', {
|
|
'fields': (
|
|
'is_refill', 'refill_number'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': (
|
|
'status',
|
|
)
|
|
}),
|
|
('Pickup Information', {
|
|
'fields': (
|
|
'picked_up_by', 'pickup_datetime', 'identification_verified'
|
|
)
|
|
}),
|
|
('Quality Control', {
|
|
'fields': (
|
|
'quality_check_performed', 'quality_notes'
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def prescription_number(self, obj):
|
|
"""Get prescription number."""
|
|
return obj.prescription.prescription_number
|
|
prescription_number.short_description = 'Prescription #'
|
|
|
|
def patient_name(self, obj):
|
|
"""Get patient name."""
|
|
return obj.patient.get_full_name()
|
|
patient_name.short_description = 'Patient'
|
|
|
|
def medication_name(self, obj):
|
|
"""Get medication name."""
|
|
return obj.medication
|
|
medication_name.short_description = 'Medication'
|
|
|
|
|
|
@admin.register(MedicationAdministration)
|
|
class MedicationAdministrationAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for MedicationAdministration model.
|
|
"""
|
|
list_display = [
|
|
'patient_name', 'medication_name', 'scheduled_datetime',
|
|
'actual_datetime', 'status', 'administered_by'
|
|
]
|
|
list_filter = [
|
|
'status', 'scheduled_datetime', 'route_given', 'double_checked'
|
|
]
|
|
search_fields = [
|
|
'patient__first_name', 'patient__last_name',
|
|
'prescription__medication__generic_name'
|
|
]
|
|
readonly_fields = [
|
|
'administration_id', 'created_at', 'updated_at'
|
|
]
|
|
|
|
fieldsets = (
|
|
('Administration Information', {
|
|
'fields': (
|
|
'administration_id', 'prescription', 'patient', 'encounter'
|
|
)
|
|
}),
|
|
('Scheduled vs Actual', {
|
|
'fields': (
|
|
'scheduled_datetime', 'actual_datetime'
|
|
)
|
|
}),
|
|
('Dosage', {
|
|
'fields': (
|
|
'dose_given', 'route_given'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': (
|
|
'status',
|
|
)
|
|
}),
|
|
('Staff', {
|
|
'fields': (
|
|
'administered_by', 'witnessed_by'
|
|
)
|
|
}),
|
|
('Reason for Not Given', {
|
|
'fields': (
|
|
'reason_not_given', 'reason_notes'
|
|
)
|
|
}),
|
|
('Patient Response', {
|
|
'fields': (
|
|
'patient_response', 'side_effects_observed'
|
|
)
|
|
}),
|
|
('Site Information', {
|
|
'fields': (
|
|
'injection_site', 'site_condition'
|
|
)
|
|
}),
|
|
('Verification', {
|
|
'fields': (
|
|
'double_checked', 'double_checked_by'
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def patient_name(self, obj):
|
|
"""Get patient name."""
|
|
return obj.patient.get_full_name()
|
|
patient_name.short_description = 'Patient'
|
|
|
|
def medication_name(self, obj):
|
|
"""Get medication name."""
|
|
return obj.medication.display_name
|
|
medication_name.short_description = 'Medication'
|
|
|
|
|
|
@admin.register(DrugInteraction)
|
|
class DrugInteractionAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin interface for DrugInteraction model.
|
|
"""
|
|
list_display = [
|
|
'medication_1_name', 'medication_2_name', 'severity',
|
|
'interaction_type', 'evidence_level', 'is_active'
|
|
]
|
|
list_filter = [
|
|
'severity', 'interaction_type', 'evidence_level', 'is_active'
|
|
]
|
|
search_fields = [
|
|
'medication_1__generic_name', 'medication_1__brand_name',
|
|
'medication_2__generic_name', 'medication_2__brand_name'
|
|
]
|
|
readonly_fields = [
|
|
'interaction_id', 'created_at', 'updated_at'
|
|
]
|
|
|
|
fieldsets = (
|
|
('Interaction Information', {
|
|
'fields': (
|
|
'interaction_id', 'medication_1', 'medication_2'
|
|
)
|
|
}),
|
|
('Severity and Type', {
|
|
'fields': (
|
|
'severity', 'interaction_type'
|
|
)
|
|
}),
|
|
('Interaction Details', {
|
|
'fields': (
|
|
'mechanism', 'clinical_effect'
|
|
)
|
|
}),
|
|
('Management', {
|
|
'fields': (
|
|
'management_recommendations', 'monitoring_parameters'
|
|
)
|
|
}),
|
|
('Evidence', {
|
|
'fields': (
|
|
'evidence_level', 'references'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': (
|
|
'is_active',
|
|
)
|
|
}),
|
|
('Metadata', {
|
|
'fields': (
|
|
'created_at', 'updated_at', 'created_by'
|
|
)
|
|
}),
|
|
)
|
|
|
|
def medication_1_name(self, obj):
|
|
"""Get first medication name."""
|
|
return obj.medication_1.display_name
|
|
medication_1_name.short_description = 'Medication 1'
|
|
|
|
def medication_2_name(self, obj):
|
|
"""Get second medication name."""
|
|
return obj.medication_2.display_name
|
|
medication_2_name.short_description = 'Medication 2'
|
|
|
|
def get_queryset(self, request):
|
|
"""Filter by tenant."""
|
|
qs = super().get_queryset(request)
|
|
if hasattr(request.user, 'tenant'):
|
|
return qs.filter(tenant=request.user.tenant)
|
|
return qs.none()
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
"""Set tenant and created_by."""
|
|
if not change:
|
|
obj.tenant = request.user.tenant
|
|
obj.created_by = request.user
|
|
super().save_model(request, obj, form, change)
|